Skip to content

Commit

Permalink
More work to export/import class definitions.
Browse files Browse the repository at this point in the history
  • Loading branch information
gaspard committed Apr 15, 2011
1 parent e63fc4c commit 447179c
Show file tree
Hide file tree
Showing 23 changed files with 936 additions and 376 deletions.
109 changes: 94 additions & 15 deletions app/controllers/virtual_classes_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class VirtualClassesController < ApplicationController
before_filter :find_virtual_class, :except => [:index, :create, :new, :import, :export]
before_filter :find_virtual_class, :except => [
:index, :create, :new, :import, :import_prepare, :export
]
before_filter :visitor_node
before_filter :check_is_admin
layout :admin_layout
Expand Down Expand Up @@ -43,36 +45,64 @@ def index
end

def export
res = {}
secure(::Role) do
::Role.all.each do |role|
res[role.name] = role.export
end
end

send_data(res.to_yaml, :filename=>"roles.yml", :type => 'text/yaml')
send_data(clean_yaml(::Role.export), :filename=>"roles.yml", :type => 'text/yaml')
end

def import
def import_prepare
attachment = params[:attachment]
if attachment.nil?
flass[:error] = "Upload failure: no definitions."
redirect_to :action => :index
else
data = YAML.load(attachment.read) rescue nil
@yaml = attachment.read rescue nil
data = YAML.load(@yaml) rescue nil
if data.nil?
flash[:error] = "Could not parse yaml document"
redirect_to :action => :index
else
@virtual_classes = secure(VirtualClass) { VirtualClass.import(data) }.paginate(:per_page => 200)
@virtual_class = VirtualClass.new('')
respond_to do |format|
format.html { render :action => 'index' }
@yaml.gsub!(/\A---\s*\n/, "--- \n")
current = current_compared_to(data)
a = clean_yaml(current).gsub('{}', '')
#a = Zena::CodeSyntax.new(a, 'yaml').to_html
b = @yaml
#b = Zena::CodeSyntax.new(b, 'yaml').to_html
@diff = Differ.diff_by_line(b, a).format_as(:html)

# UGLY HACK to not escape <ins> and <del>
@diff.gsub!(/<((ins|del)[^>]*)>/, '[yaml_diff[\1]]')
@diff.gsub!(/<\/(ins|del)>/, '[yaml_diff[/\1]]')
@diff = Zena::CodeSyntax.new(@diff, 'yaml').to_html
@diff.gsub!(/\[yaml_diff\[([^\]]+)\]\]/) do
"<#{$1.gsub('&quot;', '"')}>"
end
end
end
end

def import
data = YAML.load(params[:roles]) rescue nil
if data.nil?
flash[:error] = "Could not parse yaml document"
redirect_to :action => :index
else
@roles_backup = clean_yaml(::Role.export)
@virtual_classes = ::Role.import(data)
class << @virtual_classes
def total_pages; 1 end
end
@virtual_class = VirtualClass.new('')
respond_to do |format|
format.html { render :action => 'index' }
end
end
rescue ActiveRecord::RecordInvalid => e
flash[:error] = "#{r.class} '#{r.name}' #{e.message}"
redirect_to :action => :index
rescue Exception => e
flash[:error] = e.message
redirect_to :action => :index
end

def show
respond_to do |format|
format.html # show.erb
Expand Down Expand Up @@ -149,4 +179,53 @@ def destroy
def find_virtual_class
@virtual_class = secure!(VirtualClass) { ::Role.find(params[:id])}
end

def clean_yaml(export)
# We use an OrderedHash so that using diff is more consistent.
export.to_yaml.gsub(/: *!map:Zafu::OrderedHash */, ':')
end

def get_roles(definitions, res = {})
definitions.each do |name, definition|
next unless name =~ /\A[A-Z]/
res[name] = definition
get_roles(definition, res)
end
res
end
# When comparing current data with imported data, ignore untouched
# classes/definitions.
def current_compared_to(definitions)
current = ::Role.export
roles = get_roles(definitions)
puts roles.inspect
filter_keys(current, definitions, roles)
current
end

def filter_keys(hash_a, hash_b, roles)
remove_all = true
hash_a.keys.each do |key|
value_a, value_b = hash_a[key], hash_b[key]
if !value_b
if key =~ /\A[A-Z]/ && roles[key]
# role missing but defined elsewhere: moved
# do not remove
remove_all = false
elsif value_a.kind_of?(Hash)
if filter_keys(hash_a[key], {}, roles)
# nothing to be kept here
hash_a.delete(key)
end
else
# ignore missing key if we can ignore all
hash_a.delete(key)
end
elsif value_a.kind_of?(Hash) && value_b.kind_of?(Hash)
remove_all = false
filter_keys(hash_a[key], hash_b[key], roles)
end
end
remove_all
end
end
12 changes: 12 additions & 0 deletions app/models/column.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Column < ActiveRecord::Base
validates_presence_of :role
validates_uniqueness_of :name, :scope => :site_id
validate :name_not_in_models
validate :valid_ptype_and_index

after_save :expire_vclass_cache
after_destroy :expire_vclass_cache
Expand Down Expand Up @@ -117,6 +118,7 @@ def export

protected
def set_defaults
self.index = nil if index.blank?
self[:site_id] = current_site.id
end

Expand All @@ -141,4 +143,14 @@ def name_not_in_models
def expire_vclass_cache
VirtualClass.expire_cache!
end

def valid_ptype_and_index
if !TYPES_FOR_FORM.include?(self.ptype)
errors.add(:ptype, 'invalid')
end

if !index.blank? && !(INDICES_FOR_FORM + FIELD_INDICES.map {|i| ".#{i}"}).include?(index)
errors.add(:index, 'invalid')
end
end
end
153 changes: 144 additions & 9 deletions app/models/role.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,115 @@ class Role < ActiveRecord::Base
# We use property to store index information, default values and such
include Property

class << self
def export
{'Node' => VirtualClass['Node'].export}
end

def import(definitions, delete = false)
res = []
# Create everything in a transaction
transaction do
definitions.each do |name, definition|
klass = VirtualClass[name]
if !klass || !klass.real_class?
# Error, missing superclass
raise Exception.new("Importation needs to start with a real class: '#{name}' is not a real class.")
else
# start importing
res += import_all(klass, definition)
end
end
end
res
end

private
def import_all(superclass, definitions)
res = []
definitions.each do |name, sub|
next unless name =~ /\A[A-Z]/

klass = VirtualClass[name]
if klass && klass.real_class?
res += import_all(klass, sub)
elsif sub['type'] == 'Role'
res << import_role(superclass, name, sub)
elsif sub['type'] == 'Class'
# real class
raise Exception.new("Unknown real class '#{name}'.")
elsif sub['type'] == 'VirtualClass' || sub['type'].nil?
# VirtualClass
res += import_vclass(superclass, name, sub)
else
# Invalid type
raise Exception.new("Cannot create '#{name}': invalid type '#{sub['type']}'.")
res << tmp
end
end
res
end

def import_role(superclass, name, definition)
role = ::Role.find_by_name_and_site_id(name, current_site.id)
if role && role.class != ::Role
# Change from vclass to role ?
# Reject
raise Exception.new("Cannot convert VirtualClass '#{name}' to Role.")
elsif !role
role = ::Role.new(:name => name, :superclass => superclass)
role.save!
end

# 1. create or update attributes
# noop

# 2. create or update columns (never delete)
if !role.new_record? && columns = definition['columns']
role.import_columns(columns)
end
role
end

def import_vclass(superclass, name, definition)
res = []
vclass = ::Role.find_by_name_and_site_id(name, current_site.id)
if vclass && vclass.class != VirtualClass
# Change from role to vclass ?
# Reject
raiseException.new("Cannot convert Role '#{name}' to VirtualClass.")
elsif !vclass
vclass = VirtualClass.new(:name => name, :superclass => superclass)
end

# 1. create or update attributes
VirtualClass::EXPORT_ATTRIBUTES.each do |key|
if value = definition[key]
vclass[key] = value
else
# We do not clear attributes (import is ADD/UPDATE only).
end
end
vclass.save!
res << vclass

# 2. create or update columns (never delete)
if !vclass.new_record? && columns = definition['columns']
vclass.import_columns(columns)
end

# 3. create or update sub-classes
res += import_all(vclass, definition)
# 4. create relations
# FIXME.....
res
end
end # class << self

def real_class?
false
end

def superclass
if new_record?
Node
Expand All @@ -50,18 +159,42 @@ def defined_safe_columns
end

def export
res = {
'name' => name,
'superclass' => superclass.name,
'kpath' => kpath,
'type' => type,
}
res = Zafu::OrderedHash.new
res['type'] = real_class? ? 'Class' : type
if !defined_columns.empty?
res['columns'] = export_columns
end
res
end

def import_columns(columns)
columns.each do |name, definition|
column = secure(::Column) { ::Column.find_by_name(name) }
if !column
# create
column = ::Column.new(:name => name)
elsif column.role_id != self.id
# error (do not move a column)
raise Exception.new("Cannot set property '#{name}' in '#{self.name}': already defined in '#{column.role.name}'.")
end
column.role_id = self.id
column.ptype = definition['ptype']
column.index = definition['index']
column.save!
# if !column.errors.empty?
# errors = []
# column.errors.each_error do |er, msg|
# errors << "#{er} #{msg}"
# end
# if column.new_record?
# raise "Could not create property '#{name}': #{errors.join(', ')}"
# else
# raise"Could not update property '#{name}': #{errors.join(', ')}"
# end
# end
end
end

private
def set_defaults
self[:type] = self.class.to_s
Expand All @@ -77,10 +210,12 @@ def expire_vclass_cache
end

def export_columns
res = {}
res = Zafu::OrderedHash.new

defined_columns.each do |name, column|
col = {'ptype' => column.ptype.to_s}
defined_columns.keys.sort.each do |name|
column = defined_columns[name]
col = Zafu::OrderedHash.new
col['ptype'] = column.ptype.to_s
if column.index then
col['index'] = column.index
end
Expand Down

0 comments on commit 447179c

Please sign in to comment.