searchable_record 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +11 -0
- data/Manifest +11 -0
- data/README.rdoc +164 -0
- data/Rakefile +25 -21
- data/lib/searchable_record/core.rb +270 -0
- data/lib/{util.rb → searchable_record/util.rb} +1 -1
- data/lib/searchable_record/version.rb +11 -0
- data/lib/searchable_record.rb +5 -263
- data/searchable_record.gemspec +37 -0
- data/spec/searchable_record_spec.rb +115 -115
- data/spec/searchable_record_spec_helper.rb +3 -3
- data/spec/util_spec.rb +12 -12
- metadata +27 -19
- data/History.txt +0 -7
- data/MIT-LICENSE.txt +0 -19
- data/Manifest.txt +0 -10
- data/README.txt +0 -128
data/CHANGELOG.rdoc
ADDED
data/Manifest
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
CHANGELOG.rdoc
|
2
|
+
lib/searchable_record/core.rb
|
3
|
+
lib/searchable_record/util.rb
|
4
|
+
lib/searchable_record/version.rb
|
5
|
+
lib/searchable_record.rb
|
6
|
+
Manifest
|
7
|
+
Rakefile
|
8
|
+
README.rdoc
|
9
|
+
spec/searchable_record_spec.rb
|
10
|
+
spec/searchable_record_spec_helper.rb
|
11
|
+
spec/util_spec.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
= SearchableRecord
|
2
|
+
|
3
|
+
SearchableRecord is a small Ruby on Rails plugin that makes the parsing of
|
4
|
+
query parameters from URLs easy for resources, allowing the requester to
|
5
|
+
control the items (records) shown in the resource's representation.
|
6
|
+
|
7
|
+
The implementation is a helper module (a mixin) for ActiveRecord models. It
|
8
|
+
is used by including SearchableRecord module in a model.
|
9
|
+
|
10
|
+
The mixin provides a class method, <tt>SearchableRecord#find_queried</tt>,
|
11
|
+
to the class that includes it. The method is a front-end to
|
12
|
+
<tt>ActiveRecord::Base#find</tt>: it parses query parameters against the
|
13
|
+
given rules and calls <tt>find</tt> accordingly, returning the results of
|
14
|
+
<tt>find</tt>.
|
15
|
+
|
16
|
+
== A usage example
|
17
|
+
|
18
|
+
The following example, although a bit contrived, allows the client to
|
19
|
+
|
20
|
+
* limit the number of items as the result of the search
|
21
|
+
(<tt>limit</tt> parameter),
|
22
|
+
* set an offset for the items (<tt>offset</tt> parameter, intended to be
|
23
|
+
used together with <tt>limit</tt>),
|
24
|
+
* sort the items either in ascending (<tt>sort</tt> parameter) or
|
25
|
+
descending (<tt>rsort</tt> parameter) order by items' type and name,
|
26
|
+
* to limit the result by matching only items that were update before
|
27
|
+
(<tt>until</tt> parameter) or after (<tt>since</tt> parameter) a certain
|
28
|
+
date, and
|
29
|
+
* to limit the result by matching only items with certain kind of
|
30
|
+
types (<tt>type</tt> parameter) or names (<tt>name</tt> parameter), or
|
31
|
+
both (for a name, a conversion to the client supplied parameter must be
|
32
|
+
applied before matching the name in the database).
|
33
|
+
|
34
|
+
First, we need resource items. Let us presume the application allows its
|
35
|
+
clients to query <tt>Item</tt> type of resources:
|
36
|
+
|
37
|
+
class Item < ActiveRecord::Base
|
38
|
+
include SearchableRecord
|
39
|
+
end
|
40
|
+
|
41
|
+
By including SearchableRecord module to Item, the method
|
42
|
+
<tt>find_queried</tt> becomes available. The method can be called, for
|
43
|
+
example, in <tt>ItemController</tt> to parse the client's query parameters:
|
44
|
+
|
45
|
+
Item.find_queried(:all, query_params, rules, options)
|
46
|
+
|
47
|
+
In the beginning of this example, we stated requirements what the clients
|
48
|
+
are allowed to query. These requirements are expressed as the following
|
49
|
+
rules:
|
50
|
+
|
51
|
+
rules = {
|
52
|
+
:limit => nil, # key as a flag; the value for the key is not used
|
53
|
+
:offset => nil, # key as a flag
|
54
|
+
:sort => { "name" => "items.name", "created" => "items.created_at" },
|
55
|
+
:rsort => nil, # rsort is allowed according to rules in :sort (key as a flag)
|
56
|
+
:since => "items.created_at", # cast parameter value as the default type
|
57
|
+
:until => "items.created_at", # cast parameter value as the default type
|
58
|
+
:patterns => { :type => "items.type", # match the pattern with the default operator and converter
|
59
|
+
:name => { :column => "items.name",
|
60
|
+
:converter => lambda { |val| "%#{val.gsub('_', '.')}%" } } }
|
61
|
+
# match the pattern with the default operator
|
62
|
+
}
|
63
|
+
|
64
|
+
These rules are fed to <tt>find_queried</tt> as the third argument.
|
65
|
+
|
66
|
+
In addition, the application may to require options to be passed to
|
67
|
+
<tt>find</tt>:
|
68
|
+
|
69
|
+
options = {
|
70
|
+
:include => [ :owners ],
|
71
|
+
:conditions => "items.flag = 'f'"
|
72
|
+
}
|
73
|
+
|
74
|
+
These can be supplied to <tt>find_queried</tt> as the fourth argument.
|
75
|
+
|
76
|
+
The second argument to <tt>find_queried</tt> is the query parameters
|
77
|
+
<tt>ItemController</tt> receives. For example, the client uses the URL
|
78
|
+
<tt>http://example-site.org/items?limit=5&offset=4&rsort=name&since=2008-02-28&name=foo_bar</tt>
|
79
|
+
to fetch a representation of the application's resource containing the
|
80
|
+
items. The action results to the following parameters:
|
81
|
+
|
82
|
+
query_params = params
|
83
|
+
|
84
|
+
# => query_params = {
|
85
|
+
# 'offset' => '4',
|
86
|
+
# 'limit' => '5',
|
87
|
+
# 'rsort' => 'name',
|
88
|
+
# 'until' => '2008-02-28',
|
89
|
+
# 'name' => 'foo_bar',
|
90
|
+
# ...
|
91
|
+
# # plus Rails-specific parameters, such as 'action' and 'controller'
|
92
|
+
# }
|
93
|
+
|
94
|
+
With these query parameters and arguments, <tt>find_queried</tt> calls
|
95
|
+
<tt>find</tt> with the following arguments:
|
96
|
+
|
97
|
+
Item.find(:all,
|
98
|
+
:include => [ :owners ],
|
99
|
+
:order => "items.name desc",
|
100
|
+
:offset => 4,
|
101
|
+
:limit => 5,
|
102
|
+
:conditions => [ "(items.flag = 'f') and (items.created_at <= cast(:until as datetime)) and (items.name like :name)",
|
103
|
+
{ :until => "2008-02-28", :name => "%foo.bar%" } ])
|
104
|
+
|
105
|
+
This particular search results to at most 5 items that are
|
106
|
+
|
107
|
+
* from offset 4 (that is, items from positions 5 to 9),
|
108
|
+
* sorted in descending order by items' names,
|
109
|
+
* updated since 2008-02-28, and
|
110
|
+
* have <tt>foo.bar</tt> in their name.
|
111
|
+
|
112
|
+
See <tt>find_queried</tt> method in SearchableRecord::ClassMethods for
|
113
|
+
details.
|
114
|
+
|
115
|
+
== Installation
|
116
|
+
|
117
|
+
In order to install the plugin as a Ruby gem for a Rails application, edit
|
118
|
+
the <tt>environment.rb</tt> file of the application to contain the following
|
119
|
+
line:
|
120
|
+
|
121
|
+
config.gem "searchable_record"
|
122
|
+
|
123
|
+
(This requires Rails version 2.1 or above.)
|
124
|
+
|
125
|
+
Then install the gem, either using the Rakefile of the Rails application:
|
126
|
+
|
127
|
+
$ rake gems:install
|
128
|
+
|
129
|
+
...or with the <tt>gem</tt> tool:
|
130
|
+
|
131
|
+
$ gem install searchable_record
|
132
|
+
|
133
|
+
Use git to get the source code for modifications and hacks:
|
134
|
+
|
135
|
+
$ git clone git://gitorious.org/searchable-rec/mainline.git
|
136
|
+
|
137
|
+
== Contacting
|
138
|
+
|
139
|
+
Please send feedback by email to Tuomas Kareinen < tkareine (at) gmail (dot)
|
140
|
+
com >.
|
141
|
+
|
142
|
+
== Legal notes
|
143
|
+
|
144
|
+
This software is licensed under the terms of the "MIT license":
|
145
|
+
|
146
|
+
Copyright (c) 2008-2009 Tuomas Kareinen
|
147
|
+
|
148
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
149
|
+
of this software and associated documentation files (the "Software"), to
|
150
|
+
deal in the Software without restriction, including without limitation the
|
151
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
152
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
153
|
+
furnished to do so, subject to the following conditions:
|
154
|
+
|
155
|
+
The above copyright notice and this permission notice shall be included in
|
156
|
+
all copies or substantial portions of the Software.
|
157
|
+
|
158
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
159
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
160
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
161
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
162
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
163
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
164
|
+
IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,35 +1,39 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "rubygems"
|
2
|
+
require "spec/rake/spectask"
|
3
|
+
require File.dirname(__FILE__) << "/lib/searchable_record/version"
|
4
|
+
require "echoe"
|
4
5
|
|
5
|
-
|
6
|
+
task :default => :spec
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
Hoe.new('searchable_record', SearchableRecord::Meta::VERSION.to_s) do |p|
|
10
|
-
p.name = "searchable_record"
|
11
|
-
p.rubyforge_name = 'searchable-rec' # If different than lowercase project name
|
8
|
+
Echoe.new("searchable_record") do |p|
|
12
9
|
p.author = "Tuomas Kareinen"
|
13
|
-
p.email =
|
14
|
-
p.
|
15
|
-
|
16
|
-
control the items (records) shown in the resource's representation."
|
17
|
-
p.description = p.paragraphs_of('README.txt', 1..3).join("\n\n")
|
10
|
+
p.email = "tkareine@gmail.com"
|
11
|
+
p.project = "searchable-rec" # If different than the project name in lowercase.
|
12
|
+
p.version = SearchableRecord::Version.to_s
|
18
13
|
p.url = "http://searchable-rec.rubyforge.org"
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
p.summary =<<-END
|
15
|
+
SearchableRecord is a small Ruby on Rails plugin that makes the parsing of
|
16
|
+
query parameters from URLs easy for resources, allowing the requester to
|
17
|
+
control the items (records) shown in the resource's representation.
|
18
|
+
END
|
19
|
+
p.runtime_dependencies = %w(activesupport)
|
20
|
+
p.ignore_pattern = "release-script.txt"
|
21
|
+
p.rdoc_pattern = ["*.rdoc", "lib/**/*.rb"]
|
23
22
|
end
|
24
23
|
|
25
24
|
desc "Run specs."
|
26
|
-
Spec::Rake::SpecTask.new(
|
27
|
-
t.spec_files = FileList[
|
25
|
+
Spec::Rake::SpecTask.new("spec") do |t|
|
26
|
+
t.spec_files = FileList["spec/**/*.rb"]
|
28
27
|
t.spec_opts = ["--format", "specdoc"]
|
29
28
|
#t.warning = true
|
30
29
|
end
|
31
30
|
|
31
|
+
desc "Find code smells."
|
32
|
+
task :roodi do
|
33
|
+
sh("roodi '**/*.rb'")
|
34
|
+
end
|
35
|
+
|
32
36
|
desc "Search unfinished parts of source code."
|
33
37
|
task :todo do
|
34
|
-
FileList[
|
38
|
+
FileList["**/*.rb"].egrep /#.*(TODO|FIXME)/
|
35
39
|
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
# See SearchableRecord::ClassMethods#find_queried for usage documentation.
|
2
|
+
module SearchableRecord
|
3
|
+
def self.included(base_class) #:nodoc:
|
4
|
+
base_class.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
|
7
|
+
@@searchable_record_settings = {
|
8
|
+
:cast_since_as => "datetime",
|
9
|
+
:cast_until_as => "datetime",
|
10
|
+
:pattern_operator => "like",
|
11
|
+
:pattern_converter => lambda { |val| "%#{val}%" }
|
12
|
+
}
|
13
|
+
|
14
|
+
cattr_accessor :searchable_record_settings
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# === Description
|
22
|
+
#
|
23
|
+
# Parses the query parameters the client has given in the URL of the
|
24
|
+
# HTTP request. With the query parameters, the client may set a limit,
|
25
|
+
# an offset, or an ordering for the items in the search result. In
|
26
|
+
# addition, the client may limit the output by allowing only certain
|
27
|
+
# records that match to specific patterns.
|
28
|
+
#
|
29
|
+
# What the client user is allowed to query is defined by specific rules
|
30
|
+
# passed to the method as a Hash argument. Query parameters that are not
|
31
|
+
# explicitly stated in the rules are silently discarded.
|
32
|
+
#
|
33
|
+
# Essentially, the method is a frontend for
|
34
|
+
# <tt>ActiveRecord::Base#find</tt>. The method
|
35
|
+
#
|
36
|
+
# 1. parses the query parameters the client has given in the URL for
|
37
|
+
# the HTTP request (+query_params+) against the rules (+rules+), and
|
38
|
+
# 2. calls <tt>find</tt> with the parsed options.
|
39
|
+
#
|
40
|
+
# ==== Parsing rules
|
41
|
+
#
|
42
|
+
# The parsing rules must be given as a Hash. The recognized keys are the
|
43
|
+
# following:
|
44
|
+
#
|
45
|
+
# * <tt>:limit</tt>, allowing limiting the number of matching items
|
46
|
+
# (the same effect as with <tt>find</tt>). The value for the key is
|
47
|
+
# irrelevant; use +nil+. The rule enables query parameter "limit"
|
48
|
+
# that accepts an integer value.
|
49
|
+
# * <tt>:offset</tt>, allowing skipping matching items (the same effect
|
50
|
+
# as with <tt>find</tt>). The value for the key is irrelevant; use
|
51
|
+
# +nil+. The rule enables query parameter "offset" that accepts an
|
52
|
+
# integer value.
|
53
|
+
# * <tt>:sort</tt>, which determines the ordering of matching items
|
54
|
+
# (the same effect as with the <tt>:order</tt> option of
|
55
|
+
# <tt>find</tt>). The value is a Hash of
|
56
|
+
# <tt>"<parameter_value>" => "<table>.<column>"</tt> pairs. The rule
|
57
|
+
# enables query parameter "sort" that accepts keys from the Hash as
|
58
|
+
# its legal values.
|
59
|
+
# * <tt>:rsort</tt>, for reverse sort. Uses the rules of
|
60
|
+
# <tt>:sort</tt>; thus, use +nil+ as the value if you want to enable
|
61
|
+
# "rsort" query parameter.
|
62
|
+
# * <tt>:since</tt>, which sets a lower timedate limit. The value is
|
63
|
+
# either a string naming the database table column that has
|
64
|
+
# timestamps (using the type from default settings'
|
65
|
+
# <tt>:cast_since_as</tt> entry) or a Hash that contains entries like
|
66
|
+
# <tt>:column => "<table>.<column>"</tt> and
|
67
|
+
# <tt>:cast_as => "<sql_timedate_type>"</tt>. The rule enables query
|
68
|
+
# parameter "since" that accepts timedate values.
|
69
|
+
# * <tt>:until</tt>, which sets an upper timedate limit. It is used
|
70
|
+
# like <tt>:since</tt>.
|
71
|
+
# * <tt>:patterns</tt>, where the value is a Hash containing patterns.
|
72
|
+
# The keys in the Hash correspond to additional query parameters,
|
73
|
+
# while the corresponding values to the keys correspond to database
|
74
|
+
# table columns. For each pattern, the value is either directly a
|
75
|
+
# string, or a Hash containing an entry like
|
76
|
+
# <tt>:column => "<table>.<column>"</tt>. A pattern's Hash may
|
77
|
+
# contain two optional entries in addition to <tt>:column</tt>:
|
78
|
+
# <tt>:converter => lambda { |val| <conversion_operation_for_val> }</tt>
|
79
|
+
# and <tt>:operator => "<sql_pattern_operator>"</tt>.
|
80
|
+
# - <tt>:converter</tt> expects a block that modifies the input
|
81
|
+
# value; if the key is not given, the converter specified in
|
82
|
+
# <tt>:pattern_converter</tt> in the default settings is used
|
83
|
+
# instead.
|
84
|
+
# - <tt>:operator</tt> specifies a custom match operator for the
|
85
|
+
# pattern; if the key is not given, the operator specified in
|
86
|
+
# <tt>:pattern_operator</tt> in the default settings is used
|
87
|
+
# instead.
|
88
|
+
#
|
89
|
+
# If both +sort+ and +rsort+ parameters are given in the URL and both
|
90
|
+
# are allowed query parameter by the rules, +sort+ is favored over
|
91
|
+
# +rsort+. Unlike with +sort+ and +rsort+ rules (+rsort+ uses the rules
|
92
|
+
# of +sort+), the rules for +since+ and +until+ are independent from
|
93
|
+
# each other.
|
94
|
+
#
|
95
|
+
# For usage examples, see the example in README.txt and the specs that
|
96
|
+
# come with the plugin.
|
97
|
+
#
|
98
|
+
# ==== Default settings for rules
|
99
|
+
#
|
100
|
+
# The default settings for the rules are accessible and modifiable by
|
101
|
+
# calling the method +searchable_record_settings+. The settings are
|
102
|
+
# stored as a Hash; the following keys are recognized:
|
103
|
+
#
|
104
|
+
# * <tt>:cast_since_as</tt>,
|
105
|
+
# * <tt>:cast_until_as</tt>,
|
106
|
+
# * <tt>:pattern_operator</tt>, and
|
107
|
+
# * <tt>:pattern_converter</tt>.
|
108
|
+
#
|
109
|
+
# See the parsing rules above how the default settings are used.
|
110
|
+
#
|
111
|
+
# === Arguments
|
112
|
+
#
|
113
|
+
# +extend+:: The same as the first argument to <tt>find</tt> (such as <tt>:all</tt>).
|
114
|
+
# +query_params+:: The (unsafe) query parameters from the URL as a Hash.
|
115
|
+
# +rules+:: The parsing rules as a Hash.
|
116
|
+
# +options+:: Additional options for <tt>find</tt>, such as <tt>:include => [ :an_association ]</tt>.
|
117
|
+
#
|
118
|
+
# === Return
|
119
|
+
#
|
120
|
+
# The same as with <tt>ActiveRecord::Base#find</tt>.
|
121
|
+
def find_queried(extend, query_params, rules, options = { })
|
122
|
+
# Ensure the proper types of arguments.
|
123
|
+
query_params = query_params.to_hash
|
124
|
+
rules = rules.to_hash
|
125
|
+
options = options.to_hash
|
126
|
+
|
127
|
+
query_params = preserve_allowed_query_params(query_params, rules)
|
128
|
+
|
129
|
+
unless query_params.empty?
|
130
|
+
parse_offset(options, query_params)
|
131
|
+
parse_limit(options, query_params)
|
132
|
+
parse_order(options, query_params, rules)
|
133
|
+
parse_conditional_rules(options, query_params, rules)
|
134
|
+
end
|
135
|
+
|
136
|
+
logger.debug("find_queried: query_params=<<#{query_params.inspect}>>, resulted options=<<#{options.inspect}>>")
|
137
|
+
|
138
|
+
self.find(extend, options)
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def preserve_allowed_query_params(query_params, rules)
|
144
|
+
allowed_keys = rules.keys
|
145
|
+
|
146
|
+
# Add pattern matching parameters to the list of allowed keys.
|
147
|
+
allowed_keys.delete(:patterns)
|
148
|
+
if rules[:patterns]
|
149
|
+
allowed_keys += rules[:patterns].keys
|
150
|
+
end
|
151
|
+
|
152
|
+
# Do not affect the passed query parameters.
|
153
|
+
Util.pruned_dup(query_params, allowed_keys)
|
154
|
+
end
|
155
|
+
|
156
|
+
def parse_offset(options, query_params)
|
157
|
+
if query_params[:offset]
|
158
|
+
value = Util.parse_positive_int(query_params[:offset])
|
159
|
+
options[:offset] = value unless value.nil?
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def parse_limit(options, query_params)
|
164
|
+
if query_params[:limit]
|
165
|
+
value = Util.parse_positive_int(query_params[:limit])
|
166
|
+
options[:limit] = value unless value.nil?
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def parse_order(options, query_params, rules)
|
171
|
+
# Sort is favored over rsort.
|
172
|
+
|
173
|
+
if query_params[:rsort]
|
174
|
+
raise ArgumentError, "No sort rule specified." if rules[:sort].nil?
|
175
|
+
|
176
|
+
sort_by = rules[:sort][query_params[:rsort]]
|
177
|
+
options[:order] = "#{sort_by} desc" unless sort_by.nil?
|
178
|
+
end
|
179
|
+
|
180
|
+
if query_params[:sort]
|
181
|
+
sort_by = rules[:sort][query_params[:sort]]
|
182
|
+
options[:order] = sort_by unless sort_by.nil?
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def parse_conditional_rules(options, query_params, rules)
|
187
|
+
cond_strs = [ ]
|
188
|
+
cond_syms = { }
|
189
|
+
|
190
|
+
# The hash query_params is not empty, therefore, it contains at least
|
191
|
+
# some of the allowed query parameters (as Symbols) below. Those
|
192
|
+
# parameters that are not identified are ignored silently.
|
193
|
+
|
194
|
+
parse_since_and_until(cond_strs, cond_syms, query_params, rules)
|
195
|
+
parse_patterns(cond_strs, cond_syms, query_params, rules)
|
196
|
+
|
197
|
+
construct_conditions(options, cond_strs, cond_syms) unless cond_strs.empty?
|
198
|
+
end
|
199
|
+
|
200
|
+
def parse_since_and_until(cond_strs, cond_syms, query_params, rules)
|
201
|
+
if query_params[:since]
|
202
|
+
parse_datetime(cond_strs, cond_syms, query_params, rules, :since)
|
203
|
+
end
|
204
|
+
|
205
|
+
if query_params[:until]
|
206
|
+
parse_datetime(cond_strs, cond_syms, query_params, rules, :until)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def parse_datetime(cond_strs, cond_syms, query_params, rules, type)
|
211
|
+
rule = rules[type]
|
212
|
+
cast_type = searchable_record_settings["cast_#{type}_as".to_sym]
|
213
|
+
|
214
|
+
if rule.respond_to?(:to_hash)
|
215
|
+
column = rule[:column]
|
216
|
+
|
217
|
+
# Use custom cast type.
|
218
|
+
cast_type = rule[:cast_as] unless rule[:cast_as].nil?
|
219
|
+
else
|
220
|
+
column = rule
|
221
|
+
end
|
222
|
+
|
223
|
+
case type
|
224
|
+
when :since then op = ">="
|
225
|
+
when :until then op = "<="
|
226
|
+
else raise ArgumentError, "Could not determine comparison operator for datetime."
|
227
|
+
end
|
228
|
+
|
229
|
+
cond_strs << "(#{column} #{op} cast(:#{type} as #{cast_type}))"
|
230
|
+
cond_syms[type] = query_params[type]
|
231
|
+
end
|
232
|
+
|
233
|
+
def parse_patterns(cond_strs, cond_syms, query_params, rules)
|
234
|
+
if rules[:patterns]
|
235
|
+
rules[:patterns].each do |param, rule|
|
236
|
+
parse_pattern(cond_strs, cond_syms, query_params, param, rule)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def parse_pattern(cond_strs, cond_syms, query_params, param, rule)
|
242
|
+
if query_params[param]
|
243
|
+
match_op = searchable_record_settings[:pattern_operator]
|
244
|
+
conversion_blk = searchable_record_settings[:pattern_converter]
|
245
|
+
|
246
|
+
if rule.respond_to?(:to_hash)
|
247
|
+
column = rule[:column]
|
248
|
+
|
249
|
+
# Use custom pattern match operator.
|
250
|
+
match_op = rule[:operator] unless rule[:operator].nil?
|
251
|
+
|
252
|
+
# Use custom converter.
|
253
|
+
conversion_blk = rule[:converter] unless rule[:converter].nil?
|
254
|
+
else
|
255
|
+
column = rule
|
256
|
+
end
|
257
|
+
|
258
|
+
cond_strs << "(#{column} #{match_op} :#{param})"
|
259
|
+
cond_syms[param] = conversion_blk.call(query_params[param])
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def construct_conditions(options, cond_strs, cond_syms)
|
264
|
+
conditions = [ cond_strs.join(" and "), cond_syms ]
|
265
|
+
preconditions = options[:conditions]
|
266
|
+
conditions[0].insert(0, "(#{preconditions}) and ") unless preconditions.nil?
|
267
|
+
options[:conditions] = conditions
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -6,7 +6,7 @@ module SearchableRecord
|
|
6
6
|
dup_hash = { }
|
7
7
|
|
8
8
|
preserved_keys.to_a.each do |key|
|
9
|
-
if !hash[key.to_s].blank? # try to find first with
|
9
|
+
if !hash[key.to_s].blank? # try to find first with "key"; if that fails
|
10
10
|
dup_hash[key] = hash[key.to_s]
|
11
11
|
elsif !hash[key].blank? # ...then with :key
|
12
12
|
dup_hash[key] = hash[key]
|