paraphrase 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|