searchlogic-donotuse 2.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for 'OR' conditions. Please see the README for a more
4
+ # detailed explanation.
5
+ module OrConditions
6
+ class NoConditionSpecifiedError < StandardError; end
7
+ class UnknownConditionError < StandardError; end
8
+
9
+ def condition?(name) # :nodoc:
10
+ super || or_condition?(name)
11
+ end
12
+
13
+ private
14
+ def or_condition?(name)
15
+ !or_conditions(name).nil?
16
+ end
17
+
18
+ def method_missing(name, *args, &block)
19
+ if conditions = or_conditions(name)
20
+ create_or_condition(conditions, args)
21
+ (class << self; self; end).class_eval { alias_method name, conditions.join("_or_") } if !respond_to?(name)
22
+ send(name, *args)
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def or_conditions(name)
29
+ # First determine if we should even work on the name, we want to be as quick as possible
30
+ # with this.
31
+ if (parts = split_or_condition(name)).size > 1
32
+ conditions = interpolate_or_conditions(parts)
33
+ if conditions.any?
34
+ conditions
35
+ else
36
+ nil
37
+ end
38
+ end
39
+ end
40
+
41
+ def split_or_condition(name)
42
+ parts = name.to_s.split("_or_")
43
+ new_parts = []
44
+ parts.each do |part|
45
+ if part =~ /^equal_to(_any|_all)?$/
46
+ new_parts << new_parts.pop + "_or_equal_to"
47
+ else
48
+ new_parts << part
49
+ end
50
+ end
51
+ new_parts
52
+ end
53
+
54
+ # The purpose of this method is to convert the method name parts into actual condition names.
55
+ #
56
+ # Example:
57
+ #
58
+ # ["first_name", "last_name_like"]
59
+ # => ["first_name_like", "last_name_like"]
60
+ #
61
+ # ["id_gt", "first_name_begins_with", "last_name", "middle_name_like"]
62
+ # => ["id_gt", "first_name_begins_with", "last_name_like", "middle_name_like"]
63
+ #
64
+ # Basically if a column is specified without a condition the next condition in the list
65
+ # is what will be used. Once we are able to get a consistent list of conditions we can easily
66
+ # create a scope for it.
67
+ def interpolate_or_conditions(parts)
68
+ conditions = []
69
+ last_condition = nil
70
+
71
+ parts.reverse.each do |part|
72
+ if details = condition_details(part)
73
+ # We are a searchlogic defined scope
74
+ conditions << "#{details[:column]}_#{details[:condition]}"
75
+ last_condition = details[:condition]
76
+ elsif association_details = association_condition_details(part, last_condition)
77
+ path = full_association_path(part, last_condition, association_details[:association])
78
+ conditions << "#{path[:path].join("_").to_sym}_#{path[:column]}_#{path[:condition]}"
79
+ last_condition = path[:condition] || nil
80
+ elsif local_condition?(part)
81
+ # We are a custom scope
82
+ conditions << part
83
+ elsif column_names.include?(part)
84
+ # we are a column, use the last condition
85
+ if last_condition.nil?
86
+ raise NoConditionSpecifiedError.new("The '#{part}' column doesn't know which condition to use, if you use an exact column " +
87
+ "name you need to specify a condition sometime after (ex: id_or_created_at_lt), where id would use the 'lt' condition.")
88
+ end
89
+
90
+ conditions << "#{part}_#{last_condition}"
91
+ else
92
+ raise UnknownConditionError.new("The condition '#{part}' is not a valid condition, we could not find any scopes that match this.")
93
+ end
94
+ end
95
+
96
+ conditions.reverse
97
+ end
98
+
99
+ def full_association_path(part, last_condition, given_assoc)
100
+ path = [given_assoc.to_sym]
101
+ part.sub!(/^#{given_assoc}_/, "")
102
+ klass = self
103
+ while klass = klass.send(:reflect_on_association, given_assoc.to_sym)
104
+ klass = klass.klass
105
+ if details = klass.send(:association_condition_details, part, last_condition)
106
+ path << details[:association]
107
+ part = details[:condition]
108
+ given_assoc = details[:association]
109
+ elsif details = klass.send(:condition_details, part, nil)
110
+ return { :path => path, :column => details[:column], :condition => details[:condition] }
111
+ end
112
+ end
113
+ { :path => path, :column => part, :condition => last_condition }
114
+ end
115
+
116
+ def create_or_condition(scopes, args)
117
+ named_scope scopes.join("_or_"), lambda { |*args|
118
+ merge_scopes_with_or(scopes.collect { |scope| [scope, *args] })
119
+ }
120
+ end
121
+
122
+ def merge_scopes_with_or(scopes)
123
+ scopes_options = scopes.collect { |scope, *args| send(scope, *args).proxy_options }
124
+ conditions = scopes_options.reject { |o| o[:conditions].nil? }.collect { |o| sanitize_sql(o[:conditions]) }
125
+
126
+ scope = scopes.inject(scoped({})) do |scope, info|
127
+ scope_name, *args = info
128
+ scope.send(scope_name, *args)
129
+ end
130
+
131
+ options = scope.scope(:find)
132
+ options.delete(:readonly) unless scope.proxy_options.key?(:readonly)
133
+ options.merge(:conditions => "(" + conditions.join(") OR (") + ")")
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,48 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for ordering by columns. Example:
4
+ #
5
+ # User.ascend_by_id
6
+ # User.descend_by_username
7
+ #
8
+ # See the README for a more detailed explanation.
9
+ module Ordering
10
+ def condition?(name) # :nodoc:
11
+ super || ordering_condition?(name)
12
+ end
13
+
14
+ private
15
+ def ordering_condition?(name) # :nodoc:
16
+ !ordering_condition_details(name).nil?
17
+ end
18
+
19
+ def method_missing(name, *args, &block)
20
+ if name == :order
21
+ named_scope name, lambda { |scope_name|
22
+ return {} if !condition?(scope_name)
23
+ send(scope_name).proxy_options
24
+ }
25
+ send(name, *args)
26
+ elsif details = ordering_condition_details(name)
27
+ create_ordering_conditions(details[:column])
28
+ send(name, *args)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def ordering_condition_details(name)
35
+ if name.to_s =~ /^(ascend|descend)_by_(#{column_names.join("|")})$/
36
+ {:order_as => $1, :column => $2}
37
+ elsif name.to_s =~ /^order$/
38
+ {}
39
+ end
40
+ end
41
+
42
+ def create_ordering_conditions(column)
43
+ named_scope("ascend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} ASC"})
44
+ named_scope("descend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} DESC"})
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,76 @@
1
+ module Searchlogic
2
+ module RailsHelpers
3
+ # Creates a link that alternates between acending and descending. It basically
4
+ # alternates between calling 2 named scopes: "ascend_by_*" and "descend_by_*"
5
+ #
6
+ # By default Searchlogic gives you these named scopes for all of your columns, but
7
+ # if you wanted to create your own, it will work with those too.
8
+ #
9
+ # Examples:
10
+ #
11
+ # order @search, :by => :username
12
+ # order @search, :by => :created_at, :as => "Created"
13
+ #
14
+ # This helper accepts the following options:
15
+ #
16
+ # * <tt>:by</tt> - the name of the named scope. This helper will prepend this value with "ascend_by_" and "descend_by_"
17
+ # * <tt>:as</tt> - the text used in the link, defaults to whatever is passed to :by
18
+ # * <tt>:ascend_scope</tt> - what scope to call for ascending the data, defaults to "ascend_by_:by"
19
+ # * <tt>:descend_scope</tt> - what scope to call for descending the data, defaults to "descend_by_:by"
20
+ # * <tt>:params</tt> - hash with additional params which will be added to generated url
21
+ # * <tt>:params_scope</tt> - the name of the params key to scope the order condition by, defaults to :search
22
+ def order(search, options = {}, html_options = {})
23
+ options[:params_scope] ||= :search
24
+ if !options[:as]
25
+ id = options[:by].to_s.downcase == "id"
26
+ options[:as] = id ? options[:by].to_s.upcase : options[:by].to_s.humanize
27
+ end
28
+ options[:ascend_scope] ||= "ascend_by_#{options[:by]}"
29
+ options[:descend_scope] ||= "descend_by_#{options[:by]}"
30
+ ascending = search.order.to_s == options[:ascend_scope]
31
+ new_scope = ascending ? options[:descend_scope] : options[:ascend_scope]
32
+ selected = [options[:ascend_scope], options[:descend_scope]].include?(search.order.to_s)
33
+ if selected
34
+ css_classes = html_options[:class] ? html_options[:class].split(" ") : []
35
+ if ascending
36
+ options[:as] = "&#9650;&nbsp;#{options[:as]}"
37
+ css_classes << "ascending"
38
+ else
39
+ options[:as] = "&#9660;&nbsp;#{options[:as]}"
40
+ css_classes << "descending"
41
+ end
42
+ html_options[:class] = css_classes.join(" ")
43
+ end
44
+ url_options = {
45
+ options[:params_scope] => search.conditions.merge( { :order => new_scope } )
46
+ }.deep_merge(options[:params] || {})
47
+ link_to options[:as], url_for(url_options), html_options
48
+ end
49
+
50
+ # Automatically makes the form method :get if a Searchlogic::Search and sets
51
+ # the params scope to :search
52
+ def form_for(*args, &block)
53
+ if search_obj = args.find { |arg| arg.is_a?(Searchlogic::Search) }
54
+ options = args.extract_options!
55
+ options[:html] ||= {}
56
+ options[:html][:method] ||= :get
57
+ options[:url] ||= url_for
58
+ args.unshift(:search) if args.first == search_obj
59
+ args << options
60
+ end
61
+ super
62
+ end
63
+
64
+ # Automatically adds an "order" hidden field in your form to preserve how the data
65
+ # is being ordered.
66
+ def fields_for(*args, &block)
67
+ if search_obj = args.find { |arg| arg.is_a?(Searchlogic::Search) }
68
+ args.unshift(:search) if args.first == search_obj
69
+ concat(content_tag("div", hidden_field_tag("#{args.first}[order]", search_obj.order)) + "\n")
70
+ super
71
+ else
72
+ super
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,179 @@
1
+ module Searchlogic
2
+ # A class that acts like a model, creates attr_accessors for named_scopes, and then
3
+ # chains together everything when an "action" method is called. It basically makes
4
+ # implementing search forms in your application effortless:
5
+ #
6
+ # search = User.search
7
+ # search.username_like = "bjohnson"
8
+ # search.all
9
+ #
10
+ # Is equivalent to:
11
+ #
12
+ # User.search(:username_like => "bjohnson").all
13
+ #
14
+ # Is equivalent to:
15
+ #
16
+ # User.username_like("bjohnson").all
17
+ class Search
18
+ # Responsible for adding a "search" method into your models.
19
+ module Implementation
20
+ # Additional method, gets aliased as "search" if that method
21
+ # is available. A lot of other libraries like to use "search"
22
+ # as well, so if you have a conflict like this, you can use
23
+ # this method directly.
24
+ def searchlogic(conditions = {})
25
+ Search.new(self, scope(:find), conditions)
26
+ end
27
+ end
28
+
29
+ # Is an invalid condition is used this error will be raised. Ex:
30
+ #
31
+ # User.search(:unkown => true)
32
+ #
33
+ # Where unknown is not a valid named scope for the User model.
34
+ class UnknownConditionError < StandardError
35
+ def initialize(condition)
36
+ msg = "The #{condition} is not a valid condition. You may only use conditions that map to a named scope"
37
+ super(msg)
38
+ end
39
+ end
40
+
41
+ attr_accessor :klass, :current_scope, :conditions
42
+ undef :id if respond_to?(:id)
43
+
44
+ # Creates a new search object for the given class. Ex:
45
+ #
46
+ # Searchlogic::Search.new(User, {}, {:username_like => "bjohnson"})
47
+ def initialize(klass, current_scope, conditions = {})
48
+ self.klass = klass
49
+ self.current_scope = current_scope
50
+ self.conditions = conditions if conditions.is_a?(Hash)
51
+ end
52
+
53
+ def clone
54
+ self.class.new(klass, current_scope && current_scope.clone, conditions.clone)
55
+ end
56
+
57
+ # Returns a hash of the current conditions set.
58
+ def conditions
59
+ @conditions ||= {}
60
+ end
61
+
62
+ # Accepts a hash of conditions.
63
+ def conditions=(values)
64
+ values.each do |condition, value|
65
+ value.delete_if { |v| ignore_value?(v) } if value.is_a?(Array)
66
+ next if ignore_value?(value)
67
+ send("#{condition}=", value)
68
+ end
69
+ end
70
+
71
+ # Delete a condition from the search. Since conditions map to named scopes,
72
+ # if a named scope accepts a parameter there is no way to actually delete
73
+ # the scope if you do not want it anymore. A nil value might be meaningful
74
+ # to that scope.
75
+ def delete(*names)
76
+ names.each { |name| @conditions.delete(name.to_sym) }
77
+ self
78
+ end
79
+
80
+ private
81
+ def method_missing(name, *args, &block)
82
+ condition_name = condition_name(name)
83
+ scope_name = scope_name(condition_name)
84
+
85
+ if setter?(name)
86
+ if scope?(scope_name)
87
+ conditions[condition_name] = type_cast(args.first, cast_type(scope_name))
88
+ else
89
+ raise UnknownConditionError.new(condition_name)
90
+ end
91
+ elsif scope?(scope_name)
92
+ if args.size > 0
93
+ send("#{condition_name}=", *args)
94
+ self
95
+ else
96
+ conditions[condition_name]
97
+ end
98
+ else
99
+ scope = conditions.inject(klass.scoped(current_scope) || {}) do |scope, condition|
100
+ scope_name, value = condition
101
+ scope_name = normalize_scope_name(scope_name)
102
+ klass.send(scope_name, value) if !klass.respond_to?(scope_name)
103
+ arity = klass.named_scope_arity(scope_name)
104
+
105
+ if !arity || arity == 0
106
+ if value == true
107
+ scope.send(scope_name)
108
+ else
109
+ scope
110
+ end
111
+ else
112
+ if value.is_a?(Array)
113
+ scope.send(scope_name, *value)
114
+ else
115
+ scope.send(scope_name, value)
116
+ end
117
+ end
118
+ end
119
+ scope.send(name, *args, &block)
120
+ end
121
+ end
122
+
123
+ def normalize_scope_name(scope_name)
124
+ case
125
+ when klass.scopes.key?(scope_name.to_sym) then scope_name.to_sym
126
+ when klass.column_names.include?(scope_name.to_s) then "#{scope_name}_equals".to_sym
127
+ else scope_name.to_sym
128
+ end
129
+ end
130
+
131
+ def setter?(name)
132
+ !(name.to_s =~ /=$/).nil?
133
+ end
134
+
135
+ def condition_name(name)
136
+ condition = name.to_s.match(/(\w+)=?$/)
137
+ condition ? condition[1].to_sym : nil
138
+ end
139
+
140
+ def scope_name(condition_name)
141
+ condition_name && normalize_scope_name(condition_name)
142
+ end
143
+
144
+ def scope?(scope_name)
145
+ klass.scopes.key?(scope_name) || klass.condition?(scope_name)
146
+ end
147
+
148
+ def cast_type(name)
149
+ klass.send(name, nil) if !klass.respond_to?(name) # We need to set up the named scope if it doesn't exist, so we can get a value for named_scope_options
150
+ named_scope_options = klass.named_scope_options(name)
151
+ arity = klass.named_scope_arity(name)
152
+ if !arity || arity == 0
153
+ :boolean
154
+ else
155
+ named_scope_options.respond_to?(:searchlogic_arg_type) ? named_scope_options.searchlogic_arg_type : :string
156
+ end
157
+ end
158
+
159
+ def type_cast(value, type)
160
+ case value
161
+ when Array
162
+ value.collect { |v| type_cast(v, type) }
163
+ when Range
164
+ Range.new(type_cast(value.first, type), type_cast(value.last, type))
165
+ else
166
+ # Let's leverage ActiveRecord's type casting, so that casting is consistent
167
+ # with the other models.
168
+ column_for_type_cast = ::ActiveRecord::ConnectionAdapters::Column.new("", nil)
169
+ column_for_type_cast.instance_variable_set(:@type, type)
170
+ value = column_for_type_cast.type_cast(value)
171
+ Time.zone && value.is_a?(Time) ? value.in_time_zone : value
172
+ end
173
+ end
174
+
175
+ def ignore_value?(value)
176
+ (value.is_a?(String) && value.blank?) || (value.is_a?(Array) && value.empty?)
177
+ end
178
+ end
179
+ end
@@ -0,0 +1 @@
1
+ require "searchlogic"
@@ -0,0 +1,85 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{searchlogic-donotuse}
8
+ s.version = "2.3.9"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ben Johnson of Binary Logic"]
12
+ s.date = %q{2009-11-27}
13
+ s.description = %q{Searchlogic makes using ActiveRecord named scopes easier and less repetitive.}
14
+ s.email = %q{bjohnson@binarylogic.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "CHANGELOG.rdoc",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION.yml",
26
+ "init.rb",
27
+ "lib/searchlogic.rb",
28
+ "lib/searchlogic/active_record/consistency.rb",
29
+ "lib/searchlogic/active_record/named_scopes.rb",
30
+ "lib/searchlogic/core_ext/object.rb",
31
+ "lib/searchlogic/core_ext/proc.rb",
32
+ "lib/searchlogic/named_scopes/alias_scope.rb",
33
+ "lib/searchlogic/named_scopes/association_conditions.rb",
34
+ "lib/searchlogic/named_scopes/association_ordering.rb",
35
+ "lib/searchlogic/named_scopes/conditions.rb",
36
+ "lib/searchlogic/named_scopes/or_conditions.rb",
37
+ "lib/searchlogic/named_scopes/ordering.rb",
38
+ "lib/searchlogic/rails_helpers.rb",
39
+ "lib/searchlogic/search.rb",
40
+ "rails/init.rb",
41
+ "searchlogic.gemspec",
42
+ "spec/core_ext/object_spec.rb",
43
+ "spec/core_ext/proc_spec.rb",
44
+ "spec/named_scopes/alias_scope_spec.rb",
45
+ "spec/named_scopes/association_conditions_spec.rb",
46
+ "spec/named_scopes/association_ordering_spec.rb",
47
+ "spec/named_scopes/conditions_spec.rb",
48
+ "spec/named_scopes/or_conditions_spec.rb",
49
+ "spec/named_scopes/ordering_spec.rb",
50
+ "spec/search_spec.rb",
51
+ "spec/spec_helper.rb"
52
+ ]
53
+ s.homepage = %q{http://github.com/binarylogic/searchlogic}
54
+ s.rdoc_options = ["--charset=UTF-8"]
55
+ s.require_paths = ["lib"]
56
+ s.rubyforge_project = %q{searchlogic-donotuse}
57
+ s.rubygems_version = %q{1.3.5}
58
+ s.summary = %q{Searchlogic makes using ActiveRecord named scopes easier and less repetitive.}
59
+ s.test_files = [
60
+ "spec/core_ext/object_spec.rb",
61
+ "spec/core_ext/proc_spec.rb",
62
+ "spec/named_scopes/alias_scope_spec.rb",
63
+ "spec/named_scopes/association_conditions_spec.rb",
64
+ "spec/named_scopes/association_ordering_spec.rb",
65
+ "spec/named_scopes/conditions_spec.rb",
66
+ "spec/named_scopes/or_conditions_spec.rb",
67
+ "spec/named_scopes/ordering_spec.rb",
68
+ "spec/search_spec.rb",
69
+ "spec/spec_helper.rb"
70
+ ]
71
+
72
+ if s.respond_to? :specification_version then
73
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
74
+ s.specification_version = 3
75
+
76
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
77
+ s.add_runtime_dependency(%q<activerecord>, ["== 2.3.9"])
78
+ else
79
+ s.add_dependency(%q<activerecord>, ["== 2.3.9"])
80
+ end
81
+ else
82
+ s.add_dependency(%q<activerecord>, ["== 2.3.9"])
83
+ end
84
+ end
85
+