searchlogic 1.6.6 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG.rdoc +17 -0
  3. data/{MIT-LICENSE → LICENSE} +2 -2
  4. data/README.rdoc +128 -379
  5. data/Rakefile +56 -20
  6. data/VERSION.yml +4 -0
  7. data/init.rb +1 -1
  8. data/lib/searchlogic.rb +18 -98
  9. data/lib/searchlogic/core_ext/object.rb +33 -13
  10. data/lib/searchlogic/core_ext/proc.rb +11 -0
  11. data/lib/searchlogic/named_scopes/alias_scope.rb +63 -0
  12. data/lib/searchlogic/named_scopes/associations.rb +126 -0
  13. data/lib/searchlogic/named_scopes/conditions.rb +215 -0
  14. data/lib/searchlogic/named_scopes/ordering.rb +53 -0
  15. data/lib/searchlogic/rails_helpers.rb +69 -0
  16. data/lib/searchlogic/search.rb +146 -0
  17. data/rails/init.rb +1 -0
  18. data/searchlogic.gemspec +69 -0
  19. data/spec/core_ext/object_spec.rb +7 -0
  20. data/spec/core_ext/proc_spec.rb +9 -0
  21. data/spec/named_scopes/alias_scope_spec.rb +15 -0
  22. data/spec/named_scopes/associations_spec.rb +120 -0
  23. data/spec/named_scopes/conditions_spec.rb +253 -0
  24. data/spec/named_scopes/ordering_spec.rb +23 -0
  25. data/spec/search_spec.rb +283 -0
  26. data/spec/spec_helper.rb +78 -0
  27. metadata +40 -231
  28. data/Manifest.txt +0 -158
  29. data/TODO.rdoc +0 -4
  30. data/lib/searchlogic/active_record/associations.rb +0 -52
  31. data/lib/searchlogic/active_record/base.rb +0 -224
  32. data/lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb +0 -176
  33. data/lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb +0 -172
  34. data/lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb +0 -80
  35. data/lib/searchlogic/condition/base.rb +0 -165
  36. data/lib/searchlogic/condition/begins_with.rb +0 -17
  37. data/lib/searchlogic/condition/blank.rb +0 -24
  38. data/lib/searchlogic/condition/child_of.rb +0 -11
  39. data/lib/searchlogic/condition/descendant_of.rb +0 -11
  40. data/lib/searchlogic/condition/ends_with.rb +0 -17
  41. data/lib/searchlogic/condition/equals.rb +0 -33
  42. data/lib/searchlogic/condition/greater_than.rb +0 -15
  43. data/lib/searchlogic/condition/greater_than_or_equal_to.rb +0 -15
  44. data/lib/searchlogic/condition/inclusive_descendant_of.rb +0 -10
  45. data/lib/searchlogic/condition/keywords.rb +0 -52
  46. data/lib/searchlogic/condition/less_than.rb +0 -15
  47. data/lib/searchlogic/condition/less_than_or_equal_to.rb +0 -15
  48. data/lib/searchlogic/condition/like.rb +0 -15
  49. data/lib/searchlogic/condition/nested_set.rb +0 -17
  50. data/lib/searchlogic/condition/nil.rb +0 -21
  51. data/lib/searchlogic/condition/not_begin_with.rb +0 -20
  52. data/lib/searchlogic/condition/not_blank.rb +0 -19
  53. data/lib/searchlogic/condition/not_end_with.rb +0 -20
  54. data/lib/searchlogic/condition/not_equal.rb +0 -27
  55. data/lib/searchlogic/condition/not_have_keywords.rb +0 -20
  56. data/lib/searchlogic/condition/not_like.rb +0 -20
  57. data/lib/searchlogic/condition/not_nil.rb +0 -19
  58. data/lib/searchlogic/condition/sibling_of.rb +0 -14
  59. data/lib/searchlogic/conditions/any_or_all.rb +0 -42
  60. data/lib/searchlogic/conditions/base.rb +0 -244
  61. data/lib/searchlogic/conditions/groups.rb +0 -74
  62. data/lib/searchlogic/conditions/magic_methods.rb +0 -286
  63. data/lib/searchlogic/conditions/multiparameter_attributes.rb +0 -105
  64. data/lib/searchlogic/conditions/protection.rb +0 -36
  65. data/lib/searchlogic/config.rb +0 -31
  66. data/lib/searchlogic/config/helpers.rb +0 -338
  67. data/lib/searchlogic/config/search.rb +0 -53
  68. data/lib/searchlogic/core_ext/hash.rb +0 -75
  69. data/lib/searchlogic/helpers/control_types/link.rb +0 -310
  70. data/lib/searchlogic/helpers/control_types/links.rb +0 -242
  71. data/lib/searchlogic/helpers/control_types/remote_link.rb +0 -87
  72. data/lib/searchlogic/helpers/control_types/remote_links.rb +0 -72
  73. data/lib/searchlogic/helpers/control_types/remote_select.rb +0 -36
  74. data/lib/searchlogic/helpers/control_types/select.rb +0 -82
  75. data/lib/searchlogic/helpers/form.rb +0 -208
  76. data/lib/searchlogic/helpers/utilities.rb +0 -197
  77. data/lib/searchlogic/modifiers/absolute.rb +0 -15
  78. data/lib/searchlogic/modifiers/acos.rb +0 -11
  79. data/lib/searchlogic/modifiers/asin.rb +0 -11
  80. data/lib/searchlogic/modifiers/atan.rb +0 -11
  81. data/lib/searchlogic/modifiers/avg.rb +0 -15
  82. data/lib/searchlogic/modifiers/base.rb +0 -27
  83. data/lib/searchlogic/modifiers/ceil.rb +0 -15
  84. data/lib/searchlogic/modifiers/char_length.rb +0 -15
  85. data/lib/searchlogic/modifiers/cos.rb +0 -15
  86. data/lib/searchlogic/modifiers/cot.rb +0 -15
  87. data/lib/searchlogic/modifiers/count.rb +0 -11
  88. data/lib/searchlogic/modifiers/day_of_month.rb +0 -15
  89. data/lib/searchlogic/modifiers/day_of_week.rb +0 -15
  90. data/lib/searchlogic/modifiers/day_of_year.rb +0 -15
  91. data/lib/searchlogic/modifiers/degrees.rb +0 -11
  92. data/lib/searchlogic/modifiers/exp.rb +0 -15
  93. data/lib/searchlogic/modifiers/floor.rb +0 -15
  94. data/lib/searchlogic/modifiers/hex.rb +0 -11
  95. data/lib/searchlogic/modifiers/hour.rb +0 -11
  96. data/lib/searchlogic/modifiers/log.rb +0 -15
  97. data/lib/searchlogic/modifiers/log10.rb +0 -11
  98. data/lib/searchlogic/modifiers/log2.rb +0 -11
  99. data/lib/searchlogic/modifiers/lower.rb +0 -15
  100. data/lib/searchlogic/modifiers/ltrim.rb +0 -15
  101. data/lib/searchlogic/modifiers/md5.rb +0 -11
  102. data/lib/searchlogic/modifiers/microseconds.rb +0 -11
  103. data/lib/searchlogic/modifiers/milliseconds.rb +0 -11
  104. data/lib/searchlogic/modifiers/minute.rb +0 -15
  105. data/lib/searchlogic/modifiers/month.rb +0 -15
  106. data/lib/searchlogic/modifiers/octal.rb +0 -15
  107. data/lib/searchlogic/modifiers/radians.rb +0 -11
  108. data/lib/searchlogic/modifiers/round.rb +0 -11
  109. data/lib/searchlogic/modifiers/rtrim.rb +0 -15
  110. data/lib/searchlogic/modifiers/second.rb +0 -15
  111. data/lib/searchlogic/modifiers/sign.rb +0 -11
  112. data/lib/searchlogic/modifiers/sin.rb +0 -11
  113. data/lib/searchlogic/modifiers/square_root.rb +0 -15
  114. data/lib/searchlogic/modifiers/sum.rb +0 -11
  115. data/lib/searchlogic/modifiers/tan.rb +0 -15
  116. data/lib/searchlogic/modifiers/trim.rb +0 -15
  117. data/lib/searchlogic/modifiers/upper.rb +0 -15
  118. data/lib/searchlogic/modifiers/week.rb +0 -11
  119. data/lib/searchlogic/modifiers/year.rb +0 -11
  120. data/lib/searchlogic/search/base.rb +0 -148
  121. data/lib/searchlogic/search/conditions.rb +0 -53
  122. data/lib/searchlogic/search/ordering.rb +0 -244
  123. data/lib/searchlogic/search/pagination.rb +0 -121
  124. data/lib/searchlogic/search/protection.rb +0 -89
  125. data/lib/searchlogic/search/searching.rb +0 -32
  126. data/lib/searchlogic/shared/utilities.rb +0 -57
  127. data/lib/searchlogic/shared/virtual_classes.rb +0 -39
  128. data/lib/searchlogic/version.rb +0 -79
  129. data/test/active_record_tests/associations_test.rb +0 -94
  130. data/test/active_record_tests/base_test.rb +0 -115
  131. data/test/condition_tests/base_test.rb +0 -62
  132. data/test/condition_tests/begins_with_test.rb +0 -11
  133. data/test/condition_tests/blank_test.rb +0 -31
  134. data/test/condition_tests/child_of_test.rb +0 -17
  135. data/test/condition_tests/descendant_of_test.rb +0 -12
  136. data/test/condition_tests/ends_with_test.rb +0 -11
  137. data/test/condition_tests/equals_test.rb +0 -28
  138. data/test/condition_tests/greater_than_or_equal_to_test.rb +0 -11
  139. data/test/condition_tests/greater_than_test.rb +0 -11
  140. data/test/condition_tests/inclusive_descendant_of_test.rb +0 -12
  141. data/test/condition_tests/keywords_test.rb +0 -23
  142. data/test/condition_tests/less_than_or_equal_to_test.rb +0 -11
  143. data/test/condition_tests/less_than_test.rb +0 -11
  144. data/test/condition_tests/like_test.rb +0 -11
  145. data/test/condition_tests/nil_test.rb +0 -31
  146. data/test/condition_tests/not_begin_with_test.rb +0 -8
  147. data/test/condition_tests/not_blank_test.rb +0 -8
  148. data/test/condition_tests/not_end_with_test.rb +0 -8
  149. data/test/condition_tests/not_equal_test.rb +0 -19
  150. data/test/condition_tests/not_have_keywords_test.rb +0 -8
  151. data/test/condition_tests/not_like_test.rb +0 -8
  152. data/test/condition_tests/not_nil_test.rb +0 -13
  153. data/test/condition_tests/sibling_of_test.rb +0 -15
  154. data/test/conditions_tests/any_or_all_test.rb +0 -23
  155. data/test/conditions_tests/base_test.rb +0 -185
  156. data/test/conditions_tests/groups_test.rb +0 -68
  157. data/test/conditions_tests/magic_methods_test.rb +0 -36
  158. data/test/conditions_tests/multiparameter_attributes_test.rb +0 -15
  159. data/test/conditions_tests/protection_test.rb +0 -18
  160. data/test/config_test.rb +0 -23
  161. data/test/fixtures/accounts.yml +0 -12
  162. data/test/fixtures/animals.yml +0 -7
  163. data/test/fixtures/orders.yml +0 -12
  164. data/test/fixtures/user_groups.yml +0 -5
  165. data/test/fixtures/users.yml +0 -45
  166. data/test/libs/awesome_nested_set.rb +0 -545
  167. data/test/libs/awesome_nested_set/.autotest +0 -13
  168. data/test/libs/awesome_nested_set/compatability.rb +0 -29
  169. data/test/libs/awesome_nested_set/helper.rb +0 -40
  170. data/test/libs/awesome_nested_set/named_scope.rb +0 -140
  171. data/test/libs/rexml_fix.rb +0 -14
  172. data/test/modifier_tests/day_of_month_test.rb +0 -16
  173. data/test/search_tests/base_test.rb +0 -241
  174. data/test/search_tests/conditions_test.rb +0 -21
  175. data/test/search_tests/ordering_test.rb +0 -167
  176. data/test/search_tests/pagination_test.rb +0 -74
  177. data/test/search_tests/protection_test.rb +0 -26
  178. data/test/test_helper.rb +0 -122
data/Rakefile CHANGED
@@ -1,20 +1,56 @@
1
- ENV['RDOCOPT'] = "-S -f html -T hanna"
2
-
3
- require "rubygems"
4
- require "hoe"
5
- require File.dirname(__FILE__) << "/lib/searchlogic/version"
6
-
7
- Hoe.new("Searchlogic", Searchlogic::Version::STRING) do |p|
8
- p.name = "searchlogic"
9
- p.author = "Ben Johnson of Binary Logic"
10
- p.email = 'bjohnson@binarylogic.com'
11
- p.summary = "Object based ActiveRecord searching, ordering, pagination, and more!"
12
- p.description = "Object based ActiveRecord searching, ordering, pagination, and more!"
13
- p.url = "http://github.com/binarylogic/searchlogic"
14
- p.history_file = "CHANGELOG.rdoc"
15
- p.readme_file = "README.rdoc"
16
- p.extra_rdoc_files = ["CHANGELOG.rdoc", "README.rdoc"]
17
- p.remote_rdoc_dir = ''
18
- p.test_globs = ["test/*/test_*.rb", "test/*_test.rb", "test/*/*_test.rb"]
19
- p.extra_deps = %w(activesupport activerecord)
20
- end
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "searchlogic"
8
+ gem.summary = "Searchlogic provides common named scopes and object based searching for ActiveRecord."
9
+ gem.email = "bjohnson@binarylogic.com"
10
+ gem.homepage = "http://github.com/binarylogic/searchlogic"
11
+ gem.authors = ["Ben Johnson of Binary Logic"]
12
+ gem.rubyforge_project = "searchlogic"
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
16
+ end
17
+
18
+ require 'spec/rake/spectask'
19
+ Spec::Rake::SpecTask.new(:spec) do |spec|
20
+ spec.libs << 'lib' << 'spec'
21
+ spec.spec_files = FileList['spec/**/*_spec.rb']
22
+ end
23
+
24
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.pattern = 'spec/**/*_spec.rb'
27
+ spec.rcov = true
28
+ end
29
+
30
+
31
+ task :default => :spec
32
+
33
+ require 'rake/rdoctask'
34
+ Rake::RDocTask.new do |rdoc|
35
+ if File.exist?('VERSION.yml')
36
+ config = YAML.load(File.read('VERSION.yml'))
37
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
38
+ else
39
+ version = ""
40
+ end
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "search #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
47
+
48
+ begin
49
+ require 'rake/contrib/sshpublisher'
50
+ namespace :rubyforge do
51
+ desc "Release gem to RubyForge"
52
+ task :release => ["rubyforge:release:gem"]
53
+ end
54
+ rescue LoadError
55
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
56
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 2
3
+ :minor: 1
4
+ :patch: 0
data/init.rb CHANGED
@@ -1 +1 @@
1
- require File.dirname(__FILE__) << "/lib/searchlogic"
1
+ require "searchlogic"
@@ -1,100 +1,20 @@
1
- $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
-
3
- require "active_support"
4
- require "active_record"
5
- require "active_record/version"
6
-
7
- ["mysql", "postgresql", "sqlite"].each do |adapter_name|
8
- begin
9
- require "active_record/connection_adapters/#{adapter_name}_adapter"
10
- require "searchlogic/active_record/connection_adapters/#{adapter_name}_adapter"
11
- rescue Exception
12
- end
13
- end
14
-
15
- # Core Ext
16
- require "searchlogic/core_ext/hash"
1
+ require "searchlogic/core_ext/proc"
17
2
  require "searchlogic/core_ext/object"
18
-
19
- # Shared
20
- require "searchlogic/shared/utilities"
21
- require "searchlogic/shared/virtual_classes"
22
-
23
- # Base classes
24
- require "searchlogic/version"
25
- require "searchlogic/config/helpers"
26
- require "searchlogic/config/search"
27
- require "searchlogic/config"
28
-
29
- # ActiveRecord
30
- require "searchlogic/active_record/base"
31
- require "searchlogic/active_record/associations"
32
-
33
- # Search
34
- require "searchlogic/search/ordering"
35
- require "searchlogic/search/pagination"
36
- require "searchlogic/search/conditions"
37
- require "searchlogic/search/searching"
38
- require "searchlogic/search/base"
39
- require "searchlogic/search/protection"
40
-
41
- # Conditions
42
- require "searchlogic/conditions/any_or_all"
43
- require "searchlogic/conditions/groups"
44
- require "searchlogic/conditions/magic_methods"
45
- require "searchlogic/conditions/multiparameter_attributes"
46
- require "searchlogic/conditions/protection"
47
- require "searchlogic/conditions/base"
48
-
49
- # Condition
50
- require "searchlogic/condition/base"
51
- require "searchlogic/condition/nested_set"
52
- SEARCHLOGIC_CONDITIONS = [:begins_with, :blank, :child_of, :descendant_of, :ends_with, :equals, :greater_than, :greater_than_or_equal_to, :inclusive_descendant_of, :like, :nil, :not_begin_with,
53
- :not_blank, :not_end_with, :not_equal, :not_have_keywords, :not_like, :not_nil, :keywords, :less_than, :less_than_or_equal_to, :sibling_of]
54
- SEARCHLOGIC_CONDITIONS.each { |condition| require "searchlogic/condition/#{condition}" }
55
-
56
- # Modifiers
57
- require "searchlogic/modifiers/base"
58
- SEARCHLOGIC_MODIFIERS = [:absolute, :acos, :asin, :atan, :avg, :ceil, :char_length, :cos, :cot, :count, :day_of_month, :day_of_week, :day_of_year, :degrees, :exp, :floor, :hex, :hour, :log, :log10,
59
- :log2, :lower, :ltrim, :md5, :microseconds, :milliseconds, :minute, :month, :octal, :radians, :round, :rtrim, :second, :sign, :sin, :square_root, :sum, :tan, :trim, :upper, :week, :year]
60
- SEARCHLOGIC_MODIFIERS.each { |modifier| require "searchlogic/modifiers/#{modifier}" }
61
-
62
- # Helpers
63
- require "searchlogic/helpers/utilities"
64
- require "searchlogic/helpers/form"
65
- require "searchlogic/helpers/control_types/link"
66
- require "searchlogic/helpers/control_types/links"
67
- require "searchlogic/helpers/control_types/select"
68
- require "searchlogic/helpers/control_types/remote_link"
69
- require "searchlogic/helpers/control_types/remote_links"
70
- require "searchlogic/helpers/control_types/remote_select"
71
-
72
- # Lets do it!
73
- module Searchlogic
74
- module Search
75
- class Base
76
- include Conditions
77
- include Ordering
78
- include Protection
79
- include Pagination
80
- include Searching
81
- end
82
- end
83
-
84
- module Conditions
85
- class Base
86
- include AnyOrAll
87
- include Groups
88
- include MagicMethods
89
- include MultiparameterAttributes
90
- include Protection
91
- end
92
-
93
- SEARCHLOGIC_CONDITIONS.each { |condition| Base.register_condition("Searchlogic::Condition::#{condition.to_s.camelize}".constantize) }
94
- SEARCHLOGIC_MODIFIERS.each { |modifier| Base.register_modifier("Searchlogic::Modifiers::#{modifier.to_s.camelize}".constantize) }
95
- end
96
-
97
- # The namespace I put all cached search classes.
98
- module Cache
99
- end
3
+ require "searchlogic/named_scopes/conditions"
4
+ require "searchlogic/named_scopes/ordering"
5
+ require "searchlogic/named_scopes/associations"
6
+ require "searchlogic/named_scopes/alias_scope"
7
+ require "searchlogic/search"
8
+
9
+ Proc.send(:include, Searchlogic::CoreExt::Proc)
10
+ Object.send(:include, Searchlogic::CoreExt::Object)
11
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::Conditions)
12
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::Ordering)
13
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::Associations)
14
+ ActiveRecord::Base.extend(Searchlogic::NamedScopes::AliasScope)
15
+ ActiveRecord::Base.extend(Searchlogic::Search::Implementation)
16
+
17
+ if defined?(ActionController)
18
+ require "searchlogic/rails_helpers"
19
+ ActionController::Base.helper(Searchlogic::RailsHelpers)
100
20
  end
@@ -1,19 +1,39 @@
1
1
  module Searchlogic
2
2
  module CoreExt
3
+ # Contains extensions for the Object class that Searchlogic uses.
3
4
  module Object
4
- # Get object's meta (ghost, eigenclass, singleton) class
5
- def metaclass
6
- class << self
7
- self
8
- end
9
- end
10
-
11
- # If class_eval is called on an object, add those methods to its metaclass
12
- def class_eval(*args, &block)
13
- metaclass.class_eval(*args, &block)
5
+ # Searchlogic needs to know the expected type of the condition value so that it can properly cast
6
+ # the value in the Searchlogic::Search object. For example:
7
+ #
8
+ # search = User.search(:id_gt => "1")
9
+ #
10
+ # You would expect this:
11
+ #
12
+ # search.id_gt => 1
13
+ #
14
+ # Not this:
15
+ #
16
+ # search.id_gt => "1"
17
+ #
18
+ # Parameter values from forms are ALWAYS strings, so we have to cast them. Just like ActiveRecord
19
+ # does when you instantiate a new User object.
20
+ #
21
+ # The problem is that ruby has no variable types, so Searchlogic needs to know what type you are expecting
22
+ # for your named scope. So instead of this:
23
+ #
24
+ # named_scope :id_gt, lambda { |value| {:conditions => ["id > ?", value]} }
25
+ #
26
+ # You need to do this:
27
+ #
28
+ # named_scope :id_gt, searchlogic_lambda(:integer) { |value| {:conditions => ["id > ?", value]} }
29
+ #
30
+ # If you are wanting a string, you don't have to do anything, because Searchlogic assumes you are want a string.
31
+ # If you want something else, you need to specify it as I did in the above example.
32
+ def searchlogic_lambda(type = :string, &block)
33
+ proc = lambda(&block)
34
+ proc.searchlogic_arg_type = type
35
+ proc
14
36
  end
15
37
  end
16
38
  end
17
- end
18
-
19
- Object.send(:include, Searchlogic::CoreExt::Object)
39
+ end
@@ -0,0 +1,11 @@
1
+ module Searchlogic
2
+ module CoreExt
3
+ module Proc # :nodoc:
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ attr_accessor :searchlogic_arg_type
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,63 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Adds the ability to create alias scopes that allow you to alias a named
4
+ # scope or create a named scope procedure, while at the same time letting
5
+ # Searchlogic know that this is a safe method.
6
+ module AliasScope
7
+ # The searchlogic Search class takes a hash and chains the values together as named scopes.
8
+ # For security reasons the only hash keys that are allowed must be mapped to named scopes.
9
+ # You can not pass the name of a class method and expect that to be called. In some instances
10
+ # you might create a class method that essentially aliases a named scope or represents a
11
+ # named scope procedure. Ex:
12
+ #
13
+ # User.named_scope :teenager, :conditions => ["age >= ? AND age <= ?", 13, 19]
14
+ #
15
+ # This is obviously a very basic example, but there is logic that is duplicated here. For
16
+ # more complicated named scopes this might make more sense, but to make my point you could
17
+ # do something like this instead
18
+ #
19
+ # class User
20
+ # def teenager
21
+ # age_gte(13).age_lte(19)
22
+ # end
23
+ # end
24
+ #
25
+ # As I stated above, you could not use this method with the Searchlogic::Search class because
26
+ # there is no way to tell that this is actually a named scope. Instead, Searchlogic lets you
27
+ # do something like this:
28
+ #
29
+ # User.alias_scope :teenager, lambda { age_gte(13).age_lte(19) }
30
+ #
31
+ # It fits in better, at the same time Searchlogic will know this is an acceptable named scope.
32
+ def alias_scope(name, options = nil)
33
+ alias_scopes[name.to_sym] = options
34
+ (class << self; self end).instance_eval do
35
+ define_method name do |*args|
36
+ case options
37
+ when Symbol
38
+ send(options)
39
+ else
40
+ options.call(*args)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def alias_scopes # :nodoc:
47
+ @alias_scopes ||= {}
48
+ end
49
+
50
+ def alias_scope?(name) # :nodoc:
51
+ alias_scopes.key?(name.to_sym)
52
+ end
53
+
54
+ def condition?(name) # :nodoc:
55
+ super || alias_scope?(name)
56
+ end
57
+
58
+ def named_scope_options(name) # :nodoc:
59
+ super || alias_scopes[name.to_sym]
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,126 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for associations.
4
+ module Associations
5
+ def condition?(name) # :nodoc:
6
+ super || association_condition?(name) || association_alias_condition?(name)
7
+ end
8
+
9
+ def primary_condition_name(name) # :nodoc:
10
+ if result = super
11
+ result
12
+ elsif association_condition?(name)
13
+ name.to_sym
14
+ elsif details = association_alias_condition_details(name)
15
+ "#{details[:association]}_#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
16
+ else
17
+ nil
18
+ end
19
+ end
20
+
21
+ # Is the name of the method a valid name for an association condition?
22
+ def association_condition?(name)
23
+ !association_condition_details(name).nil?
24
+ end
25
+
26
+ # Is the ane of the method a valie name for an association alias condition?
27
+ # An alias being "gt" for "greater_than", etc.
28
+ def association_alias_condition?(name)
29
+ !association_alias_condition_details(name).nil?
30
+ end
31
+
32
+ private
33
+ def method_missing(name, *args, &block)
34
+ if details = association_condition_details(name)
35
+ create_association_condition(details[:association], details[:column], details[:condition], args)
36
+ send(name, *args)
37
+ elsif details = association_alias_condition_details(name)
38
+ create_association_alias_condition(details[:association], details[:column], details[:condition], args)
39
+ send(name, *args)
40
+ elsif details = association_ordering_condition_details(name)
41
+ create_association_ordering_condition(details[:association], details[:order_as], details[:column], args)
42
+ send(name, *args)
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ def association_condition_details(name)
49
+ associations = reflect_on_all_associations.collect { |assoc| assoc.name }
50
+ if name.to_s =~ /^(#{associations.join("|")})_(\w+)_(#{Conditions::PRIMARY_CONDITIONS.join("|")})$/
51
+ {:association => $1, :column => $2, :condition => $3}
52
+ end
53
+ end
54
+
55
+ def create_association_condition(association_name, column, condition, args)
56
+ named_scope("#{association_name}_#{column}_#{condition}", association_condition_options(association_name, "#{column}_#{condition}", args))
57
+ end
58
+
59
+ def association_alias_condition_details(name)
60
+ associations = reflect_on_all_associations.collect { |assoc| assoc.name }
61
+ if name.to_s =~ /^(#{associations.join("|")})_(\w+)_(#{Conditions::ALIAS_CONDITIONS.join("|")})$/
62
+ {:association => $1, :column => $2, :condition => $3}
63
+ end
64
+ end
65
+
66
+ def create_association_alias_condition(association, column, condition, args)
67
+ primary_condition = primary_condition(condition)
68
+ alias_name = "#{association}_#{column}_#{condition}"
69
+ primary_name = "#{association}_#{column}_#{primary_condition}"
70
+ send(primary_name, *args) # go back to method_missing and make sure we create the method
71
+ (class << self; self; end).class_eval { alias_method alias_name, primary_name }
72
+ end
73
+
74
+ def association_ordering_condition_details(name)
75
+ associations = reflect_on_all_associations.collect { |assoc| assoc.name }
76
+ if name.to_s =~ /^(ascend|descend)_by_(#{associations.join("|")})_(\w+)$/
77
+ {:order_as => $1, :association => $2, :column => $3}
78
+ end
79
+ end
80
+
81
+ def create_association_ordering_condition(association_name, order_as, column, args)
82
+ named_scope("#{order_as}_by_#{association_name}_#{column}", association_condition_options(association_name, "#{order_as}_by_#{column}", args))
83
+ end
84
+
85
+ def association_condition_options(association_name, association_condition, args)
86
+ association = reflect_on_association(association_name.to_sym)
87
+ scope = association.klass.send(association_condition, *args)
88
+ scope_options = association.klass.named_scope_options(association_condition)
89
+ arity = association.klass.named_scope_arity(association_condition)
90
+
91
+ if !arity || arity == 0
92
+ # The underlying condition doesn't require any parameters, so let's just create a simple
93
+ # named scope that is based on a hash.
94
+ options = scope.proxy_options
95
+ options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]}
96
+ #add_left_outer_joins(options, association)
97
+ options
98
+ else
99
+ # The underlying condition requires parameters, let's match the parameters it requires
100
+ # and pass those onto the named scope. We can't use proxy_options because that returns the
101
+ # result after a value has been passed.
102
+ proc_args = []
103
+ if arity > 0
104
+ arity.times { |i| proc_args << "arg#{i}"}
105
+ else
106
+ positive_arity = arity * -1
107
+ positive_arity.times do |i|
108
+ if i == (positive_arity - 1)
109
+ proc_args << "*arg#{i}"
110
+ else
111
+ proc_args << "arg#{i}"
112
+ end
113
+ end
114
+ end
115
+ eval <<-"end_eval"
116
+ searchlogic_lambda(:#{scope_options.searchlogic_arg_type}) { |#{proc_args.join(",")}|
117
+ options = association.klass.named_scope_options(association_condition).call(#{proc_args.join(",")})
118
+ options[:joins] = options[:joins].blank? ? association.name : {association.name => options[:joins]}
119
+ options
120
+ }
121
+ end_eval
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end