paraphrase 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7eee455eab9622de9d86136dfe8d1b5b766373e1
4
- data.tar.gz: fbba2474e3febdd44f4937347cdf14888de70125
3
+ metadata.gz: a1edd35702f438a125a18ad4ad3dd8fc4933c10a
4
+ data.tar.gz: f31827e436da49e098499f602d0225428492f73c
5
5
  SHA512:
6
- metadata.gz: 146a851d81c039939c2ba35688aaf55e01bc85bf2d3608a1d5441668aee600e37d46cc03aa8eb51b672e77de9fe1704829b239b3e3dd4f44222a3ad1cfc35711
7
- data.tar.gz: d383c9f1d43cd46e979618feb616c34dcc1acd58fdb70b51c9438cf313aef93540f132422712b6f8f6e2ac240dbe259bb9cf65235cb02cb0c7c248beac681e5f
6
+ metadata.gz: 3901ba0d17fe8e7ec907c92bc51028f35385c2cd8c9d024c952e1a9d644e7685ed0e8d4f0578b146e952292fbe07364bdf3c3a71e52ac920ed3271626c6c8988
7
+ data.tar.gz: 2114539257bc4b5f2bfb3f55d85625530ec8aeb6d71959ec1ad3fc6a43ffb56ad9905827bd3fe35f22519439914bb33c7d05233893ab3af6159719965795fbf4
data/CHANGELOG.md CHANGED
@@ -1,14 +1,40 @@
1
- ## Next Release
2
-
3
- * Add convenience class-level API for pre-processing query params
4
- * Pre-process params and then scrub them from
5
- * Rename `Scope` to the more appropriate `Mapping`
1
+ ## 0.11.0 / 9-17-2014
2
+
3
+ * Enable setting default values in param processors. The following will now
4
+ work:
5
+
6
+ ```ruby
7
+ class PostQuery < Paraphrase::Query
8
+ map :sort, to: :sorted_by
9
+
10
+ param :sort do
11
+ params[:sort].presence || "newest"
12
+ end
13
+
14
+ scope :sorted_by do |sort_direction|
15
+ sort_direction == "newest" ? relation.order(created_at: :desc) : relation.order(:created_at)
16
+ end
17
+ end
18
+ ```
19
+
20
+ ## 0.10.0 / 7-3-2014
21
+
22
+ * Change `Paraphrase::Query.source` to be a regular class attribute, removing
23
+ the DSL method `source` for defining the source.
24
+ * Add convenience class-level API for pre-processing query params.
25
+ * Rename `Params` to `ParamsFilter`. Always define a `ParamsFilter` subclass
26
+ for each `Paraphrase::Query` subclass on inheritance.
27
+ * Make params filtering consistent. Run custom method defiend on `ParamsFilter`
28
+ and then call `scrub` on the return value. Previously, `scrub` would not be
29
+ called if a custom method was defined.
30
+ * Rename `Scope` to the more appropriate `Mapping`.
6
31
  * Mark `Mapping` and `ActiveModel` classes as private API
7
- * Add ability to define scopes in the `Query` subclass via `Paraphrase::Repository`
8
- (see README)
9
- * Refactor `Query.source` to be a regular class attribute
10
- * Require `Paraphrase::Query` be initialized with an `ActiveRecord::Relation`
11
- instance. Ensure this happens in `Paraphrase::Syntax`.
32
+ * Add `Paraphrase::Repository` for defining model scopes in a
33
+ `Paraphrase::Query` subclass. Scopes can be defined by re-opening the
34
+ `Repository` class available in any `Paraphrase::Query` subclass or using
35
+ `Query.scope` DSL. See README for more.
36
+ * Require `Paraphrase::Query` be initialized with an instance of
37
+ `ActiveRecord::Relation`. Update `Paraphrase::Syntax`
12
38
 
13
39
  ## 0.9.0 / 5-2-2014
14
40
 
data/README.md CHANGED
@@ -3,125 +3,102 @@
3
3
  [![Code Climate](https://codeclimate.com/github/ecbypi/paraphrase.png)](https://codeclimate.com/github/ecbypi/paraphrase)
4
4
  [![Build Status](https://travis-ci.org/ecbypi/paraphrase.png?branch=master)](https://travis-ci.org/ecbypi/paraphrase)
5
5
 
6
- Paraphrase provides a way to map query params to model scopes and
7
- only apply scopes when the mapped query params are present.
6
+ `paraphrase` provides a way to map query params to model scopes and only apply
7
+ scopes when the mapped query params are present, removing all the conditional
8
+ checks you might perform in your controller to determine if a scope needs to be
9
+ applied.
8
10
 
9
- ## Installation
11
+ With `paraphrase`, you can also de-clutter your model by removing
12
+ context-specific scopes into the query builder.
10
13
 
11
- Via a `Gemfile`:
14
+ Take the following example:
12
15
 
13
- ```
14
- gem 'paraphrase'
15
- ```
16
+ ```ruby
17
+ class PostsController < ActiveRecord::Base
18
+ def index
19
+ @posts = Post.all
16
20
 
17
- Or manually:
21
+ names = params[:names]
18
22
 
19
- ```
20
- $ gem install paraphrase
21
- ```
22
-
23
- ## Usage
23
+ if names && names.delete_if { |name| name.blank? }.present?
24
+ @posts = @posts.published_by(names)
25
+ end
24
26
 
25
- Subclass `Paraphrase::Query` and use `map` to define what query params should
26
- be applied to which scopes.
27
+ start_date = Time.zone.parse(params[:start_date])
28
+ end_date = Time.zone.parse(params[:end_date])
27
29
 
28
- ```ruby
29
- # app/queries/post_query.rb
30
- class PostQuery < Paraphrase::Query
31
- map :author, to: :by_user
32
- map :start_date, :end_date, to: :published_within
30
+ if start_date && end_date
31
+ @posts = @posts.published_within(start_date, end_date)
32
+ end
33
+ end
33
34
  end
34
- ```
35
35
 
36
- By default, the `ActiveRecord` class is introspected from the demodulized class
37
- name of the `Paraphrase::Query` sublcass. If the name of the query class is
38
- not `<model>Query`, the source can be manually specified by passing a string or
39
- symbol to the `source` method.
36
+ class Post < ActiveRecord::Base
37
+ def self.published_by(names)
38
+ joins(:user).where(users: { name: names })
39
+ end
40
40
 
41
- ```ruby
42
- # app/queries/admin_post_query.rb
43
- class AdminPostQuery < Paraphrase::Query
44
- # This needs the source specific since it will look for an `AdminPost` model.
45
- self.source = :Post
41
+ def self.published_within(start_date, end_date)
42
+ where(published_at: start_date..end_date)
43
+ end
46
44
  end
47
45
  ```
48
46
 
49
- To build the query, call `.paraphrase` on your model. Only scopes whose keys are all
50
- provided will be applied.
51
-
52
- ```ruby
53
- # Based on the example `PostQuery` above, this will only apply `Post.by_user`
54
- # and skip `Post.published_within` since `:end_date` is missing.
55
- Post.paraphrase(author: 'Jim')
56
- ```
47
+ As the number of options for the query grows, the `index` method will continue
48
+ to accrue with conditional checks and the model will become bloated with that
49
+ are might only used in the controller.
57
50
 
58
- All unregistered keys are filered out of the params that are passed to `.paraphrase`.
51
+ By using paraphrase, the controller and model can be simplified to:
59
52
 
60
53
  ```ruby
61
- class PostsController < ApplicationController
62
- respond_to :html, :json
63
-
54
+ class PostsController < ActiveRecord::Base
64
55
  def index
65
- # Will filter out keys such as `:action` and `:controller`
66
56
  @posts = Post.paraphrase(params)
67
- respond_with(@posts)
68
57
  end
69
58
  end
70
- ```
71
59
 
72
- `Paraphrase::Query` will recursively determine if the value of the query
73
- param is empty. If the value is an array containing empty strings, the empty
74
- strings will be removed before being passed to the scope. If the array is empty
75
- after removing empty strings, the scope will not be called since an empty array
76
- is considered a blank value.
60
+ class PostQuery < Paraphrase::Query
61
+ map :names, to: :published_by
62
+ map :start_date, :end_date, to: :published_within
77
63
 
78
- ```ruby
79
- class UserQuery < Paraphrase::Query
80
- map :names, to: :with_name
81
- end
64
+ param :start_date do
65
+ Time.zone.parse(params[:start_date]) rescue nil
66
+ end
82
67
 
83
- class User < ActiveRecord::Base
84
- def self.with_name(names)
85
- where(name: names)
68
+ param :end_date do
69
+ Time.zone.parse(params[:end_date]) rescue nil
86
70
  end
87
- end
88
71
 
89
- User.paraphrase(names: ['', 'Jim']).to_sql
90
- # => SELECT "users".* FROM "users" WHERE "users"."name" IN ['Jim']
72
+ scope :published_by do |user_names|
73
+ relation.joins(:user).where(users: { name: user_names })
74
+ end
75
+ end
91
76
 
92
- User.paraphrase(names: ['', '']).to_sql
93
- # => SELECT "users".* FROM "users"
77
+ class Post < ActiveRecord::Base
78
+ def self.published_within(start_date, end_date)
79
+ where(published_at: start_date..end_date)
80
+ end
81
+ end
94
82
  ```
95
83
 
96
- You can chain queries on an `ActiveRecord::Relation`. This avoids adding scopes
97
- that replicate the functionality of an association like
98
- `Post.for_user(user_id)` or allow you to build a default scope.
99
-
100
- ```ruby
101
- class PostsController < ApplicationController
102
- respond_to :html, :json
84
+ ## Installation
103
85
 
104
- # GET /users/:id/posts
105
- def index
106
- @user = User.find(params[:user_id])
86
+ Via a `Gemfile`:
107
87
 
108
- # This will scope the query to posts where `posts`.`user_id` = `users`.`id`
109
- @posts = @users.posts.paraphrase(params[:q])
88
+ ```
89
+ gem 'paraphrase'
90
+ ```
110
91
 
111
- # Or you can build at a different point in a scope chain
112
- # @posts = @user.posts.published.paraphrase(params[:q])
113
- #
114
- # Order is independent too
115
- # @posts = @user.posts.paraphrase(params[:q]).published
92
+ Or manually:
116
93
 
117
- respond_with(@posts)
118
- end
119
- end
94
+ ```
95
+ $ gem install paraphrase
120
96
  ```
121
97
 
122
- ### Query Class DSL
98
+ ## Usage
123
99
 
124
100
  Scopes are mapped to param keys using `map`. You can specify one or more keys.
101
+ The scope will only be called if all the keys are present.
125
102
 
126
103
  ```ruby
127
104
  class PostQuery < Paraphrase::Query
@@ -138,35 +115,84 @@ class Post < ActiveRecord::Base
138
115
  where(published_on: pub_date)
139
116
  end
140
117
  end
118
+
119
+ Post.paraphrase(first_name: 'Jon', last_name: 'Richards', pub_date: '2010-10-01')
120
+ # => SELECT "posts".* FROM "posts"i
121
+ # WHERE "posts"."first_name" = 'Jon'
122
+ # AND "posts.last_name" = 'Richards'
123
+ # AND "posts.published_on" = '2010-10-01'
124
+
125
+ Post.paraphrase(first_name: 'Jon', pub_date: '2010-10-01')
126
+ # => SELECT "posts".* FROM "posts" WHERE "posts.published_on" = '2010-10-01'
141
127
  ```
142
128
 
129
+ ### Changing the Model Class Used
130
+
131
+ By default, the `ActiveRecord` class is determined from the `demodulize`'d name
132
+ of the `Paraphrase::Query` sublcass. For instance, `DeliveryQuery` will use the
133
+ `Delivery` model by default.
134
+
135
+ If the name of the query class does not match this convention, the source can be
136
+ specified by setting the `source` class atribute.
137
+
138
+ ```ruby
139
+ # app/queries/admin_post_query.rb
140
+ class AdminPostQuery < Paraphrase::Query
141
+ self.source = :Post
142
+ end
143
+ ```
144
+
145
+ ### Whitelisting Query Params
146
+
143
147
  If multiple query params are mapped to a scope, but only a subset are required,
144
- use the `:whitelist` option to allow them to be blank. The `:whitelist`
145
- option can be set to `true`, an individual key or an array of keys.
148
+ use the `:whitelist` option to allow them to be blank. The `:whitelist` option
149
+ can be set to `true` to whitelist all keys, an individual key or an array of
150
+ keys.
146
151
 
147
152
  ```ruby
148
153
  class PostQuery < Paraphrase::Query
149
- # requires only :last_name to be passed in, :first_name can be nil
150
154
  map :first_name, :last_name, to: :by_author, whitelist: :last_name
155
+ map :pub_date, to: :pub_date
151
156
  end
152
157
 
153
158
  class Post < ActiveRecord::Base
159
+ # `last_name` will be `nil` if not supplied.
154
160
  def self.by_author(first_name, last_name)
155
- query = where(user: { first_name: first_name })
161
+ query = where(users: { first_name: first_name })
156
162
 
163
+ # Only filter by `:last_name` if supplied
157
164
  if last_name
158
- query = query.where(user: { last_name: last_name })
165
+ query = query.where(users: { last_name: last_name })
159
166
  end
160
167
 
161
168
  query
162
169
  end
163
170
  end
164
171
 
165
- Post.paraphrase(first_name: 'John').to_sql
166
- # => SELECT "posts".* FROM "posts" WHERE "posts"."first_name" = 'John'
172
+ Post.paraphrase(first_name: 'Jon', pub_date: '2010-10-01')
173
+ # => SELECT "posts".* FROM "posts"i
174
+ # WHERE "posts"."first_name" = 'Jon'
175
+ # AND "posts.published_on" = '2010-10-01'
176
+ ```
177
+
178
+ Whitelisting is also useful for query params that are optional and have a
179
+ default, implied value such as with sorting:
180
+
181
+ ```ruby
182
+ class PostQuery < Paraphrase::Query
183
+ map :sort, to: :sorted_by, whitelist: true
184
+ end
167
185
 
168
- Post.paraphrase(first_name: 'John', last_name: 'Smith').to_sql
169
- # => SELECT "posts".* FROM "posts" WHERE "posts"."first_name" = 'John' AND "posts"."last_name" = 'Smith'
186
+ class Post < ActiveRecord::Base
187
+ def self.sorted_by(sort_direction)
188
+ case sort_direction
189
+ when nil, 'newest'
190
+ order(created_at: :desc)
191
+ else
192
+ order(:created_at)
193
+ end
194
+ end
195
+ end
170
196
  ```
171
197
 
172
198
  ### Boolean Scopes
@@ -194,21 +220,50 @@ Post.paraphrase(published: '1').to_sql
194
220
  # => SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't'
195
221
  ```
196
222
 
197
- ### Pre-processing Query Params
223
+ ### Filtering `blank` Values
224
+
225
+ By default, `paraphrase` will recursively determine if the value of a query
226
+ param is `blank?`. This is meant to deal with form submissions, since blank
227
+ values are submitted even if the input is not filled in.
228
+
229
+ For example, if the value is an array containing empty strings, the empty
230
+ strings will be removed before being passed to the scope. If the array is empty
231
+ after removing empty strings, the scope will not be called since an empty array
232
+ is considered a blank value.
233
+
234
+ ```ruby
235
+ class UserQuery < Paraphrase::Query
236
+ map :names, to: :with_name
237
+ end
238
+
239
+ class User < ActiveRecord::Base
240
+ def self.with_name(names)
241
+ where(name: names)
242
+ end
243
+ end
244
+
245
+ User.paraphrase(names: ['', 'Jim']).to_sql
246
+ # => SELECT "users".* FROM "users" WHERE "users"."name" IN ['Jim']
247
+
248
+ User.paraphrase(names: ['', '']).to_sql
249
+ # => SELECT "users".* FROM "users"
250
+ ```
251
+
252
+ ### Pre-processing Values
198
253
 
199
- To pre-process a query param, such as an ISO formatted date, you can either use
200
- the `param` class method or re-open the `ParamsFilter` class that is defined
201
- when inheriting from `Paraphrase::Query`. Using the `param` class method
202
- defines the equivalent method on the `ParamsFilter` class.
254
+ To pre-process a query param, such as an ISO formatted date, you can use the
255
+ `param` class method or re-open the `ParamsFilter` class that is defined when
256
+ inheriting from `Paraphrase::Query`. Using the `param` class method defines the
257
+ equivalent method on the `ParamsFilter` class.
203
258
 
204
- In the method, you have access to the `params` attribute that represents the
205
- original, unprocessed params.
259
+ In the method, you have access to `params` that represents the original,
260
+ unprocessed params.
206
261
 
207
262
  ```ruby
208
263
  class PostQuery < Paraphrase::Query
209
264
  map :start_date, :end_date, to: :published_within
210
265
 
211
- class ParamsFilter
266
+ class ParamsFilter < Paraphrase::ParamsFilter
212
267
  def start_date
213
268
  Time.zone.parse(params[:start_date]) rescue nil
214
269
  end
@@ -225,24 +280,30 @@ class Post < ActiveRecord::Base
225
280
  end
226
281
  end
227
282
 
283
+ Post.parahrase(start_date: '2011-03-21', end_date: '2013-03-25').to_sql
284
+ # => SELECT "posts".* FROM "posts"
285
+ WHERE "posts"."published_at" BETWEEN '2011-03-21' AND '2013-03-25'
286
+
287
+ # The typo in the `start_date` query param causes `Time.zone.parse` to fail so
288
+ # the pre-procssed `start_date` is `nil`. Since not all params are present, the
289
+ # scope is not run.
228
290
  Post.parahrase(start_date: '201-03-21', end_date: '2013-03-25').to_sql
229
291
  # => SELECT "posts".* FROM "posts"
230
292
  ```
231
293
 
232
294
  In the above example, if either `:start_date` or `:end_date` are incorrectly
233
- formatted, the `pubished_within` scope will not be applied because the values
234
- are will be `nil`.
295
+ formatted, the `pubished_within` scope will not be applied since
296
+ `Time.zone.parse` will fail and return `nil`.
235
297
 
236
- ### Define scopes on the `Query` class
298
+ ### Define scopes in the `Query` class
237
299
 
238
- If your model is cluttered with scopes that aren't general-purpose, and only
239
- used by your query class, you can define them in the query class. You can
240
- define scopes by re-opening the `Repository` class defined on inheritance from
241
- `Paraphrase::Query`. There is also the `scope` class method that serves as a
242
- proxy for defining methods on the `Repository` class.
300
+ Scopes can be defined in the `Query` class using the `scope` keyword or
301
+ re-opening the `Repository` class defined in the `Query` subclass. This helps to
302
+ avoid cluttering the model class with scopes that are only used by the query
303
+ class.
243
304
 
244
- In the method, you have to call the scope on the `relation` property of the
245
- `Repository` instance.
305
+ When defining scopes this way, any `ActiveRecord::Relation` methods should be
306
+ called on the `relation` property of the `Repository` instance.
246
307
 
247
308
  ```ruby
248
309
  class PostQuery < Paraphrase::Query
@@ -254,18 +315,17 @@ class PostQuery < Paraphrase::Query
254
315
  relation.joins(:user).where(users: { name: authors })
255
316
  end
256
317
 
257
- # OR
258
- # class Repository
259
- # def by_users(authors)
260
- # relation.joins(:user).where(users: { name: authors })
261
- # end
262
- # end
318
+ class Repository < Paraphrase::Repository
319
+ def titled(post_title)
320
+ relation.where(title: post_title)
321
+ end
322
+ end
263
323
  end
264
324
 
265
325
  class Post < ActiveRecord::Base
266
326
  end
267
327
 
268
- Post.paraphrase(authors: ['Robert', 'Susie']).to_sql
328
+ Post.paraphrase(authors: ['Robert', 'Susie'], title: 'Sunshine').to_sql
269
329
  # => SELECT "posts".* FROM "posts"
270
330
  # INNER JOIN "users" ON "users"."id" = "posts"."user_id"
271
331
  # WHERE "users"."name" IN ('Robert', 'Susie')
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- paraphrase (0.10.0)
4
+ paraphrase (0.11.0)
5
5
  activemodel (>= 3.1, < 4.2)
6
6
  activerecord (>= 3.1, < 4.2)
7
7
  activesupport (>= 3.1, < 4.2)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- paraphrase (0.10.0)
4
+ paraphrase (0.11.0)
5
5
  activemodel (>= 3.1, < 4.2)
6
6
  activerecord (>= 3.1, < 4.2)
7
7
  activesupport (>= 3.1, < 4.2)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- paraphrase (0.10.0)
4
+ paraphrase (0.11.0)
5
5
  activemodel (>= 3.1, < 4.2)
6
6
  activerecord (>= 3.1, < 4.2)
7
7
  activesupport (>= 3.1, < 4.2)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- paraphrase (0.10.0)
4
+ paraphrase (0.11.0)
5
5
  activemodel (>= 3.1, < 4.2)
6
6
  activerecord (>= 3.1, < 4.2)
7
7
  activesupport (>= 3.1, < 4.2)
@@ -1,4 +1,3 @@
1
- require 'active_support/core_ext/object/blank'
2
1
  require 'active_support/core_ext/array/wrap'
3
2
 
4
3
  module Paraphrase
@@ -1,17 +1,29 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'active_support/hash_with_indifferent_access'
3
+
1
4
  module Paraphrase
5
+ # {ParamsFilter} is responsible for processing the query params the {Query}
6
+ # object was initialized with.
7
+ #
8
+ # In the following order, it:
9
+ #
10
+ # 1. Removes all keys not mapped to a model scope
11
+ # 2. Pre-processes the query param if a pre-processor is defined
12
+ # 3. Recursively removes blank values from the value
13
+ # 4. Removes the param if the pre-processed, scrubbed value is `blank?`
14
+ #
15
+ # Each {Query} subclass has its own {ParamsFilter} subclass defined on
16
+ # inheritance that can be customized to pre-process query params. The class
17
+ # can be re-opened inside the {Query} class definition or by calling the
18
+ # {Query.param param} class method.
2
19
  class ParamsFilter
3
20
  attr_reader :params, :result
4
21
 
5
22
  def initialize(unfiltered_params, keys)
6
23
  @params = unfiltered_params.with_indifferent_access.slice(*keys)
7
24
 
8
- @result = @params.inject(HashWithIndifferentAccess.new) do |result, (key, value)|
9
- value = @params[key]
10
-
11
- if respond_to?(key)
12
- value = send(key)
13
- end
14
-
25
+ @result = keys.inject(HashWithIndifferentAccess.new) do |result, key|
26
+ value = respond_to?(key) ? send(key) : @params[key]
15
27
  value = scrub(value)
16
28
 
17
29
  if value.present?
@@ -15,19 +15,16 @@ module Paraphrase
15
15
  include ActiveModel
16
16
  # @!attribute [r] mappings
17
17
  # @return [Array<Paraphrase::Mapping>] mappings for query
18
- # @!attribute [r] source
19
- # @return [Symbol, String] name of the class to use as the source for the
20
- # query
21
18
  class_attribute :mappings, instance_writer: false
22
19
  class_attribute :source, instance_writer: false, instance_reader: false
23
20
  class_attribute :params_filter, instance_writer: false
24
21
  class_attribute :repository, instance_writer: false
25
22
 
26
23
  # @!attribute [r] params
27
- # @return [HashWithIndifferentAccess] filtered parameters based on keys defined in `mappings`
28
- #
24
+ # @return [HashWithIndifferentAccess] filtered parameters based on keys
25
+ # defined in `mappings`
29
26
  # @!attribute [r] result
30
- # @return [ActiveRecord::Relation]
27
+ # @return [ActiveRecord::Relation] resulting {ActiveRecord::Relation} instance from queries
31
28
  attr_reader :params, :result
32
29
 
33
30
  # Set `mappings` on inheritance to ensure they're unique per subclass
@@ -42,15 +39,15 @@ module Paraphrase
42
39
  klass.const_set(:Repository, klass.repository)
43
40
  end
44
41
 
45
- # Keys being mapped to scopes
42
+ # Keys mapped to scopes
46
43
  #
47
44
  # @return [Array<Symbol>]
48
45
  def self.keys
49
46
  mappings.flat_map(&:keys)
50
47
  end
51
48
 
52
- # Add a {Mapping} instance to {Query#mappings}. Defines a reader for each
53
- # key to read from {Query#params}.
49
+ # Add a {Mapping} instance to {Query#mappings}. Defines a reader
50
+ # for each key to read from {Query#params}.
54
51
  #
55
52
  # @overload map(*keys, options)
56
53
  # Maps a key to a scope
@@ -78,7 +75,7 @@ module Paraphrase
78
75
  # param
79
76
  #
80
77
  # @param [Symbol] query_param query param to process
81
- # @param [Proc] block block to process the query param
78
+ # @param [Proc] block block to process the specified `query_param`
82
79
  def self.param(query_param, &block)
83
80
  params_filter.class_eval do
84
81
  define_method(query_param, &block)
@@ -86,6 +83,9 @@ module Paraphrase
86
83
  end
87
84
 
88
85
  # Define a scope on `Repository`
86
+ #
87
+ # @param [Symbol] scope_name name of the scope specified in {Query.map}
88
+ # @param [Proc] block body of the scope to be defined
89
89
  def self.scope(scope_name, &block)
90
90
  repository.class_eval do
91
91
  define_method(scope_name, &block)
@@ -95,8 +95,8 @@ module Paraphrase
95
95
  # Filters out parameters irrelevant to the query and sets the base scope
96
96
  # for to begin the chain.
97
97
  #
98
- # @param [Hash] params query parameters
99
- # @param [ActiveRecord::Relation] relation object to apply methods to
98
+ # @param [Hash] query_params query parameters
99
+ # @param [ActiveRecord::Relation] relation relation object to apply methods to
100
100
  def initialize(query_params, relation = nil)
101
101
  @params = filter_params(query_params || {})
102
102
 
@@ -1,4 +1,10 @@
1
1
  module Paraphrase
2
+ # {Repository} is were query-specific scopes are defined. They can be defined
3
+ # by re-opening the class inside the {Query} class definition or by using the
4
+ # {Query.scope scope} class method on {Query}. Both methods are equivalent.
5
+ #
6
+ # Inside scopes defined on a {Repository}, the method has access to
7
+ # {Query#params} as `params`.
2
8
  class Repository
3
9
  attr_reader :relation, :mapping, :params
4
10
 
@@ -1,3 +1,3 @@
1
1
  module Paraphrase
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -8,6 +8,7 @@ module Paraphrase
8
8
  map :is_published, to: :published
9
9
  map :authors, to: :by_users
10
10
  map :start_date, :end_date, to: :published_between
11
+ map :sort, to: :sort_by
11
12
 
12
13
  class ParamsFilter
13
14
  def start_date
@@ -17,7 +18,7 @@ module Paraphrase
17
18
 
18
19
  class Repository
19
20
  def published_between(start_date, end_date)
20
- where(published_at: start_date..end_date)
21
+ relation.where(published_at: start_date..end_date)
21
22
  end
22
23
  end
23
24
 
@@ -25,9 +26,17 @@ module Paraphrase
25
26
  Time.parse(params[:end_date]) rescue nil
26
27
  end
27
28
 
29
+ param :sort do
30
+ params[:sort].presence || "newest"
31
+ end
32
+
28
33
  scope :by_users do |authors|
29
34
  relation.joins(:user).where(users: { name: authors })
30
35
  end
36
+
37
+ scope :sort_by do |sort_direction|
38
+ sort_direction == "newest" ? relation.order("created_at DESC") : relation
39
+ end
31
40
  end
32
41
 
33
42
  describe ".map" do
@@ -86,21 +95,32 @@ module Paraphrase
86
95
  expect(query.params[:authors]).to eq ['kevin']
87
96
  expect(query.params).not_to have_key :title
88
97
  end
98
+
99
+ it "can have default values defined" do
100
+ query = PostQuery.new(Hash.new)
101
+
102
+ expect(query.sort).to eq "newest"
103
+ end
89
104
  end
90
105
 
91
106
  it 'skips scopes if query params are missing' do
92
- expect(Post).not_to receive(:published_between)
93
- expect(Post).not_to receive(:titled)
94
- expect(Post).not_to receive(:by_users)
95
- expect(Post).to receive(:published)
96
-
97
- PostQuery.new(
107
+ post = Post.create!(
108
+ published_at: Time.local(2009, 10, 30),
109
+ title: 'bar',
110
+ user: User.create!,
111
+ published: true
112
+ )
113
+ params = {
98
114
  start_date: Time.local(2010, 10, 30),
99
115
  end_date: 'foo',
100
116
  is_published: '1',
101
117
  authors: [],
102
118
  title: ['', {}]
103
- )
119
+ }
120
+
121
+ query = PostQuery.new(params)
122
+
123
+ expect(query.result).to eq [post]
104
124
  end
105
125
 
106
126
  it 'supports defining scopes in the query class' do
data/spec/spec_helper.rb CHANGED
@@ -30,6 +30,7 @@ ActiveRecord::Schema.define do
30
30
  t.boolean :published
31
31
  t.datetime :published_at
32
32
  t.references :user
33
+ t.timestamps
33
34
  end
34
35
 
35
36
  create_table :accounts, force: true do |t|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paraphrase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eduardo Gutierrez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-03 00:00:00.000000000 Z
11
+ date: 2014-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -276,7 +276,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
276
276
  version: '0'
277
277
  requirements: []
278
278
  rubyforge_project:
279
- rubygems_version: 2.2.0
279
+ rubygems_version: 2.2.2
280
280
  signing_key:
281
281
  specification_version: 4
282
282
  summary: Map query params to model scopes