yoomee-searchlogic 2.4.27
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.
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +308 -0
- data/Rakefile +35 -0
- data/VERSION.yml +5 -0
- data/init.rb +1 -0
- data/lib/searchlogic.rb +56 -0
- data/lib/searchlogic/active_record/association_proxy.rb +19 -0
- data/lib/searchlogic/active_record/consistency.rb +49 -0
- data/lib/searchlogic/active_record/named_scope_tools.rb +101 -0
- data/lib/searchlogic/core_ext/object.rb +43 -0
- data/lib/searchlogic/core_ext/proc.rb +17 -0
- data/lib/searchlogic/named_scopes/alias_scope.rb +67 -0
- data/lib/searchlogic/named_scopes/association_conditions.rb +132 -0
- data/lib/searchlogic/named_scopes/association_ordering.rb +44 -0
- data/lib/searchlogic/named_scopes/conditions.rb +232 -0
- data/lib/searchlogic/named_scopes/or_conditions.rb +141 -0
- data/lib/searchlogic/named_scopes/ordering.rb +48 -0
- data/lib/searchlogic/rails_helpers.rb +79 -0
- data/lib/searchlogic/search.rb +26 -0
- data/lib/searchlogic/search/base.rb +26 -0
- data/lib/searchlogic/search/conditions.rb +58 -0
- data/lib/searchlogic/search/date_parts.rb +23 -0
- data/lib/searchlogic/search/implementation.rb +14 -0
- data/lib/searchlogic/search/method_missing.rb +123 -0
- data/lib/searchlogic/search/ordering.rb +10 -0
- data/lib/searchlogic/search/scopes.rb +19 -0
- data/lib/searchlogic/search/to_yaml.rb +38 -0
- data/lib/searchlogic/search/unknown_condition_error.rb +15 -0
- data/rails/init.rb +1 -0
- data/searchlogic.gemspec +98 -0
- data/spec/searchlogic/active_record/association_proxy_spec.rb +23 -0
- data/spec/searchlogic/active_record/consistency_spec.rb +28 -0
- data/spec/searchlogic/core_ext/object_spec.rb +9 -0
- data/spec/searchlogic/core_ext/proc_spec.rb +8 -0
- data/spec/searchlogic/named_scopes/alias_scope_spec.rb +23 -0
- data/spec/searchlogic/named_scopes/association_conditions_spec.rb +203 -0
- data/spec/searchlogic/named_scopes/association_ordering_spec.rb +27 -0
- data/spec/searchlogic/named_scopes/conditions_spec.rb +319 -0
- data/spec/searchlogic/named_scopes/or_conditions_spec.rb +66 -0
- data/spec/searchlogic/named_scopes/ordering_spec.rb +34 -0
- data/spec/searchlogic/search_spec.rb +497 -0
- data/spec/spec_helper.rb +132 -0
- 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,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,38 @@
|
|
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
|
+
extend ClassMethods
|
8
|
+
include InstanceMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def yaml_new(klass, tag, val)
|
14
|
+
raise "ass"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods
|
19
|
+
def to_yaml( opts = {} )
|
20
|
+
YAML::quick_emit( self, opts ) do |out|
|
21
|
+
out.map("tag:ruby.yaml.org,2002:object:Searchlogic::Search") do |map|
|
22
|
+
map.add('class_name', klass.name)
|
23
|
+
map.add('current_scope', current_scope)
|
24
|
+
map.add('conditions', conditions)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def yaml_initialize(taguri, attributes = {})
|
30
|
+
self.klass = attributes["class_name"].constantize
|
31
|
+
self.current_scope = attributes["current_scope"]
|
32
|
+
@conditions ||= {}
|
33
|
+
self.conditions = attributes["conditions"]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
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"
|
data/searchlogic.gemspec
ADDED
@@ -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{searchlogic}
|
8
|
+
s.version = "2.4.27"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Yoomee Developers, Ben Johnson of Binary Logic"]
|
12
|
+
s.date = %q{2010-09-22}
|
13
|
+
s.description = %q{Searchlogic makes using ActiveRecord named scopes easier and less repetitive.}
|
14
|
+
s.email = %q{developers@yoomee.com, 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/binarylogic/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
|
+
|