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.
@@ -1,267 +1,9 @@
1
1
  unless defined? ActiveSupport
2
- require 'rubygems'
3
- gem 'activesupport'
4
- require 'active_support'
2
+ require "rubygems"
3
+ gem "activesupport"
4
+ require "active_support"
5
5
  end
6
- require 'util'
7
6
 
8
- # See SearchableRecord::ClassMethods#find_queried for usage documentation.
9
- module SearchableRecord
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