searchable_record 0.0.2 → 0.0.3
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/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]
|