Skip to content

Commit

Permalink
Added 'idx_reverse_scope' to scope_index with a trigger on destroy (u…
Browse files Browse the repository at this point in the history
…sed to update scope_index on destroy).
  • Loading branch information
gaspard committed Mar 17, 2011
1 parent 8aaaa7f commit 9efa182
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 18 deletions.
3 changes: 2 additions & 1 deletion app/models/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ def self.author_proc
:data_b => {:class => ['DataEntry'], :zafu => {:data_root => 'node_b'}},
:data_c => {:class => ['DataEntry'], :zafu => {:data_root => 'node_c'}},
:data_d => {:class => ['DataEntry'], :zafu => {:data_root => 'node_d'}},
:traductions => ['Version'], :discussion => 'Discussion'
:traductions => ['Version'], :discussion => 'Discussion',
:project => 'Node'

# we use safe_method because the columns can be null, but the values are never null
safe_method :kpath => String, :user_zip => Number,
Expand Down
3 changes: 2 additions & 1 deletion app/views/virtual_classes/_form.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@
<% if @virtual_class.kind_of?(VirtualClass) -%>
<% Zena::Use::Fulltext::FULLTEXT_FIELDS.reverse_each do |fld| -%>
<tr><td class='label'><%= _(fld)%></td><td><%= text_area('virtual_class', fld, :rows => 2, :cols => 30) %></td></tr>
<% end -%>
<% end -%>
<tr><td class='label'><%= _('idx_class')%></td><td><%= select('virtual_class', 'idx_class', Zena::Use::ScopeIndex.models_for_form) %></td></tr>
<tr><td class='label'><%= _('idx_scope')%></td><td><%= text_area('virtual_class', 'idx_scope', :rows => 2, :cols => 30) %></td></tr>
<tr><td class='label'><%= _('idx_reverse_scope')%></td><td><%= text_area('virtual_class', 'idx_reverse_scope', :rows => 2, :cols => 30) %></td></tr>
<tr><td class='label'><%= _('prop eval')%></td><td><%= text_area('virtual_class', 'prop_eval', :rows => 2, :cols => 30) %></td></tr>

<tr><td class='label'><%= _('create group')%></td><td><%= select('virtual_class', 'create_group_id', visitor.all_groups.map{|g| [g.name, g.id]} ) %></td></tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AddReverseScopeToRoles < ActiveRecord::Migration
def self.up
add_column :roles, :idx_reverse_scope, :string
end

def self.down
remove_column :roles, :idx_reverse_scope
end
end
11 changes: 5 additions & 6 deletions lib/zena/use/query_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,18 @@ def safe_first(query)

# Find related nodes.
# See Node#build_find for details on the options available.
# TODO: do we need rubyless translate here ?
def find(count, rel, opts = {})
if !opts[:skip_rubyless] && rel.size == 1 && type = RubyLess::SafeClass.safe_method_type_for(self.class, [rel.first])
# TODO: do we need rubyless translate here ? It should be removed.
def find(count, pseudo_sql, opts = {})
if !opts[:skip_rubyless] && type = RubyLess::SafeClass.safe_method_type_for(self.class, [pseudo_sql])
self.send(type[:method])
else
begin
query = self.class.build_query(count, rel,
query = self.class.build_query(count, pseudo_sql,
:node_name => 'self',
:main_class => virtual_class,
:rubyless_helper => (opts[:rubyless_helper] || virtual_class), # should it be || self ?
:default => opts[:default]
)

if limit = opts[:limit]
query.limit = " LIMIT #{limit.to_i}"
query.offset = " OFFSET #{opts[:offset].to_i}"
Expand All @@ -52,7 +51,7 @@ def find(count, rel, opts = {})

type = [:all, :first].include?(count) ? :find : :count

self.class.do_find(count, eval(query.to_s(type)))
Node.do_find(count, eval(query.to_s(type)))
end
end

Expand Down
67 changes: 61 additions & 6 deletions lib/zena/use/scope_index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,19 +122,43 @@ def update_with(node, keys, force_create = false)
end
end

def update_after_destroy(deleted_node, keys)
groups = self.class.groups
attrs = {}
keys.each do |group_key|
next unless list = groups[group_key]
next unless should_clear_group?(group_key, deleted_node)
list.each do |key|
attrs["#{group_key}_#{key}"] = nil
end
end

if !attrs.empty?
self.attributes = attrs
save
end
end

# Return true if the indices from the given group should be altered by the node.
def should_update_group?(group_key, node)
node.id >= self["#{group_key}_id"].to_i
end

# Return true if the indices from the given group should be cleared when
# the given node is deleted.
def should_clear_group?(group_key, deleted_node)
deleted_node.id == self["#{group_key}_id"].to_i
end

def set_site_id
self[:site_id] = current_site.id
end
end # ModelMethods

module ModelMethods
def self.included(base)
base.after_save :update_scope_indices
base.after_save :update_scope_indices
base.after_destroy :update_scope_indices_on_destroy
base.safe_context :scope_index => scope_index_proc
base.alias_method_chain :rebuild_index!, :scope_index
end
Expand All @@ -149,7 +173,7 @@ def self.scope_index_proc
end
end

# FIXME: test !
# Rebuild 'remote' indexes based on changes in this node.
def rebuild_index_with_scope_index!
rebuild_index_without_scope_index!
update_scope_indices
Expand All @@ -171,6 +195,21 @@ def scope_index
end
end

# Trigger 'rebuild_index!' in all elements that could affect this model's
# scope index.
def rebuild_scope_index!
if vclass && query = vclass.idx_reverse_scope
if nodes = find(:all, query)
nodes.each do |node|
node.rebuild_index!
end
end
else
nil
end

end

protected
# Update scope indices (project/section).
def update_scope_indices
Expand All @@ -182,7 +221,14 @@ def update_scope_indices
# FIXME: raise when we have transactional save.
end

def update_scope_indices_on_prop_change
def update_scope_indices_on_destroy
return unless version.status == Zena::Status[:pub]
update_scope_indices_on_prop_change(true)
# How can we handle this ?
# update_scope_indices_on_link_change
end

def update_scope_indices_on_prop_change(deleted=false)
if virtual_class && scopes = virtual_class.idx_scope
scopes = safe_eval(scopes)
return unless scopes.kind_of?(Hash)
Expand All @@ -198,15 +244,24 @@ def update_scope_indices_on_prop_change
mapped_scopes.each do |keys, query|
next unless query.kind_of?(String) && keys.kind_of?(Array) && keys.inject(true) {|s,k| s && k.kind_of?(String) }
if query.strip == 'self'
next if deleted
models_to_update = [self]
else
models_to_update = find(:all, query) || []
models_to_update = find(:all, query, :skip_rubyless => true) || []
raise if !models_to_update.empty? && title == models_to_update.first.title
end

models_to_update.each do |m|
if idx_model = m.scope_index
# force creation of index record
idx_model.update_with(self, keys, true)
if deleted
# Clear obsolete content
idx_model.update_after_destroy(self, keys)
# Rebuild index
m.rebuild_scope_index!
else
# force creation of index record
idx_model.update_with(self, keys, true)
end
end
end
end
Expand Down
3 changes: 3 additions & 0 deletions test/integration/query_node/relations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ link_selects_in_sub_query:
src: "icons or images"
sql: "[%Q{SELECT nodes.*,links.id AS link_id,links.status AS l_status,links.comment AS l_comment,links.date AS l_date FROM links,nodes WHERE #{secure_scope('nodes')} AND ((nodes.id = links.target_id AND links.relation_id = _ID(node_has_an_icon) AND links.source_id = ?) OR (nodes.kpath LIKE 'NDI%' AND nodes.parent_id = ? AND links.id = 0)) GROUP BY nodes.id ORDER BY nodes.zip ASC}, @node.id, @node.id]"

project:
sql: "[%Q{SELECT nodes.* FROM nodes WHERE #{secure_scope('nodes')} AND nodes.id = ? ORDER BY nodes.zip ASC}, @node.get_project_id]"

projects_from_tags:
sql: "/nodes.id = no1.project_id AND no1.kpath LIKE 'NPT%' AND no1.parent_id = ?/"

Expand Down
3 changes: 2 additions & 1 deletion test/sites/zena/roles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ Blog:
kpath: NPPB
real_class: Project
idx_class: IdxProject
idx_scope: "{'blog' => 'self'}"
idx_scope: "{'blog' => 'self'}"
idx_reverse_scope: "nodes in project"
27 changes: 25 additions & 2 deletions test/unit/zena/use/scope_index_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,29 @@ class ScopeIndexTest < Zena::Unit::TestCase
end
end # creating a sub node

context 'deleting a sub node' do
setup do
secure(Node) { Node.create_node(:klass => 'Contact', :name => 'Life threat', :first_name => 'Earthquake', :parent_id => @project.zip, :v_status => Zena::Status[:pub])
}
end

subject do
secure(Node) { Node.create_node(:klass => 'Contact', :name => 'Life threat', :first_name => 'Fukushima', :parent_id => @project.zip, :v_status => Zena::Status[:pub])
}
end

should 'rebuild target indexes' do
assert_equal 'Earthquake', IdxProject.find(@project.scope_index).contact_first_name
subject
assert_equal 'Fukushima', IdxProject.find(@project.scope_index).contact_first_name
assert_difference('IdxProject.count', 0) do
subject.destroy
idx = IdxProject.find(@project.scope_index)
assert_equal 'Earthquake', idx.contact_first_name
end
end
end # deleting a sub node

context 'moving a sub node' do
subject do
secure(Node) { VirtualClass['Contact'].create_instance(:first_name => 'Friedrich', :name => 'Hölderlin', :parent_id => nodes_id(:zena), :v_status => Zena::Status[:pub]) }
Expand Down Expand Up @@ -412,7 +435,7 @@ class ScopeIndexTest < Zena::Unit::TestCase
vclass = VirtualClass.create(subject)
assert_equal "Invalid entry: keys should be a String and query should be a String or an Array of strings (1 => \"project\")", vclass.errors[:idx_scope]
end

context 'with array keys' do
subject do
{:name => 'Song', :superclass => 'Post', :idx_scope => "{%w{self project} => 'project'}", :create_group_id => groups_id(:public) }
Expand All @@ -423,7 +446,7 @@ class ScopeIndexTest < Zena::Unit::TestCase
assert_equal "Invalid rubyless: Invalid key type for hash (should be a literal value, was :array)", vclass.errors[:idx_scope]
end
end # with array keys

end # with an invalid idx_scope

context 'with an invalid idx_scope type' do
Expand Down
4 changes: 3 additions & 1 deletion zena.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Gem::Specification.new do |s|

s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
s.authors = ["Gaspard Bucher"]
s.date = %q{2011-03-15}
s.date = %q{2011-03-17}
s.default_executable = %q{zena}
s.description = %q{zena is a Ruby on Rails CMS (content managment system) with a focus on usability, ease of customization and web 2.0 goodness (application like behaviour).}
s.email = %q{gaspard@teti.ch}
Expand Down Expand Up @@ -1832,6 +1832,7 @@ Gem::Specification.new do |s|
"test/integration/zafu_compiler/complex.yml",
"test/integration/zafu_compiler/complex_ok.yml",
"test/integration/zafu_compiler/conditional.yml",
"test/integration/zafu_compiler/context.yml",
"test/integration/zafu_compiler/data.yml",
"test/integration/zafu_compiler/dates.yml",
"test/integration/zafu_compiler/display.yml",
Expand All @@ -1841,6 +1842,7 @@ Gem::Specification.new do |s|
"test/integration/zafu_compiler/i18n.yml",
"test/integration/zafu_compiler/idx_scope.yml",
"test/integration/zafu_compiler/later.yml",
"test/integration/zafu_compiler/meta.yml",
"test/integration/zafu_compiler/off/off.yml",
"test/integration/zafu_compiler/query.yml",
"test/integration/zafu_compiler/recursion.yml",
Expand Down

0 comments on commit 9efa182

Please sign in to comment.