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 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
- paraphrase provides a way to map one or multiple request params to model
4
- scopes.
5
-
6
- paraphrase is geared towards building a query-based public API where you may
7
- want to require certain parameters to prevent consumers from scraping all your
8
- information or to mitigate large, performance-intensive database queries.
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 gemfile:
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
- ### Setup
26
+ Create a subclass of `Paraphrase::Query` or call `register_mapping` from within
27
+ your model to setup mappings.
31
28
 
32
- `Paraphrase::Query` classes can be created in the following ways:
29
+ ```ruby
30
+ # app/queries/post_query.rb
31
+ class PostQuery < Paraphrase::Query
32
+ map :by_user, :to => :author
33
+ end
33
34
 
34
- * Calling `register_mapping` in an `ActiveRecord::Base` subclass:
35
+ # or
35
36
 
36
- ```ruby
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(author_name)
43
- joins(:user).where(:user => { :name => author_name })
45
+ def self.by_user(author)
46
+ where(:user => { :name => author })
44
47
  end
45
48
  end
46
49
  ```
47
50
 
48
- * Subclassing `Paraphrase::Query`:
49
-
50
- ```ruby
51
- class PostQuery < Paraphrase::Query
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 scope queries from an association, useful if you have nested resources:
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/1/posts
75
+ # GET /users/:id/posts
85
76
  def index
86
77
  @user = User.find(params[:user_id])
87
78
 
88
- # This will do a join on the users table, scoping posts to the current user
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
- ### Configuring Mappings
90
+ ### Query Class DSL
97
91
 
98
- In any of these contexts, the `:to` option of the `:map` method registers
99
- attribute(s) to extract from the params supplied and what method to pass them
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 Post < ActiveRecord::Base
104
- register_mapping do
105
- map :by_user, :to => [:first_name, :last_name]
106
- map :published_on, :to => :pub_date
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, pass `:require =>
120
- true` or `:require => [:array, :of, :keys]` to the options. If any values are
121
- missing for the scope, an empty result set will be returned. If a key is an
122
- array of attributes, you can specify a subset of the key to be required. The
123
- rest of the attributes will be allowed to be nil.
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
- map :by_author, :to => [:first_name, :last_name], :require => :last_name # requires :last_name, whitelists :first_name
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 can be whitelisted allowing nil values to be passed to the 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
- map :by_author, :to => [:first_name, :last_name], :allow_nil => :first_name # :first_name does not need to be specified
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
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/edd_d/src/paraphrase
3
3
  specs:
4
- paraphrase (0.4.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)
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/edd_d/src/paraphrase
3
3
  specs:
4
- paraphrase (0.4.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)
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/edd_d/src/paraphrase
3
3
  specs:
4
- paraphrase (0.4.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)
@@ -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
- attr_reader :errors, :params
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
- def initialize(params = {}, relation = nil)
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
- @errors = ActiveModel::Errors.new(self)
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
- results = mappings.inject(@relation) do |query, scope|
79
- scope.chain(self, @params, query)
80
- end
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
- @results = @errors.any? ? [] : results
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 method name
7
+ # @return [Symbol] scope name
8
8
  #
9
- # @!attribute [r] options
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] whitelist_keys
12
+ # @!attribute [r] whitelist
16
13
  # @return [Array] keys allowed to be nil
17
- attr_reader :keys, :method_name, :options, :required, :whitelist
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>] :key param key(s) to extract values from
22
- # @option options [true] :require lists scope as required
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[:allow_nil])
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
- @options = options.freeze
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] chain current model scope
45
+ # @param [ActiveRecord::Relation, ActiveRecord::Base] relation scope chain
45
46
  # @return [ActiveRecord::Relation]
46
- def chain(query, params, 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? && ( required.include?(key) || !whitelist.include?(key) )
51
- query.errors.add(key, 'is required') if required.include?(key)
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.empty? ? chain : chain.send(method_name, *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
@@ -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 :paraphraser, :instance_writer => false, :instance_reader => false
6
+ class_attribute :_paraphraser, :instance_writer => false, :instance_reader => false
7
7
  end
8
8
  end
9
9
 
10
- # Register a {Query} class mapped to `self`. If the mapping has already
11
- # been registered, calling again will clear existing scopes and evaluate
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
- klass = Class.new(Query, &block)
17
- klass.source = self
18
- self.paraphraser = klass
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 params
61
+ # @param [Hash] params query parameters
57
62
  # @return [Paraphrase::Query]
58
63
  def paraphrase(params)
59
64
  klass.paraphraser.new(params, self)
@@ -1,3 +1,3 @@
1
1
  module Paraphrase
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
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
- class AccountSearch < Query
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
- AccountSearch.instance_eval do
19
- map :name_like, :to => :name
20
- end
10
+ subject.map :name_like, :to => :name
21
11
 
22
- AccountSearch.mappings.should_not be_empty
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 { AccountSearch.instance_eval { map :name_like, :to => :name } }.to raise_error Paraphrase::DuplicateScopeError
16
+ expect { subject.map :name_like, :to => :name }.to raise_error Paraphrase::DuplicateScopeError
27
17
  end
28
18
  end
29
19
 
30
- describe "#initialize" do
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
- AccountSearch.instance_eval do
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 = AccountSearch.new(:name => 'Jon Snow', :title => 'Wall Watcher')
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 errors were added" do
70
- query = AccountSearch.new
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) do
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(query, { :name => 'Jon Snow' }, Account)
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(query, {}, Account)
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, :allow_nil => true)
21
+ mapping = ScopeMapping.new(:name_like, :to => :name, :whitelist => true)
33
22
 
34
23
  Account.should_receive(:name_like).with(nil)
35
- mapping.chain(query, {}, Account)
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(query, { :last_name => 'Lannister' }, Account)
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
- describe Syntax do
5
- describe ".register_mapping" do
6
- it "creates new sublcass of Query" do
7
- Account.register_mapping {}
8
- Account.paraphraser.should_not be_nil
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
@@ -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.5.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-08 00:00:00.000000000 Z
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