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