paraphrase 0.10.0 → 0.11.0

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.
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