paraphrase 0.5.0 → 0.6.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.
- data/Appraisals +0 -3
- data/README.md +109 -57
- data/gemfiles/3.0.gemfile.lock +5 -6
- data/gemfiles/3.1.gemfile.lock +5 -6
- data/gemfiles/3.2.gemfile.lock +5 -6
- data/lib/paraphrase/query.rb +18 -32
- data/lib/paraphrase/scope_mapping.rb +27 -18
- data/lib/paraphrase/syntax.rb +15 -10
- data/lib/paraphrase/version.rb +1 -1
- data/paraphrase.gemspec +1 -2
- data/spec/paraphrase/query_spec.rb +11 -37
- data/spec/paraphrase/scope_mapping_spec.rb +6 -17
- data/spec/paraphrase/syntax_spec.rb +7 -5
- data/spec/support/database.rb +7 -0
- metadata +4 -20
data/Appraisals
CHANGED
@@ -1,17 +1,14 @@
|
|
1
1
|
appraise '3.0' do
|
2
2
|
gem 'activerecord', '3.0.15'
|
3
|
-
gem 'activemodel', '3.0.15'
|
4
3
|
gem 'activesupport', '3.0.15'
|
5
4
|
end
|
6
5
|
|
7
6
|
appraise '3.1' do
|
8
7
|
gem 'activerecord', '3.1.6'
|
9
|
-
gem 'activemodel', '3.1.6'
|
10
8
|
gem 'activesupport', '3.1.6'
|
11
9
|
end
|
12
10
|
|
13
11
|
appraise '3.2' do
|
14
12
|
gem 'activerecord', '3.2.6'
|
15
|
-
gem 'activemodel', '3.2.6'
|
16
13
|
gem 'activesupport', '3.2.6'
|
17
14
|
end
|
data/README.md
CHANGED
@@ -1,22 +1,18 @@
|
|
1
1
|
# paraphrase
|
2
2
|
|
3
|
-
|
4
|
-
scopes.
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
Paraphrase provides a way to map request params to model scopes and apply those
|
4
|
+
scopes based on what params are supplied. It adds a `.paraphrase` method to
|
5
|
+
your model classes and `ActiveRecord::Relation` instances that, after setting
|
6
|
+
up your scope => key mappings, will apply scopes if the parameters mapped to a
|
7
|
+
scope are present. You can also require and whitelist certain parameters to
|
8
|
+
provide more flexibility on complex scopes.
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
12
|
-
Via a
|
12
|
+
Via a `Gemfile`:
|
13
13
|
|
14
|
-
```ruby
|
15
|
-
gem 'paraphrase'
|
16
14
|
```
|
17
|
-
|
18
|
-
```
|
19
|
-
$ bundle
|
15
|
+
gem 'paraphrase'
|
20
16
|
```
|
21
17
|
|
22
18
|
Or manually:
|
@@ -27,39 +23,35 @@ $ gem install paraphrase
|
|
27
23
|
|
28
24
|
## Usage
|
29
25
|
|
30
|
-
|
26
|
+
Create a subclass of `Paraphrase::Query` or call `register_mapping` from within
|
27
|
+
your model to setup mappings.
|
31
28
|
|
32
|
-
|
29
|
+
```ruby
|
30
|
+
# app/queries/post_query.rb
|
31
|
+
class PostQuery < Paraphrase::Query
|
32
|
+
map :by_user, :to => :author
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
+
# or
|
35
36
|
|
36
|
-
|
37
|
+
# app/models/post.rb
|
37
38
|
class Post < ActiveRecord::Base
|
39
|
+
belongs_to :user
|
40
|
+
|
38
41
|
register_mapping do
|
39
42
|
map :by_user, :to => :author
|
40
43
|
end
|
41
44
|
|
42
|
-
def self.by_user(
|
43
|
-
|
45
|
+
def self.by_user(author)
|
46
|
+
where(:user => { :name => author })
|
44
47
|
end
|
45
48
|
end
|
46
49
|
```
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# takes the constant or a symbol/string that can be classified
|
53
|
-
# into a constant name
|
54
|
-
paraphrases Post
|
55
|
-
|
56
|
-
map :by_user, :to => :author
|
57
|
-
end
|
58
|
-
```
|
59
|
-
|
60
|
-
### Making a Query
|
61
|
-
|
62
|
-
In your controller, call the relevant method based on your setup:
|
51
|
+
In the controller, call `.paraphrase` on your model, passing in the hash
|
52
|
+
containing the query params. This will filter out the registered parameters,
|
53
|
+
calling the scopes whose inputs are supplied. If inputs for a scope are
|
54
|
+
missing, it is skipped.
|
63
55
|
|
64
56
|
```ruby
|
65
57
|
class PostsController < ApplicationController
|
@@ -67,45 +59,46 @@ class PostsController < ApplicationController
|
|
67
59
|
|
68
60
|
def index
|
69
61
|
@posts = Post.paraphrase(params)
|
70
|
-
# Or if you created a subclass
|
71
|
-
# @posts = PostQuery.new(params)
|
72
|
-
|
73
62
|
respond_with(@posts)
|
74
63
|
end
|
75
64
|
end
|
76
65
|
```
|
77
66
|
|
78
|
-
You can
|
67
|
+
You can chain queries on an `ActiveRecord::Relation`. This avoids adding scopes
|
68
|
+
that replicate the functionality of an association like
|
69
|
+
`Post.for_user(user_id)` or allows you to build a default scope.
|
79
70
|
|
80
71
|
```ruby
|
81
72
|
class PostsController < ApplicationController
|
82
73
|
respond_to :html, :json
|
83
74
|
|
84
|
-
# GET /users/
|
75
|
+
# GET /users/:id/posts
|
85
76
|
def index
|
86
77
|
@user = User.find(params[:user_id])
|
87
78
|
|
88
|
-
# This will
|
79
|
+
# This will scope the query to posts where `posts`.`user_id` = `users`.`id`
|
89
80
|
@posts = @users.posts.paraphrase(params)
|
90
81
|
|
82
|
+
# Or you can build at a different point in a scope chain
|
83
|
+
# @posts = @user.posts.published.paraphrase(params)
|
84
|
+
|
91
85
|
respond_with(@posts)
|
92
86
|
end
|
93
87
|
end
|
94
88
|
```
|
95
89
|
|
96
|
-
###
|
90
|
+
### Query Class DSL
|
97
91
|
|
98
|
-
|
99
|
-
|
100
|
-
to. An array of keys can be supplied to pass multiple arguments to a scope.
|
92
|
+
Scopes are mapped to param keys using the `map` class method provided by
|
93
|
+
`Paraphrase::Query`. You can specify one or more keys.
|
101
94
|
|
102
95
|
```ruby
|
103
|
-
class
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
end
|
96
|
+
class PostQuery < Paraphrase::Query
|
97
|
+
map :by_user, :to => [:first_name, :last_name]
|
98
|
+
map :published_on, :to => :pub_date
|
99
|
+
end
|
108
100
|
|
101
|
+
class Post < ActiveRecord::Base
|
109
102
|
def self.by_user(first_name, last_name)
|
110
103
|
joins(:user).where(:user => { :first_name => first_name, :last_name => last_name })
|
111
104
|
end
|
@@ -116,29 +109,88 @@ class Post < ActiveRecord::Base
|
|
116
109
|
end
|
117
110
|
```
|
118
111
|
|
119
|
-
If a scope is required for a query to be considered valid,
|
120
|
-
true` or `:require => [:array, :of, :keys]`
|
121
|
-
missing
|
122
|
-
array
|
123
|
-
|
112
|
+
If a scope is required for a query to be considered valid, add `:require =>
|
113
|
+
true` or `:require => [:array, :of, :keys]` in the options. If any values are
|
114
|
+
missing, an empty result set will be returned. If the base key is an
|
115
|
+
array, you can specify a subset of the key to be required. In this case, the rest of the
|
116
|
+
attributes will be whitelisted.
|
124
117
|
|
125
118
|
```ruby
|
126
119
|
class Post < ActiveRecord::Base
|
127
120
|
register_mapping do
|
121
|
+
# requires :pub_date to be supplied
|
128
122
|
map :published_on, :to => :pub_date, :require => true
|
129
|
-
|
123
|
+
|
124
|
+
# requires only :last_name to be passed in, :first_name can be nil
|
125
|
+
map :by_author, :to => [:first_name, :last_name], :require => :last_name
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.by_author(first_name, last_name)
|
129
|
+
query = where(:user => { :last_name => last_name })
|
130
|
+
|
131
|
+
if first_name
|
132
|
+
query.where(:user => { :first_name => first_name })
|
133
|
+
end
|
134
|
+
|
135
|
+
query
|
130
136
|
end
|
131
137
|
end
|
132
138
|
|
133
|
-
Post.paraphrase.results # => []
|
139
|
+
Post.paraphrase({}).results # => []
|
134
140
|
```
|
135
141
|
|
136
|
-
Alternatively, a scope
|
142
|
+
Alternatively, a scope or a subset of its keys can be whitelisted allowing nil
|
143
|
+
values to be passed to the scope. This is intended for scopes that alter their
|
144
|
+
behavior conditionally on a parameter being present. You should whitelist
|
145
|
+
inputs if you still want other scopes to be applied as requiring them will halt
|
146
|
+
execution of scopes and return an empty result set.
|
137
147
|
|
138
148
|
```ruby
|
139
149
|
class Post < ActiveRecord::Base
|
140
150
|
register_mapping do
|
141
|
-
|
151
|
+
# :first_name can be nil, :last_name is still required to apply the scope
|
152
|
+
map :by_author, :to => [:first_name, :last_name], :whitelist => :first_name
|
142
153
|
end
|
143
154
|
end
|
144
155
|
```
|
156
|
+
|
157
|
+
### Boolean Scopes
|
158
|
+
|
159
|
+
Some scopes take the form of a switch, filtering records based on a boolean
|
160
|
+
column. It doesn't make sense for these methods to take any arguments and
|
161
|
+
requirng them to would couple them to `Paraphrase::Query` classes in a
|
162
|
+
complicated way.
|
163
|
+
|
164
|
+
Paraphrase will detect if the method specified takes no arguments. If not, it
|
165
|
+
will call the method without any arguments, assuming the inputs are present and
|
166
|
+
valid.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class Post < ActiveRecord::Base
|
170
|
+
register_mapping do
|
171
|
+
map :published, :to => :published
|
172
|
+
end
|
173
|
+
|
174
|
+
# If the params supplied include a non-nil value for :published,
|
175
|
+
# this method will be called.
|
176
|
+
def self.published
|
177
|
+
where('published_at IS NOT NULL')
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
### ActiveSupport::Notifications
|
183
|
+
|
184
|
+
You can subscribe to notifications when the query is built.
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
ActiveSupport::Notifications.subscribe('query.paraphrase') do |name, start, end, id, payload|
|
188
|
+
# ...
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
`payload` contains:
|
193
|
+
|
194
|
+
* `:params`: the params filtered
|
195
|
+
* `:source_name`: name of the class being queried
|
196
|
+
* `:source`: `ActiveRecord::Relation` being used as the base for the query
|
data/gemfiles/3.0.gemfile.lock
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: /Users/edd_d/src/paraphrase
|
3
3
|
specs:
|
4
|
-
paraphrase (0.
|
5
|
-
activemodel (~> 3.0)
|
4
|
+
paraphrase (0.6.0)
|
6
5
|
activerecord (~> 3.0)
|
7
6
|
activesupport (~> 3.0)
|
8
7
|
|
@@ -47,11 +46,11 @@ DEPENDENCIES
|
|
47
46
|
activemodel (= 3.0.15)
|
48
47
|
activerecord (= 3.0.15)
|
49
48
|
activesupport (= 3.0.15)
|
50
|
-
appraisal
|
49
|
+
appraisal (~> 0.4)
|
51
50
|
bundler (~> 1.0)
|
52
51
|
paraphrase!
|
53
|
-
rake
|
54
|
-
redcarpet
|
52
|
+
rake (~> 0.9.2)
|
53
|
+
redcarpet (~> 2.1.1)
|
55
54
|
rspec (~> 2.10)
|
56
|
-
sqlite3
|
55
|
+
sqlite3 (~> 1.3.6)
|
57
56
|
yard (~> 0.7)
|
data/gemfiles/3.1.gemfile.lock
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: /Users/edd_d/src/paraphrase
|
3
3
|
specs:
|
4
|
-
paraphrase (0.
|
5
|
-
activemodel (~> 3.0)
|
4
|
+
paraphrase (0.6.0)
|
6
5
|
activerecord (~> 3.0)
|
7
6
|
activesupport (~> 3.0)
|
8
7
|
|
@@ -49,11 +48,11 @@ DEPENDENCIES
|
|
49
48
|
activemodel (= 3.1.6)
|
50
49
|
activerecord (= 3.1.6)
|
51
50
|
activesupport (= 3.1.6)
|
52
|
-
appraisal
|
51
|
+
appraisal (~> 0.4)
|
53
52
|
bundler (~> 1.0)
|
54
53
|
paraphrase!
|
55
|
-
rake
|
56
|
-
redcarpet
|
54
|
+
rake (~> 0.9.2)
|
55
|
+
redcarpet (~> 2.1.1)
|
57
56
|
rspec (~> 2.10)
|
58
|
-
sqlite3
|
57
|
+
sqlite3 (~> 1.3.6)
|
59
58
|
yard (~> 0.7)
|
data/gemfiles/3.2.gemfile.lock
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: /Users/edd_d/src/paraphrase
|
3
3
|
specs:
|
4
|
-
paraphrase (0.
|
5
|
-
activemodel (~> 3.0)
|
4
|
+
paraphrase (0.6.0)
|
6
5
|
activerecord (~> 3.0)
|
7
6
|
activesupport (~> 3.0)
|
8
7
|
|
@@ -49,11 +48,11 @@ DEPENDENCIES
|
|
49
48
|
activemodel (= 3.2.6)
|
50
49
|
activerecord (= 3.2.6)
|
51
50
|
activesupport (= 3.2.6)
|
52
|
-
appraisal
|
51
|
+
appraisal (~> 0.4)
|
53
52
|
bundler (~> 1.0)
|
54
53
|
paraphrase!
|
55
|
-
rake
|
56
|
-
redcarpet
|
54
|
+
rake (~> 0.9.2)
|
55
|
+
redcarpet (~> 2.1.1)
|
57
56
|
rspec (~> 2.10)
|
58
|
-
sqlite3
|
57
|
+
sqlite3 (~> 1.3.6)
|
59
58
|
yard (~> 0.7)
|
data/lib/paraphrase/query.rb
CHANGED
@@ -2,46 +2,28 @@ require 'active_support/core_ext/class/attribute'
|
|
2
2
|
require 'active_support/core_ext/module/delegation'
|
3
3
|
require 'active_support/core_ext/string/inflections'
|
4
4
|
require 'active_support/hash_with_indifferent_access'
|
5
|
-
require 'active_model/naming'
|
6
5
|
|
7
6
|
module Paraphrase
|
8
7
|
class Query
|
9
|
-
extend ActiveModel::Naming
|
10
|
-
|
11
8
|
# @!attribute [r] mappings
|
12
9
|
# @return [Array<ScopeMapping>] mappings for query
|
13
|
-
|
14
|
-
# @!attribute [r] source
|
15
|
-
# @return [ActiveRecord::Relation] source to apply scopes to
|
16
|
-
class_attribute :mappings, :source, :instance_writer => false
|
10
|
+
class_attribute :mappings, :instance_writer => false
|
17
11
|
|
18
12
|
# Delegate enumerable methods to results
|
19
13
|
delegate :collect, :map, :each, :select, :to_a, :to_ary, :to => :results
|
20
14
|
|
21
|
-
# @!attribute [r] errors
|
22
|
-
# @return [ActiveModel::Errors] errors from determining results
|
23
|
-
#
|
24
15
|
# @!attribute [r] params
|
25
16
|
# @return [HashWithIndifferentAccess] filters parameters based on keys defined in mappings
|
26
|
-
|
17
|
+
#
|
18
|
+
# @!attribute [r] source
|
19
|
+
# @return [ActiveRecord::Relation]
|
20
|
+
attr_reader :params, :source
|
27
21
|
|
28
22
|
# Set `mappings` on inheritance to ensure they're unique per subclass
|
29
23
|
def self.inherited(klass)
|
30
24
|
klass.mappings = []
|
31
25
|
end
|
32
26
|
|
33
|
-
# Specify the ActiveRecord model to use as the source for queries
|
34
|
-
#
|
35
|
-
# @param [String, Symbol, ActiveRecord::Base] klass name of the class to
|
36
|
-
# use or the class itself
|
37
|
-
def self.paraphrases(klass)
|
38
|
-
if !klass.is_a?(Class)
|
39
|
-
klass = Object.const_get(klass.to_s.classify)
|
40
|
-
end
|
41
|
-
|
42
|
-
self.source = klass
|
43
|
-
end
|
44
|
-
|
45
27
|
# Add a {ScopeMapping} instance to {@@mappings .mappings}
|
46
28
|
#
|
47
29
|
# @see ScopeMapping#initialize
|
@@ -53,19 +35,20 @@ module Paraphrase
|
|
53
35
|
mappings << ScopeMapping.new(name, options)
|
54
36
|
end
|
55
37
|
|
56
|
-
# Filters out parameters irrelevant to the query
|
38
|
+
# Filters out parameters irrelevant to the query and sets the base scope
|
39
|
+
# for to begin the chain.
|
57
40
|
#
|
58
41
|
# @param [Hash] params query parameters
|
59
|
-
|
42
|
+
# @param [ActiveRecord::Base, ActiveRecord::Relation] source object to
|
43
|
+
# apply methods to
|
44
|
+
def initialize(params, class_or_relation)
|
60
45
|
keys = mappings.map(&:keys).flatten.map(&:to_s)
|
61
46
|
|
62
|
-
@relation = relation || source.scoped
|
63
|
-
|
64
47
|
@params = HashWithIndifferentAccess.new(params)
|
65
48
|
@params.select! { |key, value| keys.include?(key) }
|
66
49
|
@params.freeze
|
67
50
|
|
68
|
-
@
|
51
|
+
@source = class_or_relation.scoped
|
69
52
|
end
|
70
53
|
|
71
54
|
# Loops through {#mappings} and apply scope methods to {#source}. If values
|
@@ -75,11 +58,14 @@ module Paraphrase
|
|
75
58
|
def results
|
76
59
|
return @results if @results
|
77
60
|
|
78
|
-
|
79
|
-
|
80
|
-
|
61
|
+
ActiveSupport::Notifications.instrument('query.paraphrase', :params => params, :source_name => source.name, :source => source) do
|
62
|
+
@results = mappings.inject(source) do |query, scope|
|
63
|
+
query = scope.chain(params, query)
|
81
64
|
|
82
|
-
|
65
|
+
break [] if query.nil?
|
66
|
+
query
|
67
|
+
end
|
68
|
+
end
|
83
69
|
end
|
84
70
|
|
85
71
|
def respond_to?(name)
|
@@ -4,34 +4,36 @@ module Paraphrase
|
|
4
4
|
# @return [Array<Symbol>] param keys to extract
|
5
5
|
#
|
6
6
|
# @!attribute [r] method_name
|
7
|
-
# @return [Symbol] scope
|
7
|
+
# @return [Symbol] scope name
|
8
8
|
#
|
9
|
-
# @!attribute [r]
|
10
|
-
# @return [Hash] configuration options
|
11
|
-
#
|
12
|
-
# @!attribute [r] required_keys
|
9
|
+
# @!attribute [r] required
|
13
10
|
# @return [Array] keys required for query
|
14
11
|
#
|
15
|
-
# @!attribute [r]
|
12
|
+
# @!attribute [r] whitelist
|
16
13
|
# @return [Array] keys allowed to be nil
|
17
|
-
attr_reader :keys, :method_name, :
|
14
|
+
attr_reader :keys, :method_name, :required, :whitelist
|
18
15
|
|
19
16
|
# @param [Symbol] name name of the scope
|
20
17
|
# @param [Hash] options options to configure {ScopeMapping ScopeMapping} instance
|
21
|
-
# @option options [Symbol, Array<Symbol>] :
|
22
|
-
# @option options [true] :require lists
|
18
|
+
# @option options [Symbol, Array<Symbol>] :to param key(s) to extract values from
|
19
|
+
# @option options [true, Symbol, Array<Symbol>] :require lists all or a
|
20
|
+
# subset of param keys as required
|
21
|
+
# @option options [true, Symbol, Array<Symbol>] :whitelist lists all or a
|
22
|
+
# subset of param keys as whitelisted
|
23
23
|
def initialize(name, options)
|
24
24
|
@method_name = name
|
25
25
|
@keys = Array(options.delete(:to))
|
26
26
|
|
27
27
|
@required = register_keys(options[:require])
|
28
|
-
@whitelist = register_keys(options[:
|
28
|
+
@whitelist = register_keys(options[:whitelist])
|
29
29
|
|
30
30
|
if @whitelist.empty? && !@required.empty?
|
31
31
|
@whitelist = @keys - @required
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
if (whitelist & required).any?
|
35
|
+
raise ArgumentError, "cannot whitelist and require the same keys"
|
36
|
+
end
|
35
37
|
end
|
36
38
|
|
37
39
|
# Sends {#method_name} to `chain`, extracting arguments from `params`. If
|
@@ -39,23 +41,30 @@ module Paraphrase
|
|
39
41
|
# If {#required? required}, errors are added to the {Query} instance as
|
40
42
|
# well.
|
41
43
|
#
|
42
|
-
# @param [Query] query {Query} instance applying the scope
|
43
44
|
# @param [Hash] params hash of query parameters
|
44
|
-
# @param [ActiveRecord::Relation, ActiveRecord::Base]
|
45
|
+
# @param [ActiveRecord::Relation, ActiveRecord::Base] relation scope chain
|
45
46
|
# @return [ActiveRecord::Relation]
|
46
|
-
def chain(
|
47
|
+
def chain(params, relation)
|
48
|
+
scope = relation.klass.method(method_name)
|
49
|
+
|
47
50
|
inputs = keys.map do |key|
|
48
51
|
input = params[key]
|
49
52
|
|
50
|
-
if input.nil?
|
51
|
-
|
52
|
-
break []
|
53
|
+
if input.nil?
|
54
|
+
break if required.include?(key)
|
55
|
+
break [] if !whitelist.include?(key)
|
53
56
|
end
|
54
57
|
|
55
58
|
input
|
56
59
|
end
|
57
60
|
|
58
|
-
inputs.
|
61
|
+
if inputs.nil?
|
62
|
+
return
|
63
|
+
elsif inputs.empty?
|
64
|
+
return relation
|
65
|
+
end
|
66
|
+
|
67
|
+
scope.arity == 0 ? scope.call : scope.call(*inputs)
|
59
68
|
end
|
60
69
|
|
61
70
|
private
|
data/lib/paraphrase/syntax.rb
CHANGED
@@ -3,26 +3,31 @@ module Paraphrase
|
|
3
3
|
module Base
|
4
4
|
def self.extended(klass)
|
5
5
|
klass.instance_eval do
|
6
|
-
class_attribute :
|
6
|
+
class_attribute :_paraphraser, :instance_writer => false, :instance_reader => false
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# the block.
|
10
|
+
# Create a {Query} subclass from a block using the `Query` DSL to map
|
11
|
+
# scopes to param keys
|
13
12
|
#
|
14
|
-
# @param [Proc] &block block to define scope mappings
|
13
|
+
# @param [Proc] &block block to to define scope mappings
|
15
14
|
def register_mapping(&block)
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
self._paraphraser = Class.new(Query, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Attempts to find paraphrase class based on class name. Override if
|
19
|
+
# using a different naming convention.
|
20
|
+
def paraphraser
|
21
|
+
self._paraphraser || "#{self.name}Query".constantize
|
22
|
+
rescue
|
23
|
+
nil
|
19
24
|
end
|
20
25
|
|
21
26
|
# Instantiate the {Query} class that is mapped to `self`.
|
22
27
|
#
|
23
28
|
# @param [Hash] params query parameters
|
24
29
|
def paraphrase(params)
|
25
|
-
self.paraphraser.new(params)
|
30
|
+
self.paraphraser.new(params, self)
|
26
31
|
end
|
27
32
|
end
|
28
33
|
|
@@ -53,7 +58,7 @@ module Paraphrase
|
|
53
58
|
# # => SELECT `posts`.* FROM `posts` INNER JOIN `users` ON `users`.`post_id` = `posts`.`id` WHERE `posts`.`title LIKE "%Game of Thrones Finale%";
|
54
59
|
# ```
|
55
60
|
#
|
56
|
-
# @param [Hash] params query
|
61
|
+
# @param [Hash] params query parameters
|
57
62
|
# @return [Paraphrase::Query]
|
58
63
|
def paraphrase(params)
|
59
64
|
klass.paraphraser.new(params, self)
|
data/lib/paraphrase/version.rb
CHANGED
data/paraphrase.gemspec
CHANGED
@@ -24,7 +24,6 @@ Gem::Specification.new do |gem|
|
|
24
24
|
|
25
25
|
gem.add_dependency 'activerecord', '~> 3.0'
|
26
26
|
gem.add_dependency 'activesupport', '~> 3.0'
|
27
|
-
gem.add_dependency 'activemodel', '~> 3.0'
|
28
27
|
|
29
28
|
gem.add_development_dependency 'bundler', '~> 1.0'
|
30
29
|
gem.add_development_dependency 'yard', '~> 0.7'
|
@@ -32,5 +31,5 @@ Gem::Specification.new do |gem|
|
|
32
31
|
gem.add_development_dependency 'redcarpet', '~> 2.1.1'
|
33
32
|
gem.add_development_dependency 'rake', '~> 0.9.2'
|
34
33
|
gem.add_development_dependency 'sqlite3', '~> 1.3.6'
|
35
|
-
gem.add_development_dependency 'appraisal', '0.4'
|
34
|
+
gem.add_development_dependency 'appraisal', '~> 0.4'
|
36
35
|
end
|
@@ -2,34 +2,22 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Paraphrase
|
4
4
|
describe Query do
|
5
|
+
subject { AccountQuery }
|
6
|
+
let(:query) { Account.paraphrase(:name => 'Tyrion Lannister', :nickname => 'Half Man') }
|
5
7
|
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
describe ".paraphrases" do
|
10
|
-
it "stores the class being queried" do
|
11
|
-
AccountSearch.paraphrases :account
|
12
|
-
AccountSearch.source.should eq Account
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe ".scope" do
|
8
|
+
describe ".map" do
|
17
9
|
it "adds information to Query.mappings" do
|
18
|
-
|
19
|
-
map :name_like, :to => :name
|
20
|
-
end
|
10
|
+
subject.map :name_like, :to => :name
|
21
11
|
|
22
|
-
|
12
|
+
subject.mappings.should_not be_empty
|
23
13
|
end
|
24
14
|
|
25
15
|
it "raises an error if a scope is added twice" do
|
26
|
-
expect {
|
16
|
+
expect { subject.map :name_like, :to => :name }.to raise_error Paraphrase::DuplicateScopeError
|
27
17
|
end
|
28
18
|
end
|
29
19
|
|
30
|
-
describe "
|
31
|
-
let(:query) { AccountSearch.new(:name => 'Tyrion Lannister', :nickname => 'Half Man') }
|
32
|
-
|
20
|
+
describe "on initialization" do
|
33
21
|
it "filters out params not specified in mappings" do
|
34
22
|
query.params.should_not have_key :nickname
|
35
23
|
query.params.should have_key :name
|
@@ -38,38 +26,24 @@ module Paraphrase
|
|
38
26
|
it "sets up params with indifferent access" do
|
39
27
|
query.params.should have_key 'name'
|
40
28
|
end
|
41
|
-
|
42
|
-
it "accepts an ActiveRecord::Relation to use as the base scope" do
|
43
|
-
user = User.create!
|
44
|
-
associated_account = Account.create!(:user => user)
|
45
|
-
lonely_account = Account.create!
|
46
|
-
|
47
|
-
results = AccountSearch.new({ :name => 'Tyrion Lannister'}, user.accounts).results
|
48
|
-
|
49
|
-
results.should include associated_account
|
50
|
-
results.should_not include lonely_account
|
51
|
-
end
|
52
29
|
end
|
53
30
|
|
54
31
|
describe "#results" do
|
55
32
|
before :all do
|
56
|
-
|
57
|
-
map :title_like, :to => :title, :require => true
|
58
|
-
end
|
33
|
+
subject.map :title_like, :to => :title, :require => true
|
59
34
|
end
|
60
35
|
|
61
36
|
it "loops through scope methods and applies them to source" do
|
62
37
|
Account.should_receive(:title_like).and_return(Account.scoped)
|
63
38
|
Account.should_receive(:name_like).and_return(Account.scoped)
|
64
39
|
|
65
|
-
query =
|
40
|
+
query = Account.paraphrase(:name => 'Jon Snow', :title => 'Wall Watcher')
|
66
41
|
query.results
|
67
42
|
end
|
68
43
|
|
69
|
-
it "returns empty array if
|
70
|
-
query =
|
44
|
+
it "returns empty array if inputs were missing and required" do
|
45
|
+
query = Account.paraphrase({})
|
71
46
|
query.results.should eq []
|
72
|
-
query.errors.should_not be_empty
|
73
47
|
end
|
74
48
|
end
|
75
49
|
end
|
@@ -4,41 +4,30 @@ module Paraphrase
|
|
4
4
|
describe ScopeMapping do
|
5
5
|
let(:mapping) { ScopeMapping.new(:name_like, :to => :name) }
|
6
6
|
let(:compound_mapping) { ScopeMapping.new(:name_like, :to => [:first_name, :last_name], :require => :last_name) }
|
7
|
-
let(:query)
|
8
|
-
query = double('query')
|
9
|
-
query.stub(:errors).and_return(double('errors'))
|
10
|
-
query
|
11
|
-
end
|
7
|
+
let(:query) { Account.scoped }
|
12
8
|
|
13
9
|
describe "#chain" do
|
14
10
|
it "applies scope method to query object with values from params hash" do
|
15
11
|
Account.should_receive(:name_like).with('Jon Snow')
|
16
|
-
mapping.chain(
|
12
|
+
mapping.chain({ :name => 'Jon Snow' }, query)
|
17
13
|
end
|
18
14
|
|
19
15
|
it "does nothing if values are missing" do
|
20
16
|
Account.should_not_receive(:name_like).with('Jon Snow')
|
21
|
-
mapping.chain(
|
22
|
-
end
|
23
|
-
|
24
|
-
it "adds errors to query object if missing and required" do
|
25
|
-
required_mapping = ScopeMapping.new(:name_like, :to => :name, :require => true)
|
26
|
-
|
27
|
-
query.errors.should_receive(:add)
|
28
|
-
required_mapping.chain(query, {}, Account)
|
17
|
+
mapping.chain({}, query)
|
29
18
|
end
|
30
19
|
|
31
20
|
it "passes through nil values if scope has been whitelisted" do
|
32
|
-
mapping = ScopeMapping.new(:name_like, :to => :name, :
|
21
|
+
mapping = ScopeMapping.new(:name_like, :to => :name, :whitelist => true)
|
33
22
|
|
34
23
|
Account.should_receive(:name_like).with(nil)
|
35
|
-
mapping.chain(
|
24
|
+
mapping.chain({}, query)
|
36
25
|
end
|
37
26
|
end
|
38
27
|
|
39
28
|
it "can require a subset of a compound key" do
|
40
29
|
Account.should_receive(:name_like).with(nil, 'Lannister')
|
41
|
-
compound_mapping.chain(
|
30
|
+
compound_mapping.chain({ :last_name => 'Lannister' }, query)
|
42
31
|
end
|
43
32
|
|
44
33
|
it "whitelists the the non-required keys of a compound key" do
|
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
module Paraphrase
|
4
|
-
|
5
|
-
describe
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module Syntax
|
5
|
+
describe Base do
|
6
|
+
describe "#register_mapping" do
|
7
|
+
it "creates new sublcass of Query" do
|
8
|
+
Account.register_mapping {}
|
9
|
+
Account._paraphraser.should_not be_nil
|
10
|
+
end
|
9
11
|
end
|
10
12
|
end
|
11
13
|
end
|
data/spec/support/database.rb
CHANGED
@@ -28,7 +28,14 @@ class Account < ActiveRecord::Base
|
|
28
28
|
extend Paraphrase::Syntax::Base
|
29
29
|
belongs_to :user
|
30
30
|
|
31
|
+
def self.title_like(*args)
|
32
|
+
scoped
|
33
|
+
end
|
34
|
+
|
31
35
|
def self.name_like(*args)
|
32
36
|
scoped
|
33
37
|
end
|
34
38
|
end
|
39
|
+
|
40
|
+
class AccountQuery < Paraphrase::Query
|
41
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paraphrase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -43,22 +43,6 @@ dependencies:
|
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '3.0'
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: activemodel
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
|
-
requirements:
|
51
|
-
- - ~>
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '3.0'
|
54
|
-
type: :runtime
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '3.0'
|
62
46
|
- !ruby/object:Gem::Dependency
|
63
47
|
name: bundler
|
64
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -160,7 +144,7 @@ dependencies:
|
|
160
144
|
requirement: !ruby/object:Gem::Requirement
|
161
145
|
none: false
|
162
146
|
requirements:
|
163
|
-
- -
|
147
|
+
- - ~>
|
164
148
|
- !ruby/object:Gem::Version
|
165
149
|
version: '0.4'
|
166
150
|
type: :development
|
@@ -168,7 +152,7 @@ dependencies:
|
|
168
152
|
version_requirements: !ruby/object:Gem::Requirement
|
169
153
|
none: false
|
170
154
|
requirements:
|
171
|
-
- -
|
155
|
+
- - ~>
|
172
156
|
- !ruby/object:Gem::Version
|
173
157
|
version: '0.4'
|
174
158
|
description: ! "\n Map query parameters to model scopes, pairing
|