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/lib/searchable_record.rb
CHANGED
@@ -1,267 +1,9 @@
|
|
1
1
|
unless defined? ActiveSupport
|
2
|
-
require
|
3
|
-
gem
|
4
|
-
require
|
2
|
+
require "rubygems"
|
3
|
+
gem "activesupport"
|
4
|
+
require "active_support"
|
5
5
|
end
|
6
|
-
require 'util'
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
module Meta #:nodoc:
|
11
|
-
module VERSION #:nodoc:
|
12
|
-
MAJOR = 0
|
13
|
-
MINOR = 0
|
14
|
-
BUILD = 2
|
15
|
-
|
16
|
-
def self.to_s
|
17
|
-
[ MAJOR, MINOR, BUILD ].join('.')
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.included(base_class) #:nodoc:
|
23
|
-
base_class.class_eval do
|
24
|
-
extend ClassMethods
|
25
|
-
|
26
|
-
@@searchable_record_settings = {
|
27
|
-
:cast_since_as => 'datetime',
|
28
|
-
:cast_until_as => 'datetime',
|
29
|
-
:pattern_operator => 'like',
|
30
|
-
:pattern_converter => lambda { |val| "%#{val}%" }
|
31
|
-
}
|
32
|
-
|
33
|
-
cattr_accessor :searchable_record_settings
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
module ClassMethods
|
40
|
-
# === Description
|
41
|
-
#
|
42
|
-
# Parses the query parameters the client has given in the URL of the HTTP
|
43
|
-
# request. With the query parameters, the client may set a limit, an
|
44
|
-
# offset, or an ordering to the items in the search result. In addition,
|
45
|
-
# the client may limit the output by allowing only certain records that
|
46
|
-
# match to specific patterns.
|
47
|
-
#
|
48
|
-
# What the client user is allowed to query is defined by specific rules
|
49
|
-
# passed to the method as a Hash argument. Query parameters that are not
|
50
|
-
# explicitly stated in the rules are silently discarded.
|
51
|
-
#
|
52
|
-
# Essentially, the method is a frontend for
|
53
|
-
# ActiveRecord::Base#find. The method
|
54
|
-
#
|
55
|
-
# 1. parses the query parameters the client has given in the URL for the
|
56
|
-
# HTTP request (+query_params+) against the rules (+rules+), and
|
57
|
-
# 2. calls <tt>find</tt> with the parsed options.
|
58
|
-
#
|
59
|
-
# ==== Parsing rules
|
60
|
-
#
|
61
|
-
# The parsing rules must be given as a Hash; the keys in the hash indicate
|
62
|
-
# the parameters that are allowed. The recognized keys are the following:
|
63
|
-
#
|
64
|
-
# * <tt>:limit</tt>, which uses nil as the value (the same effect as with
|
65
|
-
# <tt>find</tt>).
|
66
|
-
# * <tt>:offset</tt>, which uses nil as the value (the same effect as with
|
67
|
-
# <tt>find</tt>).
|
68
|
-
# * <tt>:sort</tt>, which determines the ordering. The value is a Hash of
|
69
|
-
# <tt>'parameter value' => 'internal table column'</tt> pairs (the same
|
70
|
-
# effect as with the <tt>:order</tt> option of
|
71
|
-
# <tt>find</tt>);
|
72
|
-
# * <tt>:rsort</tt>, for reverse sort. Uses the rules of +:sort+; thus,
|
73
|
-
# use <tt>nil</tt> as the value.
|
74
|
-
# * <tt>:since</tt>, which sets a lower timedate limit. The value is
|
75
|
-
# either a string naming the database table column that has timestamps
|
76
|
-
# (using the type from default settings' <tt>:cast_since_as</tt> entry)
|
77
|
-
# or a Hash that contains entries <tt>:column => 'table.column'</tt> and
|
78
|
-
# <tt>:cast_as => '<sql_timedate_type>'</tt>.
|
79
|
-
# * <tt>:until</tt>, which sets an upper timedate limit. Used like
|
80
|
-
# <tt>:since</tt>.
|
81
|
-
# * <tt>:patterns</tt>, where the value is a Hash containing patterns. The
|
82
|
-
# keys in the Hash correspond to additional query parameters and the
|
83
|
-
# corresponding values to database table columns. For each pattern,
|
84
|
-
# the value is either directly a string, or a Hash containing the
|
85
|
-
# entry <tt>:column => 'table.column'</tt>. In addition, the Hash may
|
86
|
-
# contain optional entries
|
87
|
-
# <tt>:converter => lambda { |val| <conversion_operation_for_val> }</tt>
|
88
|
-
# and <tt>:operator => '<sql_pattern_operator>'</tt>.
|
89
|
-
# <tt>:converter</tt> expects a block that modifies the input value; if
|
90
|
-
# the key is not used, the converter specified in
|
91
|
-
# <tt>:pattern_converter</tt> in the default settings is used.
|
92
|
-
# <tt>:pattern_operator</tt> specifies a custom match operator for the
|
93
|
-
# pattern; if the key is not used, the operator specified in
|
94
|
-
# <tt>:pattern_operator</tt> in default settings is used.
|
95
|
-
#
|
96
|
-
# If both +sort+ and +rsort+ parameters are given in the URL and both are
|
97
|
-
# allowed by the rules, +sort+ is favored over +rsort+. Unlike with +sort+
|
98
|
-
# and +rsort+ rules (+rsort+ uses the rules of +sort+), the rules for
|
99
|
-
# +since+ and +until+ are independent from each other.
|
100
|
-
#
|
101
|
-
# For usage examples, see the example in README and the unit tests that
|
102
|
-
# come with the plugin.
|
103
|
-
#
|
104
|
-
# ==== Default settings for rules
|
105
|
-
#
|
106
|
-
# The default settings for the rules are accessible and modifiable by
|
107
|
-
# calling the method +searchable_record_settings+. The settings are
|
108
|
-
# stored as a Hash; the following keys are recognized:
|
109
|
-
#
|
110
|
-
# * <tt>:cast_since_as</tt>,
|
111
|
-
# * <tt>:cast_until_as</tt>,
|
112
|
-
# * <tt>:pattern_operator</tt>, and
|
113
|
-
# * <tt>:pattern_converter</tt>.
|
114
|
-
#
|
115
|
-
# See the parsing rules above how the default settings are used.
|
116
|
-
#
|
117
|
-
# === Arguments
|
118
|
-
#
|
119
|
-
# +extend+:: The same as the first argument to <tt>find</tt> (such as <tt>:all</tt>).
|
120
|
-
# +query_params+:: The (unsafe) query parameters from the URL.
|
121
|
-
# +rules+:: The parsing rules as a Hash.
|
122
|
-
# +options+:: Additional options for <tt>find</tt>, such as <tt>:include => [ :an_association ]</tt>.
|
123
|
-
#
|
124
|
-
# === Return
|
125
|
-
#
|
126
|
-
# The same as with ActiveRecord::Base#find.
|
127
|
-
def find_queried(extend, query_params, rules, options = { })
|
128
|
-
query_params = preserve_allowed_query_params(query_params, rules)
|
129
|
-
|
130
|
-
unless query_params.empty?
|
131
|
-
parse_offset(options, query_params)
|
132
|
-
parse_limit(options, query_params)
|
133
|
-
parse_order(options, query_params, rules)
|
134
|
-
parse_conditions(options, query_params, rules)
|
135
|
-
end
|
136
|
-
|
137
|
-
logger.debug("find_queried: query_params=<<#{query_params.inspect}>>, resulted options=<<#{options.inspect}>>")
|
138
|
-
|
139
|
-
self.find(extend, options)
|
140
|
-
end
|
141
|
-
|
142
|
-
private
|
143
|
-
|
144
|
-
def preserve_allowed_query_params(query_params, rules)
|
145
|
-
allowed_keys = rules.keys
|
146
|
-
|
147
|
-
# Add pattern matching parameters to the list of allowed keys.
|
148
|
-
allowed_keys.delete(:patterns)
|
149
|
-
if rules[:patterns]
|
150
|
-
allowed_keys += rules[:patterns].keys
|
151
|
-
end
|
152
|
-
|
153
|
-
# Do not affect the passed query parameters.
|
154
|
-
Util.pruned_dup(query_params, allowed_keys)
|
155
|
-
end
|
156
|
-
|
157
|
-
def parse_offset(options, query_params)
|
158
|
-
if query_params[:offset]
|
159
|
-
value = Util.parse_positive_int(query_params[:offset])
|
160
|
-
options[:offset] = value unless value.nil?
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def parse_limit(options, query_params)
|
165
|
-
if query_params[:limit]
|
166
|
-
value = Util.parse_positive_int(query_params[:limit])
|
167
|
-
options[:limit] = value unless value.nil?
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def parse_order(options, query_params, rules)
|
172
|
-
# Sort is favored over rsort.
|
173
|
-
|
174
|
-
if query_params[:rsort]
|
175
|
-
raise ArgumentError, "No sort rule specified." if rules[:sort].nil?
|
176
|
-
|
177
|
-
sort_by = rules[:sort][query_params[:rsort]]
|
178
|
-
options[:order] = sort_by + ' desc' unless sort_by.nil?
|
179
|
-
end
|
180
|
-
|
181
|
-
if query_params[:sort]
|
182
|
-
sort_by = rules[:sort][query_params[:sort]]
|
183
|
-
options[:order] = sort_by unless sort_by.nil?
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def parse_conditions(options, query_params, rules)
|
188
|
-
cond_strs = [ ]
|
189
|
-
cond_syms = { }
|
190
|
-
|
191
|
-
# The hash query_params is not empty, therefore, it contains at least
|
192
|
-
# some of the allowed query parameters (as Symbols) below. Those
|
193
|
-
# parameters that are not identified are ignored silently.
|
194
|
-
|
195
|
-
parse_since_until(cond_strs, cond_syms, query_params, rules)
|
196
|
-
parse_patterns(cond_strs, cond_syms, query_params, rules)
|
197
|
-
|
198
|
-
construct_conditions(options, cond_strs, cond_syms) unless cond_strs.empty?
|
199
|
-
end
|
200
|
-
|
201
|
-
def parse_since_until(cond_strs, cond_syms, query_params, rules)
|
202
|
-
if query_params[:since]
|
203
|
-
parse_datetime(cond_strs, cond_syms, query_params, rules, :since)
|
204
|
-
end
|
205
|
-
|
206
|
-
if query_params[:until]
|
207
|
-
parse_datetime(cond_strs, cond_syms, query_params, rules, :until)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
def parse_patterns(cond_strs, cond_syms, query_params, rules)
|
212
|
-
if rules[:patterns]
|
213
|
-
rules[:patterns].each do |param, rule|
|
214
|
-
if query_params[param]
|
215
|
-
match_op = searchable_record_settings[:pattern_operator]
|
216
|
-
conversion_blk = searchable_record_settings[:pattern_converter]
|
217
|
-
|
218
|
-
if rule.respond_to?(:to_hash)
|
219
|
-
column = rule[:column]
|
220
|
-
|
221
|
-
# Use custom pattern match operator.
|
222
|
-
match_op = rule[:operator] unless rule[:operator].nil?
|
223
|
-
|
224
|
-
# Use custom converter.
|
225
|
-
conversion_blk = rule[:converter] unless rule[:converter].nil?
|
226
|
-
else
|
227
|
-
column = rule
|
228
|
-
end
|
229
|
-
|
230
|
-
cond_strs << "(#{column} #{match_op} :#{param})"
|
231
|
-
cond_syms[param] = conversion_blk.call(query_params[param])
|
232
|
-
end
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
def parse_datetime(cond_strs, cond_syms, query_params, rules, type)
|
238
|
-
rule = rules[type]
|
239
|
-
cast_type = searchable_record_settings["cast_#{type}_as".to_sym]
|
240
|
-
|
241
|
-
if rule.respond_to?(:to_hash)
|
242
|
-
column = rule[:column]
|
243
|
-
|
244
|
-
# Use custom cast type.
|
245
|
-
cast_type = rule[:cast_as] unless rule[:cast_as].nil?
|
246
|
-
else
|
247
|
-
column = rule
|
248
|
-
end
|
249
|
-
|
250
|
-
case type
|
251
|
-
when :since then op = '>='
|
252
|
-
when :until then op = '<='
|
253
|
-
else raise ArgumentError, "Could not determine comparison operator for datetime."
|
254
|
-
end
|
255
|
-
|
256
|
-
cond_strs << "(#{column} #{op} cast(:#{type} as #{cast_type}))"
|
257
|
-
cond_syms[type] = query_params[type]
|
258
|
-
end
|
259
|
-
|
260
|
-
def construct_conditions(options, cond_strs, cond_syms)
|
261
|
-
conditions = [ cond_strs.join(' and '), cond_syms ]
|
262
|
-
preconditions = options[:conditions]
|
263
|
-
conditions[0].insert(0, "(#{preconditions}) and ") unless preconditions.nil?
|
264
|
-
options[:conditions] = conditions
|
265
|
-
end
|
266
|
-
end
|
7
|
+
%w(util version core).each do |file|
|
8
|
+
require "searchable_record/#{file}"
|
267
9
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{searchable_record}
|
5
|
+
s.version = "0.0.3"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Tuomas Kareinen"]
|
9
|
+
s.date = %q{2009-01-14}
|
10
|
+
s.description = %q{SearchableRecord is a small Ruby on Rails plugin that makes the parsing of query parameters from URLs easy for resources, allowing the requester to control the items (records) shown in the resource's representation.}
|
11
|
+
s.email = %q{tkareine@gmail.com}
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG.rdoc", "lib/searchable_record/core.rb", "lib/searchable_record/util.rb", "lib/searchable_record/version.rb", "lib/searchable_record.rb", "README.rdoc"]
|
13
|
+
s.files = ["CHANGELOG.rdoc", "lib/searchable_record/core.rb", "lib/searchable_record/util.rb", "lib/searchable_record/version.rb", "lib/searchable_record.rb", "Manifest", "Rakefile", "README.rdoc", "spec/searchable_record_spec.rb", "spec/searchable_record_spec_helper.rb", "spec/util_spec.rb", "searchable_record.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://searchable-rec.rubyforge.org}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Searchable_record", "--main", "README.rdoc"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{searchable-rec}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{SearchableRecord is a small Ruby on Rails plugin that makes the parsing of query parameters from URLs easy for resources, allowing the requester to control the items (records) shown in the resource's representation.}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
28
|
+
s.add_development_dependency(%q<echoe>, [">= 0"])
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
31
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
32
|
+
end
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
35
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
36
|
+
end
|
37
|
+
end
|