Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More compatibility improvements #1

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 54 additions & 55 deletions lib/sequel_nested_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def before_destroy
end
end

model.set_restricted_columns(*([:left, :right, :level, :parent_id, options[:parent_column], options[:left_column], options[:right_column], options[:level]].uniq))
model.set_restricted_columns(*([:left, :right, :parent_id, options[:parent_column], options[:left_column], options[:right_column]].uniq))
end

module DatasetMethods
Expand All @@ -90,73 +90,69 @@ def nested(left)

# Returns dataset for all root nodes
def roots
nested.filter(self.model_classes[nil].qualified_parent_column => nil)
nested(self.model.qualified_left_column).filter(self.model.qualified_parent_column => nil)
end

# Returns dataset for all of nodes which do not have children
def leaves
nested.filter(self.model_classes[nil].qualified_right_column - self.model_classes[nil].qualified_left_column => 1)
lft = self.model.qualified_left_column_literal
rgt = self.model.qualified_right_column_literal
nested(self.model.qualified_left_column).where("#{rgt} == #{lft} + 1")
end
end

module ClassMethods
def method_missing(method, *args, &block)
if self.dataset.respond_to?(method)
self.dataset.send(method, *args, &block)
elsif method =~ /^qualified_(.*)_literal$/
self.send(:qualified_column_literal, $1.to_sym)
elsif method =~ /^qualified_(.*)$/
self.send(:qualified_column, $1.to_sym, *args)
else
super
end
end

# Returns the first root
def root
roots.first
end

def qualified_parent_column(table_name = self.implicit_table_name)
"#{self.nested_set_options[:parent_column]}".to_sym
end

def qualified_parent_column_literal
self.dataset.literal(self.nested_set_options[:parent_column])
end

def qualified_left_column(table_name = self.implicit_table_name)
"#{self.nested_set_options[:left_column]}".to_sym
end

def qualified_left_column_literal
self.dataset.literal(self.nested_set_options[:left_column])
end

def qualified_right_column(table_name = self.implicit_table_name)
"#{self.nested_set_options[:right_column]}".to_sym
end

def qualified_right_column_literal
self.dataset.literal(self.nested_set_options[:right_column])
end

def qualified_level_column(table_name = self.implicit_table_name)
"#{self.nested_set_options[:level_column]}".to_sym
def qualified_column(column_key, table_name = self.table_name)
"#{table_name}__#{self.nested_set_options[column_key]}".to_sym
end

def qualified_level_column_literal
self.dataset.literal(self.nested_set_options[:level_column])
def qualified_column_literal(column_key)
self.dataset.literal(self.nested_set_options[column_key])
end

def valid?
self.left_and_rights_valid? && self.no_duplicates_for_columns? && self.all_roots_valid?
end

def left_and_rights_valid?
self.left_outer_join(Client.implicit_table_name.as(:parent), self.qualified_parent_column => "parent__#{self.primary_key}".to_sym).
filter({ self.qualified_left_column => nil } |
{ self.qualified_right_column => nil } |
(self.qualified_left_column >= self.qualified_right_column) |
(~{ self.qualified_parent_column => nil } & ((self.qualified_left_column <= self.qualified_left_column(:parent)) |
(self.qualified_right_column >= self.qualified_right_column(:parent))))).count == 0
self_lft = Sequel.expr(self.qualified_left_column)
self_rgt = Sequel.expr(self.qualified_right_column)
self_prt = Sequel.expr(self.qualified_parent_column)
prt_lft = Sequel.expr(self.qualified_left_column(:parent))
prt_rgt = Sequel.expr(self.qualified_right_column(:parent))
self.left_outer_join(Sequel.as(self.implicit_table_name, :parent), self.qualified_parent_column => "parent__#{self.primary_key}".to_sym)
.where({ self.qualified_left_column => nil })
.or({ self.qualified_right_column => nil })
.or(self_lft >= self_rgt)
.or("(`#{self_prt.table}`.`#{self_prt.column}` IS NOT NULL
AND `#{self_lft.table}`.`#{self_lft.column}` <= `#{prt_lft.table}`.`#{prt_lft.column}`)
OR `#{self_rgt.table}`.`#{self_rgt.column}` >= `#{prt_rgt.table}`.`#{prt_rgt.column}`")
.all.length == 0
end

def left_and_rights_valid_dataset?
self.left_outer_join(Client.implicit_table_name.as(:parent), self.qualified_parent_column => "parent__#{self.primary_key}".to_sym).
filter({ self.qualified_left_column => nil } |
{ self.qualified_right_column => nil } |
(self.qualified_left_column >= self.qualified_right_column) |
(~{ self.qualified_parent_column => nil } & ((self.qualified_left_column <= self.qualified_left_column(:parent)) |
self.left_outer_join(Sequel.as(self.implicit_table_name.as, :parent), self.qualified_parent_column => "parent__#{self.primary_key}".to_sym).
filter({ self.qualified_left_column => nil })
.or({ self.qualified_right_column => nil })
.or(self.qualified_left_column >= self.qualified_right_column)
.or((( self.qualified_parent_column != nil ) & ((self.qualified_left_column <= self.qualified_left_column(:parent))
(self.qualified_right_column >= self.qualified_right_column(:parent)))))
end

Expand All @@ -166,7 +162,7 @@ def no_duplicates_for_columns?
# connection.quote_column_name(c)
# end.push(nil).join(", ")
[self.qualified_left_column, self.qualified_right_column].all? do |column|
self.dataset.select(column, :count[column]).group(column).having(:count[column] > 1).first.nil?
self.dataset.select(column).select_more{count(column)}.group(column).having{count(column) > 1}.first.nil?
end
end

Expand Down Expand Up @@ -195,7 +191,7 @@ def each_root_valid?(roots_to_validate)
# Rebuilds the left & rights if unset or invalid. Also very useful for converting from acts_as_tree.
def rebuild!

scope = lambda{}
scope = lambda{|node|}
# TODO: add scope stuff

# Don't rebuild a valid tree.
Expand Down Expand Up @@ -325,27 +321,27 @@ def root

# Returns the immediate parent
def parent
model.nested(self.class.qualified_left_column).filter(self.primary_key => self.parent_id).first if self.parent_id
model.dataset.nested(self.class.qualified_left_column).filter(self.primary_key => self.parent_id).first if self.parent_id
end

# Returns the dataset for all parent nodes and self
def self_and_ancestors
model.filter("#{self.class.qualified_left_column} <= :left AND #{self.class.qualified_right_column} >= :right", :left => left, :right => right)
model.filter("#{self.class.qualified_left_column_literal} <= :left AND #{self.class.qualified_right_column_literal} >= :right", :left => left, :right => right)
end

# Returns the dataset for all children of the parent, including self
def self_and_siblings
model.nested(self.class.qualified_left_column).filter(self.class.qualified_parent_column => self.parent_id)
model.dataset.nested(self.class.qualified_left_column).filter(self.class.qualified_parent_column => self.parent_id)
end

# Returns dataset for itself and all of its nested children
def self_and_descendants
model.nested(self.class.qualified_left_column).filter("#{self.class.qualified_left_column} >= :left AND #{self.class.qualified_right_column} <= :right", :left => left, :right => right)
model.dataset.nested(self.class.qualified_left_column).filter("#{self.class.qualified_left_column_literal} >= :left AND #{self.class.qualified_right_column_literal} <= :right", :left => left, :right => right)
end

# Filter for dataset that will exclude self object
def without_self(dataset)
dataset.filter(~{self.primary_key => self.id})
dataset.exclude({self.primary_key => self.id})
end

# Returns dataset for its immediate children
Expand All @@ -370,7 +366,7 @@ def descendants

# Returns dataset for all of its nested children which do not have children
def leaves
descendants.filter(self.class.qualified_right_column - self.class.qualified_left_column => 1)
descendants.where("#{self.class.qualified_right_column_literal} - #{self.class.qualified_left_column_literal} = 1")
end

def is_descendant_of?(other)
Expand Down Expand Up @@ -398,12 +394,12 @@ def same_scope?(other)

# Find the first sibling to the left
def left_sibling
siblings.filter(self.class.qualified_left_column < left).order(self.class.qualified_left_column.desc).first
siblings.where("#{self.class.qualified_left_column_literal} < #{left}").order(Sequel.desc(self.class.qualified_left_column)).first
end

# Find the first sibling to the right
def right_sibling
siblings.filter(self.class.qualified_left_column > left).first
siblings.where("#{self.class.qualified_left_column_literal} > #{left}").first
end


Expand Down Expand Up @@ -577,11 +573,14 @@ def move_to(target, position)

target.refresh if target
self.refresh
update(self.class.qualified_level_column => (root? ? 0 : ancestors.count))
update(self.nested_set_options[:level_column] => (root? ? 0 : ancestors.count))
children.each do |child|
child.update(self.nested_set_options[:level_column] => child.ancestors.count)
end

if !root?
siblings.each do |children|
children.update(self.class.qualified_level_column => (children.root? ? 0 : children.ancestors.count))
siblings.each do |sibling|
sibling.update(self.nested_set_options[:level_column] => (sibling.root? ? 0 : sibling.ancestors.count))
end
end
#TODO: add after_move
Expand Down
4 changes: 2 additions & 2 deletions sequel_nested_set.gemspec
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
Gem::Specification.new do |s|
s.name = "sequel_nested_set"
s.version = "0.9.9"
s.date = "2009-01-11"
s.date = "2013-09-24"
s.summary = "Nested set implementation for Sequel Models"
s.email = "[email protected]"
s.homepage = "http://sequelns.rubyforge.org/"
s.description = "Nested set implementation, ported from the Awesome Nested Set Active Record plugin."
s.has_rdoc = true
s.authors = "Paweł Kondzior"
s.authors = ["Pawel Kondzior", "Olga Reznikova"]
s.files = ["lib/sequel_nested_set.rb"]
s.test_files = ["spec/nested_set_spec.rb", "spec/rcov.opts", "spec/spec.opts", "spec/spec_helper.rb"]
s.rdoc_options = ["--quiet", "--title", "Sequel Nested Set", "--opname", "index.html", "--line-numbers", "--main", "README", "--inline-source", "--charset", "utf8"]
Expand Down
10 changes: 5 additions & 5 deletions spec/nested_set_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

it "should have all leaves" do
leaves = Client.leaves.all
leaves.should == Client.nested.filter(:rgt - :lft => 1).all
leaves.should == Client.nested(:lft).where("rgt - lft = 1").all
leaves.should == [@node1, @node2_1, @node3, @root2]
end

Expand Down Expand Up @@ -95,9 +95,9 @@
end

it "should parent, left and right setters be protected methods" do
Client.new.protected_methods.include?("left=").should be_true
Client.new.protected_methods.include?("right=").should be_true
Client.new.protected_methods.include?("parent_id=").should be_true
Client.new.protected_methods.include?("left=".to_sym).should be_true
Client.new.protected_methods.include?("right=".to_sym).should be_true
Client.new.protected_methods.include?("parent_id=".to_sym).should be_true
end

it "shoud have faild on new when passing keys configured as right_column, left_column, parent_column" do
Expand Down Expand Up @@ -318,7 +318,7 @@
@node2.move_to_root
@node2.parent.should be_nil
@node2.level.should == 0
@node2_1.level.should == 1
@node2_1.refresh.level.should == 1
@node2.left == 1
@node2.right == 4
Client.valid?.should be_true
Expand Down
23 changes: 13 additions & 10 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
require 'rubygems'
require 'spec'
require 'rspec'
require 'sequel'
require 'logger'
require 'ruby-debug'

require File.dirname(__FILE__) + '/../lib/sequel_nested_set'

DB = Sequel.sqlite # memory database
DB.logger = Logger.new('log/db.log')
#DB.logger = Logger.new('log/db.log')

class Client < Sequel::Model
is :nested_set
Sequel.extension :sequel_3_dataset_methods
plugin :blacklist_security
plugin :schema
plugin :nested_set
set_schema do
primary_key :id
column :name, :text
column :level, :integer
column :parent_id, :integer
column :lft, :integer
column :rgt, :integer
Expand All @@ -25,12 +28,12 @@ class Client < Sequel::Model
def prepare_nested_set_data
Client.drop_table if Client.table_exists?
Client.create_table!
DB[:clients] << {"name"=>"Top Level 2", "lft"=>11, "id"=>6, "rgt"=>12}
DB[:clients] << {"name"=>"Child 2.1", "lft"=>5, "id"=>4, "parent_id"=>3, "rgt"=>6}
DB[:clients] << {"name"=>"Child 1", "lft"=>2, "id"=>2, "parent_id"=>1, "rgt"=>3}
DB[:clients] << {"name"=>"Top Level", "lft"=>1, "id"=>1, "rgt"=>10}
DB[:clients] << {"name"=>"Child 2", "lft"=>4, "id"=>3, "parent_id"=>1, "rgt"=>7}
DB[:clients] << {"name"=>"Child 3", "lft"=>8, "id"=>5, "parent_id"=>1, "rgt"=>9}
DB[:clients] << {"name"=>"Top Level 2", "level"=>0, "lft"=>11, "id"=>6, "rgt"=>12}
DB[:clients] << {"name"=>"Child 2.1", "level"=>2, "lft"=>5, "id"=>4, "parent_id"=>3, "rgt"=>6}
DB[:clients] << {"name"=>"Child 1", "level"=>1, "lft"=>2, "id"=>2, "parent_id"=>1, "rgt"=>3}
DB[:clients] << {"name"=>"Top Level", "level"=>0, "lft"=>1, "id"=>1, "rgt"=>10}
DB[:clients] << {"name"=>"Child 2", "level"=>1, "lft"=>4, "id"=>3, "parent_id"=>1, "rgt"=>7}
DB[:clients] << {"name"=>"Child 3", "level"=>1, "lft"=>8, "id"=>5, "parent_id"=>1, "rgt"=>9}
end

prepare_nested_set_data
Expand Down