obitum-searchlogic 2.4.28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.gitignore +6 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +308 -0
  4. data/Rakefile +35 -0
  5. data/VERSION.yml +5 -0
  6. data/init.rb +1 -0
  7. data/lib/searchlogic.rb +56 -0
  8. data/lib/searchlogic/active_record/association_proxy.rb +19 -0
  9. data/lib/searchlogic/active_record/consistency.rb +49 -0
  10. data/lib/searchlogic/active_record/named_scope_tools.rb +101 -0
  11. data/lib/searchlogic/core_ext/object.rb +43 -0
  12. data/lib/searchlogic/core_ext/proc.rb +17 -0
  13. data/lib/searchlogic/named_scopes/alias_scope.rb +67 -0
  14. data/lib/searchlogic/named_scopes/association_conditions.rb +132 -0
  15. data/lib/searchlogic/named_scopes/association_ordering.rb +44 -0
  16. data/lib/searchlogic/named_scopes/conditions.rb +226 -0
  17. data/lib/searchlogic/named_scopes/or_conditions.rb +141 -0
  18. data/lib/searchlogic/named_scopes/ordering.rb +48 -0
  19. data/lib/searchlogic/rails_helpers.rb +79 -0
  20. data/lib/searchlogic/search.rb +26 -0
  21. data/lib/searchlogic/search/base.rb +26 -0
  22. data/lib/searchlogic/search/conditions.rb +58 -0
  23. data/lib/searchlogic/search/date_parts.rb +23 -0
  24. data/lib/searchlogic/search/implementation.rb +14 -0
  25. data/lib/searchlogic/search/method_missing.rb +123 -0
  26. data/lib/searchlogic/search/ordering.rb +10 -0
  27. data/lib/searchlogic/search/scopes.rb +19 -0
  28. data/lib/searchlogic/search/to_yaml.rb +31 -0
  29. data/lib/searchlogic/search/unknown_condition_error.rb +15 -0
  30. data/rails/init.rb +1 -0
  31. data/searchlogic.gemspec +98 -0
  32. data/spec/searchlogic/active_record/association_proxy_spec.rb +23 -0
  33. data/spec/searchlogic/active_record/consistency_spec.rb +28 -0
  34. data/spec/searchlogic/core_ext/object_spec.rb +9 -0
  35. data/spec/searchlogic/core_ext/proc_spec.rb +8 -0
  36. data/spec/searchlogic/named_scopes/alias_scope_spec.rb +23 -0
  37. data/spec/searchlogic/named_scopes/association_conditions_spec.rb +203 -0
  38. data/spec/searchlogic/named_scopes/association_ordering_spec.rb +27 -0
  39. data/spec/searchlogic/named_scopes/conditions_spec.rb +319 -0
  40. data/spec/searchlogic/named_scopes/or_conditions_spec.rb +66 -0
  41. data/spec/searchlogic/named_scopes/ordering_spec.rb +34 -0
  42. data/spec/searchlogic/search_spec.rb +497 -0
  43. data/spec/spec_helper.rb +132 -0
  44. metadata +136 -0
@@ -0,0 +1,26 @@
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
+ include Base
19
+ include Conditions
20
+ include DateParts
21
+ include MethodMissing
22
+ include Scopes
23
+ include Ordering
24
+ include ToYaml
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Searchlogic
2
+ class Search
3
+ module Base
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ attr_accessor :klass, :current_scope
7
+ undef :id if respond_to?(:id)
8
+ end
9
+ end
10
+
11
+ # Creates a new search object for the given class. Ex:
12
+ #
13
+ # Searchlogic::Search.new(User, {}, {:username_like => "bjohnson"})
14
+ def initialize(klass, current_scope, conditions = {})
15
+ self.klass = klass
16
+ self.current_scope = current_scope
17
+ @conditions ||= {}
18
+ self.conditions = conditions if conditions.is_a?(Hash)
19
+ end
20
+
21
+ def clone
22
+ self.class.new(klass, current_scope && current_scope.clone, conditions.clone)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,58 @@
1
+ module Searchlogic
2
+ class Search
3
+ module Conditions
4
+ # Returns a hash of the current conditions set.
5
+ def conditions
6
+ mass_conditions.clone.merge(@conditions)
7
+ end
8
+
9
+ def compact_conditions
10
+ conditions.select { |k,v| !v.blank? }
11
+ end
12
+
13
+ # Accepts a hash of conditions.
14
+ def conditions=(values)
15
+ values.each do |condition, value|
16
+ mass_conditions[condition.to_sym] = value
17
+ value.delete_if { |v| ignore_value?(v) } if value.is_a?(Array)
18
+ next if ignore_value?(value)
19
+ send("#{condition}=", value)
20
+ end
21
+ end
22
+
23
+ # Delete a condition from the search. Since conditions map to named scopes,
24
+ # if a named scope accepts a parameter there is no way to actually delete
25
+ # the scope if you do not want it anymore. A nil value might be meaningful
26
+ # to that scope.
27
+ def delete(*names)
28
+ names.each do |name|
29
+ @conditions.delete(name.to_sym)
30
+ mass_conditions.delete(name)
31
+ end
32
+ self
33
+ end
34
+
35
+ private
36
+ # This is here as a hook to allow people to modify the order in which the conditions are called, for whatever reason.
37
+ def conditions_array
38
+ @conditions.to_a
39
+ end
40
+
41
+ def write_condition(name, value)
42
+ @conditions[name] = value
43
+ end
44
+
45
+ def read_condition(name)
46
+ @conditions[name]
47
+ end
48
+
49
+ def mass_conditions
50
+ @mass_conditions ||= {}
51
+ end
52
+
53
+ def ignore_value?(value)
54
+ (value.is_a?(String) && value.blank?) || (value.is_a?(Array) && value.empty?)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ module Searchlogic
2
+ class Search
3
+ module DateParts
4
+ def conditions=(values)
5
+ values.clone.each do |condition, value|
6
+ # if a condition name ends with "(1i)", assume it's date / datetime
7
+ if condition =~ /(.*)\(1i\)$/
8
+ date_scope_name = $1
9
+ date_parts = (1..6).to_a.map do |idx|
10
+ values.delete("#{ date_scope_name }(#{ idx }i)")
11
+ end.reject{|s| s.blank? }.map{|s| s.to_i }
12
+
13
+ # did we get enough info to build a time?
14
+ if date_parts.length >= 3
15
+ values[date_scope_name] = Time.zone.local(*date_parts)
16
+ end
17
+ end
18
+ end
19
+ super
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module Searchlogic
2
+ class Search
3
+ # Responsible for adding a "search" method into your models.
4
+ module Implementation
5
+ # Additional method, gets aliased as "search" if that method
6
+ # is available. A lot of other libraries like to use "search"
7
+ # as well, so if you have a conflict like this, you can use
8
+ # this method directly.
9
+ def searchlogic(conditions = {})
10
+ Search.new(self, scope(:find), conditions)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,123 @@
1
+ module Searchlogic
2
+ class Search
3
+ module MethodMissing
4
+ def respond_to?(*args)
5
+ super || scope?(normalize_scope_name(args.first))
6
+ rescue Searchlogic::NamedScopes::OrConditions::UnknownConditionError
7
+ false
8
+ end
9
+
10
+ private
11
+ def method_missing(name, *args, &block)
12
+ condition_name = condition_name(name)
13
+ scope_name = scope_name(condition_name)
14
+
15
+ if setter?(name)
16
+ if scope?(scope_name)
17
+ if args.size == 1
18
+ write_condition(
19
+ condition_name,
20
+ type_cast(
21
+ args.first,
22
+ cast_type(scope_name),
23
+ scope_options(scope_name).respond_to?(:searchlogic_options) ? scope_options(scope_name).searchlogic_options : {}
24
+ )
25
+ )
26
+ else
27
+ write_condition(condition_name, args)
28
+ end
29
+ else
30
+ raise UnknownConditionError.new(condition_name)
31
+ end
32
+ elsif scope?(scope_name) && args.size <= 1
33
+ if args.size == 0
34
+ read_condition(condition_name)
35
+ else
36
+ send("#{condition_name}=", *args)
37
+ self
38
+ end
39
+ else
40
+ scope = conditions_array.inject(klass.scoped(current_scope) || {}) do |scope, condition|
41
+ scope_name, value = condition
42
+ scope_name = normalize_scope_name(scope_name)
43
+ klass.send(scope_name, value) if !klass.respond_to?(scope_name)
44
+ arity = klass.named_scope_arity(scope_name)
45
+
46
+ if !arity || arity == 0
47
+ if value == true
48
+ scope.send(scope_name)
49
+ else
50
+ scope
51
+ end
52
+ elsif arity == -1
53
+ scope.send(scope_name, *(value.is_a?(Array) ? value : [value]))
54
+ else
55
+ scope.send(scope_name, value)
56
+ end
57
+ end
58
+ scope.send(name, *args, &block)
59
+ end
60
+ end
61
+
62
+ def normalize_scope_name(scope_name)
63
+ case
64
+ when klass.scopes.key?(scope_name.to_sym) then scope_name.to_sym
65
+ when klass.column_names.include?(scope_name.to_s) then "#{scope_name}_equals".to_sym
66
+ else scope_name.to_sym
67
+ end
68
+ end
69
+
70
+ def setter?(name)
71
+ !(name.to_s =~ /=$/).nil?
72
+ end
73
+
74
+ def condition_name(name)
75
+ condition = name.to_s.match(/(\w+)=?$/)
76
+ condition ? condition[1].to_sym : nil
77
+ end
78
+
79
+ def cast_type(name)
80
+ named_scope_options = scope_options(name)
81
+ arity = klass.named_scope_arity(name)
82
+ if !arity || arity == 0
83
+ :boolean
84
+ else
85
+ named_scope_options.respond_to?(:searchlogic_options) ? named_scope_options.searchlogic_options[:type] : :string
86
+ end
87
+ end
88
+
89
+ def type_cast(value, type, options = {})
90
+ case value
91
+ when Array
92
+ value.collect { |v| type_cast(v, type) }
93
+ when Range
94
+ Range.new(type_cast(value.first, type), type_cast(value.last, type))
95
+ else
96
+ # Let's leverage ActiveRecord's type casting, so that casting is consistent
97
+ # with the other models.
98
+ column_for_type_cast = ::ActiveRecord::ConnectionAdapters::Column.new("", nil)
99
+ column_for_type_cast.instance_variable_set(:@type, type)
100
+ casted_value = column_for_type_cast.type_cast(value)
101
+
102
+ if Time.zone && casted_value.is_a?(Time)
103
+ if value.is_a?(String)
104
+ if options[:skip_conversion]
105
+ casted_value.utc
106
+ else
107
+ (casted_value + (Time.zone.utc_offset * -1)).in_time_zone
108
+ end
109
+ else
110
+ if options[:skip_conversion]
111
+ casted_value.utc
112
+ else
113
+ casted_value.in_time_zone
114
+ end
115
+ end
116
+ else
117
+ casted_value
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,10 @@
1
+ module Searchlogic
2
+ class Search
3
+ module Ordering
4
+ # Returns the column we are currently ordering by
5
+ def ordering_by
6
+ order && order.to_s.gsub(/^(ascend|descend)_by_/, '')
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module Searchlogic
2
+ class Search
3
+ module Scopes
4
+ private
5
+ def scope_name(condition_name)
6
+ condition_name && normalize_scope_name(condition_name)
7
+ end
8
+
9
+ def scope?(scope_name)
10
+ klass.scopes.key?(scope_name) || klass.condition?(scope_name)
11
+ end
12
+
13
+ def scope_options(name)
14
+ 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
15
+ klass.named_scope_options(name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ module Searchlogic
2
+ class Search
3
+ module ToYaml
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ yaml_as "tag:ruby.yaml.org,2002:class"
7
+ include InstanceMethods
8
+ end
9
+ end
10
+
11
+ module InstanceMethods
12
+ def to_yaml( opts = {} )
13
+ YAML::quick_emit( self, opts ) do |out|
14
+ out.map("tag:ruby.yaml.org,2002:object:Searchlogic::Search") do |map|
15
+ map.add('class_name', klass.name)
16
+ map.add('current_scope', current_scope)
17
+ map.add('conditions', conditions)
18
+ end
19
+ end
20
+ end
21
+
22
+ def yaml_initialize(taguri, attributes = {})
23
+ self.klass = attributes["class_name"].constantize
24
+ self.current_scope = attributes["current_scope"]
25
+ @conditions ||= {}
26
+ self.conditions = attributes["conditions"]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module Searchlogic
2
+ class Search
3
+ # Is an invalid condition is used this error will be raised. Ex:
4
+ #
5
+ # User.search(:unkown => true)
6
+ #
7
+ # Where unknown is not a valid named scope for the User model.
8
+ class UnknownConditionError < StandardError
9
+ def initialize(condition)
10
+ msg = "The #{condition} is not a valid condition. You may only use conditions that map to a named scope"
11
+ super(msg)
12
+ end
13
+ end
14
+ end
15
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "searchlogic"
@@ -0,0 +1,98 @@
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{obitum-searchlogic}
8
+ s.version = "2.4.28"
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{2010-10-05}
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
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION.yml",
25
+ "init.rb",
26
+ "lib/searchlogic.rb",
27
+ "lib/searchlogic/active_record/association_proxy.rb",
28
+ "lib/searchlogic/active_record/consistency.rb",
29
+ "lib/searchlogic/active_record/named_scope_tools.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
+ "lib/searchlogic/search/base.rb",
41
+ "lib/searchlogic/search/conditions.rb",
42
+ "lib/searchlogic/search/date_parts.rb",
43
+ "lib/searchlogic/search/implementation.rb",
44
+ "lib/searchlogic/search/method_missing.rb",
45
+ "lib/searchlogic/search/ordering.rb",
46
+ "lib/searchlogic/search/scopes.rb",
47
+ "lib/searchlogic/search/to_yaml.rb",
48
+ "lib/searchlogic/search/unknown_condition_error.rb",
49
+ "rails/init.rb",
50
+ "searchlogic.gemspec",
51
+ "spec/searchlogic/active_record/association_proxy_spec.rb",
52
+ "spec/searchlogic/active_record/consistency_spec.rb",
53
+ "spec/searchlogic/core_ext/object_spec.rb",
54
+ "spec/searchlogic/core_ext/proc_spec.rb",
55
+ "spec/searchlogic/named_scopes/alias_scope_spec.rb",
56
+ "spec/searchlogic/named_scopes/association_conditions_spec.rb",
57
+ "spec/searchlogic/named_scopes/association_ordering_spec.rb",
58
+ "spec/searchlogic/named_scopes/conditions_spec.rb",
59
+ "spec/searchlogic/named_scopes/or_conditions_spec.rb",
60
+ "spec/searchlogic/named_scopes/ordering_spec.rb",
61
+ "spec/searchlogic/search_spec.rb",
62
+ "spec/spec_helper.rb"
63
+ ]
64
+ s.homepage = %q{http://github.com/obitum/searchlogic}
65
+ s.rdoc_options = ["--charset=UTF-8"]
66
+ s.require_paths = ["lib"]
67
+ s.rubyforge_project = %q{searchlogic}
68
+ s.rubygems_version = %q{1.3.7}
69
+ s.summary = %q{Searchlogic makes using ActiveRecord named scopes easier and less repetitive.}
70
+ s.test_files = [
71
+ "spec/searchlogic/active_record/association_proxy_spec.rb",
72
+ "spec/searchlogic/active_record/consistency_spec.rb",
73
+ "spec/searchlogic/core_ext/object_spec.rb",
74
+ "spec/searchlogic/core_ext/proc_spec.rb",
75
+ "spec/searchlogic/named_scopes/alias_scope_spec.rb",
76
+ "spec/searchlogic/named_scopes/association_conditions_spec.rb",
77
+ "spec/searchlogic/named_scopes/association_ordering_spec.rb",
78
+ "spec/searchlogic/named_scopes/conditions_spec.rb",
79
+ "spec/searchlogic/named_scopes/or_conditions_spec.rb",
80
+ "spec/searchlogic/named_scopes/ordering_spec.rb",
81
+ "spec/searchlogic/search_spec.rb",
82
+ "spec/spec_helper.rb"
83
+ ]
84
+
85
+ if s.respond_to? :specification_version then
86
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
87
+ s.specification_version = 3
88
+
89
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
90
+ s.add_runtime_dependency(%q<activerecord>, [">= 2.0.0"])
91
+ else
92
+ s.add_dependency(%q<activerecord>, [">= 2.0.0"])
93
+ end
94
+ else
95
+ s.add_dependency(%q<activerecord>, [">= 2.0.0"])
96
+ end
97
+ end
98
+