searchlogic 1.5.10 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +7 -1
- data/Manifest +5 -2
- data/README.rdoc +2 -0
- data/lib/searchlogic.rb +5 -3
- data/lib/searchlogic/condition/base.rb +1 -2
- data/lib/searchlogic/condition/child_of.rb +1 -1
- data/lib/searchlogic/condition/descendant_of.rb +4 -17
- data/lib/searchlogic/condition/inclusive_descendant_of.rb +3 -4
- data/lib/searchlogic/condition/{tree.rb → nested_set.rb} +1 -1
- data/lib/searchlogic/condition/not_begin_with.rb +1 -1
- data/lib/searchlogic/condition/sibling_of.rb +1 -1
- data/lib/searchlogic/conditions/magic_methods.rb +1 -1
- data/lib/searchlogic/search/base.rb +5 -0
- data/lib/searchlogic/search/searching.rb +0 -1
- data/lib/searchlogic/version.rb +2 -2
- data/searchlogic.gemspec +4 -4
- data/test/active_record_tests/associations_test.rb +4 -5
- data/test/condition_tests/descendant_of_test.rb +1 -5
- data/test/condition_tests/inclusive_descendant_of_test.rb +1 -5
- data/test/conditions_tests/magic_methods_test.rb +5 -4
- data/test/fixtures/orders.yml +2 -2
- data/test/fixtures/user_groups.yml +1 -3
- data/test/fixtures/users.yml +18 -3
- data/test/libs/awesome_nested_set.rb +545 -0
- data/test/libs/awesome_nested_set/compatability.rb +29 -0
- data/test/libs/awesome_nested_set/helper.rb +40 -0
- data/test/libs/awesome_nested_set/named_scope.rb +140 -0
- data/test/search_tests/base_test.rb +1 -1
- data/test/test_helper.rb +4 -2
- metadata +8 -5
- data/test/libs/acts_as_tree.rb +0 -98
@@ -0,0 +1,29 @@
|
|
1
|
+
# Rails <2.x doesn't define #except
|
2
|
+
class Hash #:nodoc:
|
3
|
+
# Returns a new hash without the given keys.
|
4
|
+
def except(*keys)
|
5
|
+
clone.except!(*keys)
|
6
|
+
end unless method_defined?(:except)
|
7
|
+
|
8
|
+
# Replaces the hash without the given keys.
|
9
|
+
def except!(*keys)
|
10
|
+
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
|
11
|
+
keys.each { |key| delete(key) }
|
12
|
+
self
|
13
|
+
end unless method_defined?(:except!)
|
14
|
+
end
|
15
|
+
|
16
|
+
# NamedScope is new to Rails 2.1
|
17
|
+
unless defined? ActiveRecord::NamedScope
|
18
|
+
require 'awesome_nested_set/named_scope'
|
19
|
+
ActiveRecord::Base.class_eval do
|
20
|
+
include CollectiveIdea::NamedScope
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Rails 1.2.x doesn't define #quoted_table_name
|
25
|
+
class ActiveRecord::Base #:nodoc:
|
26
|
+
def self.quoted_table_name
|
27
|
+
self.connection.quote_column_name(self.table_name)
|
28
|
+
end unless methods.include?('quoted_table_name')
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CollectiveIdea #:nodoc:
|
2
|
+
module Acts #:nodoc:
|
3
|
+
module NestedSet #:nodoc:
|
4
|
+
# This module provides some helpers for the model classes using acts_as_nested_set.
|
5
|
+
# It is included by default in all views.
|
6
|
+
#
|
7
|
+
module Helper
|
8
|
+
# Returns options for select.
|
9
|
+
# You can exclude some items from the tree.
|
10
|
+
# You can pass a block receiving an item and returning the string displayed in the select.
|
11
|
+
#
|
12
|
+
# == Params
|
13
|
+
# * +class_or_item+ - Class name or top level times
|
14
|
+
# * +mover+ - The item that is being move, used to exlude impossible moves
|
15
|
+
# * +&block+ - a block that will be used to display: { |item| ... item.name }
|
16
|
+
#
|
17
|
+
# == Usage
|
18
|
+
#
|
19
|
+
# <%= f.select :parent_id, nested_set_options(Category, @category) {|i|
|
20
|
+
# "#{'–' * i.level} #{i.name}"
|
21
|
+
# }) %>
|
22
|
+
#
|
23
|
+
def nested_set_options(class_or_item, mover = nil)
|
24
|
+
class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
|
25
|
+
items = Array(class_or_item)
|
26
|
+
result = []
|
27
|
+
items.each do |root|
|
28
|
+
result += root.self_and_descendants.map do |i|
|
29
|
+
if mover.nil? || mover.new_record? || mover.move_possible?(i)
|
30
|
+
[yield(i), i.id]
|
31
|
+
end
|
32
|
+
end.compact
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# Taken from Rails 2.1
|
2
|
+
module CollectiveIdea #:nodoc:
|
3
|
+
module NamedScope #:nodoc:
|
4
|
+
# All subclasses of ActiveRecord::Base have two named_scopes:
|
5
|
+
# * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
|
6
|
+
# * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
|
7
|
+
#
|
8
|
+
# Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
|
9
|
+
#
|
10
|
+
# These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
|
11
|
+
# intermediate values (scopes) around as first-class objects is convenient.
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
extend ClassMethods
|
15
|
+
named_scope :scoped, lambda { |scope| scope }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def scopes
|
21
|
+
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
|
25
|
+
# such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
|
26
|
+
#
|
27
|
+
# class Shirt < ActiveRecord::Base
|
28
|
+
# named_scope :red, :conditions => {:color => 'red'}
|
29
|
+
# named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
|
33
|
+
# in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
|
34
|
+
#
|
35
|
+
# Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
|
36
|
+
# constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
|
37
|
+
# <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
|
38
|
+
# as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
|
39
|
+
# <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
|
40
|
+
#
|
41
|
+
# These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
|
42
|
+
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
|
43
|
+
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
|
44
|
+
#
|
45
|
+
# All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
|
46
|
+
# <tt>has_many</tt> associations. If,
|
47
|
+
#
|
48
|
+
# class Person < ActiveRecord::Base
|
49
|
+
# has_many :shirts
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
|
53
|
+
# only shirts.
|
54
|
+
#
|
55
|
+
# Named scopes can also be procedural.
|
56
|
+
#
|
57
|
+
# class Shirt < ActiveRecord::Base
|
58
|
+
# named_scope :colored, lambda { |color|
|
59
|
+
# { :conditions => { :color => color } }
|
60
|
+
# }
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
|
64
|
+
#
|
65
|
+
# Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
|
66
|
+
#
|
67
|
+
# class Shirt < ActiveRecord::Base
|
68
|
+
# named_scope :red, :conditions => {:color => 'red'} do
|
69
|
+
# def dom_id
|
70
|
+
# 'red_shirts'
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
#
|
76
|
+
# For testing complex named scopes, you can examine the scoping options using the
|
77
|
+
# <tt>proxy_options</tt> method on the proxy itself.
|
78
|
+
#
|
79
|
+
# class Shirt < ActiveRecord::Base
|
80
|
+
# named_scope :colored, lambda { |color|
|
81
|
+
# { :conditions => { :color => color } }
|
82
|
+
# }
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# expected_options = { :conditions => { :colored => 'red' } }
|
86
|
+
# assert_equal expected_options, Shirt.colored('red').proxy_options
|
87
|
+
def named_scope(name, options = {}, &block)
|
88
|
+
scopes[name] = lambda do |parent_scope, *args|
|
89
|
+
Scope.new(parent_scope, case options
|
90
|
+
when Hash
|
91
|
+
options
|
92
|
+
when Proc
|
93
|
+
options.call(*args)
|
94
|
+
end, &block)
|
95
|
+
end
|
96
|
+
(class << self; self end).instance_eval do
|
97
|
+
define_method name do |*args|
|
98
|
+
scopes[name].call(self, *args)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Scope
|
105
|
+
attr_reader :proxy_scope, :proxy_options
|
106
|
+
[].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
|
107
|
+
delegate :scopes, :with_scope, :to => :proxy_scope
|
108
|
+
|
109
|
+
def initialize(proxy_scope, options, &block)
|
110
|
+
[options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
|
111
|
+
extend Module.new(&block) if block_given?
|
112
|
+
@proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
|
113
|
+
end
|
114
|
+
|
115
|
+
def reload
|
116
|
+
load_found; self
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
def proxy_found
|
121
|
+
@found || load_found
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def method_missing(method, *args, &block)
|
126
|
+
if scopes.include?(method)
|
127
|
+
scopes[method].call(self, *args)
|
128
|
+
else
|
129
|
+
with_scope :find => proxy_options do
|
130
|
+
proxy_scope.send(method, *args, &block)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def load_found
|
136
|
+
@found = find(:all)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -142,7 +142,7 @@ module SearchTests
|
|
142
142
|
search.conditions.users.id_greater_than = 2
|
143
143
|
search.page = 3
|
144
144
|
search.readonly = true
|
145
|
-
assert_equal({:joins => :users, :offset => 4, :readonly => true, :conditions => ["\"accounts\".\"name\" LIKE ? AND \"users\".\"id\" > ?", "%Binary%", 2], :limit => 2 }, search.sanitize)
|
145
|
+
assert_equal({:select => "DISTINCT \"accounts\".*", :joins => :users, :offset => 4, :readonly => true, :conditions => ["\"accounts\".\"name\" LIKE ? AND \"users\".\"id\" > ?", "%Binary%", 2], :limit => 2 }, search.sanitize)
|
146
146
|
end
|
147
147
|
|
148
148
|
def test_scope
|
data/test/test_helper.rb
CHANGED
@@ -3,7 +3,7 @@ require "rubygems"
|
|
3
3
|
require "ruby-debug"
|
4
4
|
require "active_record"
|
5
5
|
require "active_record/fixtures"
|
6
|
-
require File.dirname(__FILE__) + '/libs/
|
6
|
+
require File.dirname(__FILE__) + '/libs/awesome_nested_set'
|
7
7
|
require File.dirname(__FILE__) + '/libs/rexml_fix'
|
8
8
|
require File.dirname(__FILE__) + '/../lib/searchlogic' unless defined?(Searchlogic)
|
9
9
|
|
@@ -34,6 +34,8 @@ ActiveRecord::Schema.define(:version => 1) do
|
|
34
34
|
t.datetime :updated_at
|
35
35
|
t.integer :account_id
|
36
36
|
t.integer :parent_id
|
37
|
+
t.integer :lft
|
38
|
+
t.integer :rgt
|
37
39
|
t.string :first_name
|
38
40
|
t.string :last_name
|
39
41
|
t.boolean :active
|
@@ -78,7 +80,7 @@ class UserGroup < ActiveRecord::Base
|
|
78
80
|
end
|
79
81
|
|
80
82
|
class User < ActiveRecord::Base
|
81
|
-
|
83
|
+
acts_as_nested_set
|
82
84
|
belongs_to :account
|
83
85
|
has_many :orders, :dependent => :destroy
|
84
86
|
has_many :cats, :dependent => :destroy
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: searchlogic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Johnson of Binary Logic
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-12-
|
12
|
+
date: 2008-12-08 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -69,6 +69,7 @@ extra_rdoc_files:
|
|
69
69
|
- lib/searchlogic/condition/less_than.rb
|
70
70
|
- lib/searchlogic/condition/less_than_or_equal_to.rb
|
71
71
|
- lib/searchlogic/condition/like.rb
|
72
|
+
- lib/searchlogic/condition/nested_set.rb
|
72
73
|
- lib/searchlogic/condition/nil.rb
|
73
74
|
- lib/searchlogic/condition/not_begin_with.rb
|
74
75
|
- lib/searchlogic/condition/not_blank.rb
|
@@ -78,7 +79,6 @@ extra_rdoc_files:
|
|
78
79
|
- lib/searchlogic/condition/not_like.rb
|
79
80
|
- lib/searchlogic/condition/not_nil.rb
|
80
81
|
- lib/searchlogic/condition/sibling_of.rb
|
81
|
-
- lib/searchlogic/condition/tree.rb
|
82
82
|
- lib/searchlogic/conditions/any_or_all.rb
|
83
83
|
- lib/searchlogic/conditions/base.rb
|
84
84
|
- lib/searchlogic/conditions/groups.rb
|
@@ -175,6 +175,7 @@ files:
|
|
175
175
|
- lib/searchlogic/condition/less_than.rb
|
176
176
|
- lib/searchlogic/condition/less_than_or_equal_to.rb
|
177
177
|
- lib/searchlogic/condition/like.rb
|
178
|
+
- lib/searchlogic/condition/nested_set.rb
|
178
179
|
- lib/searchlogic/condition/nil.rb
|
179
180
|
- lib/searchlogic/condition/not_begin_with.rb
|
180
181
|
- lib/searchlogic/condition/not_blank.rb
|
@@ -184,7 +185,6 @@ files:
|
|
184
185
|
- lib/searchlogic/condition/not_like.rb
|
185
186
|
- lib/searchlogic/condition/not_nil.rb
|
186
187
|
- lib/searchlogic/condition/sibling_of.rb
|
187
|
-
- lib/searchlogic/condition/tree.rb
|
188
188
|
- lib/searchlogic/conditions/any_or_all.rb
|
189
189
|
- lib/searchlogic/conditions/base.rb
|
190
190
|
- lib/searchlogic/conditions/groups.rb
|
@@ -298,7 +298,10 @@ files:
|
|
298
298
|
- test/fixtures/orders.yml
|
299
299
|
- test/fixtures/user_groups.yml
|
300
300
|
- test/fixtures/users.yml
|
301
|
-
- test/libs/
|
301
|
+
- test/libs/awesome_nested_set/compatability.rb
|
302
|
+
- test/libs/awesome_nested_set/helper.rb
|
303
|
+
- test/libs/awesome_nested_set/named_scope.rb
|
304
|
+
- test/libs/awesome_nested_set.rb
|
302
305
|
- test/libs/rexml_fix.rb
|
303
306
|
- test/modifier_tests/day_of_month_test.rb
|
304
307
|
- test/search_tests/base_test.rb
|
data/test/libs/acts_as_tree.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Acts
|
3
|
-
module Tree
|
4
|
-
def self.included(base)
|
5
|
-
base.extend(ClassMethods)
|
6
|
-
end
|
7
|
-
|
8
|
-
# Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children
|
9
|
-
# association. This requires that you have a foreign key column, which by default is called +parent_id+.
|
10
|
-
#
|
11
|
-
# class Category < ActiveRecord::Base
|
12
|
-
# acts_as_tree :order => "name"
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# Example:
|
16
|
-
# root
|
17
|
-
# \_ child1
|
18
|
-
# \_ subchild1
|
19
|
-
# \_ subchild2
|
20
|
-
#
|
21
|
-
# root = Category.create("name" => "root")
|
22
|
-
# child1 = root.children.create("name" => "child1")
|
23
|
-
# subchild1 = child1.children.create("name" => "subchild1")
|
24
|
-
#
|
25
|
-
# root.parent # => nil
|
26
|
-
# child1.parent # => root
|
27
|
-
# root.children # => [child1]
|
28
|
-
# root.children.first.children.first # => subchild1
|
29
|
-
#
|
30
|
-
# In addition to the parent and children associations, the following instance methods are added to the class
|
31
|
-
# after calling <tt>acts_as_tree</tt>:
|
32
|
-
# * <tt>siblings</tt> - Returns all the children of the parent, excluding the current node (<tt>[subchild2]</tt> when called on <tt>subchild1</tt>)
|
33
|
-
# * <tt>self_and_siblings</tt> - Returns all the children of the parent, including the current node (<tt>[subchild1, subchild2]</tt> when called on <tt>subchild1</tt>)
|
34
|
-
# * <tt>ancestors</tt> - Returns all the ancestors of the current node (<tt>[child1, root]</tt> when called on <tt>subchild2</tt>)
|
35
|
-
# * <tt>root</tt> - Returns the root of the current node (<tt>root</tt> when called on <tt>subchild2</tt>)
|
36
|
-
module ClassMethods
|
37
|
-
# Configuration options are:
|
38
|
-
#
|
39
|
-
# * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
|
40
|
-
# * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
|
41
|
-
# * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
|
42
|
-
def acts_as_tree(options = {})
|
43
|
-
configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil }
|
44
|
-
configuration.update(options) if options.is_a?(Hash)
|
45
|
-
|
46
|
-
belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
|
47
|
-
has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy
|
48
|
-
|
49
|
-
class_eval <<-EOV
|
50
|
-
include ActiveRecord::Acts::Tree::InstanceMethods
|
51
|
-
|
52
|
-
def self.roots
|
53
|
-
find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.root
|
57
|
-
find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
|
58
|
-
end
|
59
|
-
EOV
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
module InstanceMethods
|
64
|
-
# Returns list of ancestors, starting from parent until root.
|
65
|
-
#
|
66
|
-
# subchild1.ancestors # => [child1, root]
|
67
|
-
def ancestors
|
68
|
-
node, nodes = self, []
|
69
|
-
nodes << node = node.parent while node.parent
|
70
|
-
nodes
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns the root node of the tree.
|
74
|
-
def root
|
75
|
-
node = self
|
76
|
-
node = node.parent while node.parent
|
77
|
-
node
|
78
|
-
end
|
79
|
-
|
80
|
-
# Returns all siblings of the current node.
|
81
|
-
#
|
82
|
-
# subchild1.siblings # => [subchild2]
|
83
|
-
def siblings
|
84
|
-
self_and_siblings - [self]
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns all siblings and a reference to the current node.
|
88
|
-
#
|
89
|
-
# subchild1.self_and_siblings # => [subchild1, subchild2]
|
90
|
-
def self_and_siblings
|
91
|
-
parent ? parent.children : self.class.roots
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
ActiveRecord::Base.send :include, ActiveRecord::Acts::Tree
|