paraphrase 0.2.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/.document +3 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/Appraisals +17 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.md +134 -0
- data/Rakefile +11 -0
- data/gemfiles/3.0.gemfile +9 -0
- data/gemfiles/3.0.gemfile.lock +57 -0
- data/gemfiles/3.1.gemfile +9 -0
- data/gemfiles/3.1.gemfile.lock +59 -0
- data/gemfiles/3.2.gemfile +9 -0
- data/gemfiles/3.2.gemfile.lock +59 -0
- data/lib/paraphrase/errors.rb +4 -0
- data/lib/paraphrase/query.rb +86 -0
- data/lib/paraphrase/rails.rb +11 -0
- data/lib/paraphrase/scope_mapping.rb +55 -0
- data/lib/paraphrase/syntax.rb +19 -0
- data/lib/paraphrase/version.rb +3 -0
- data/lib/paraphrase.rb +65 -0
- data/paraphrase.gemspec +31 -0
- data/spec/paraphrase/query_spec.rb +62 -0
- data/spec/paraphrase/scope_mapping_spec.rb +36 -0
- data/spec/paraphrase/syntax_spec.rb +19 -0
- data/spec/paraphrase_spec.rb +43 -0
- data/spec/spec_helper.rb +35 -0
- metadata +190 -0
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title "paraphrase Documentation" --protected -M github-markup -M redcarpet
|
data/Appraisals
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
appraise '3.0' do
|
2
|
+
gem 'activerecord', '3.0.15'
|
3
|
+
gem 'activemodel', '3.0.15'
|
4
|
+
gem 'activesupport', '3.0.15'
|
5
|
+
end
|
6
|
+
|
7
|
+
appraise '3.1' do
|
8
|
+
gem 'activerecord', '3.1.6'
|
9
|
+
gem 'activemodel', '3.1.6'
|
10
|
+
gem 'activesupport', '3.1.6'
|
11
|
+
end
|
12
|
+
|
13
|
+
appraise '3.2' do
|
14
|
+
gem 'activerecord', '3.2.6'
|
15
|
+
gem 'activemodel', '3.2.6'
|
16
|
+
gem 'activesupport', '3.2.6'
|
17
|
+
end
|
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Eduardo Gutierrez
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# paraphrase
|
2
|
+
|
3
|
+
paraphrase provides a way to map one or multiple request params to model
|
4
|
+
scopes.
|
5
|
+
|
6
|
+
paraphrase was designed and geared towards building a query-based public API
|
7
|
+
where you may want to require certain parameters to prevent consumers from
|
8
|
+
scraping all your information or to mitigate the possibility of large,
|
9
|
+
performance-intensive data-dumps.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Via a gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'paraphrase'
|
17
|
+
```
|
18
|
+
|
19
|
+
```
|
20
|
+
$ bundle
|
21
|
+
```
|
22
|
+
|
23
|
+
Or manually:
|
24
|
+
|
25
|
+
```
|
26
|
+
$ gem install paraphrase
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
paraphrase aims to be as flexible as possible for your needs.
|
32
|
+
* From within an `ActiveRecord::Base` subclass:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class Post < ActiveRecord::Base
|
36
|
+
register_mapping do
|
37
|
+
scope :by_user, :key => :author
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.by_user(author_name)
|
41
|
+
joins(:user).where(:user => { :name => author_name })
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
* In an initializer to register multiple mappings in one place:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# config/initializers/paraphrase.rb
|
50
|
+
|
51
|
+
Paraphrase.configure do |mappings|
|
52
|
+
mappings.register :post do
|
53
|
+
paraphrases Post
|
54
|
+
scope :by_user, :key => :author
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
* By creating a subclass of `Paraphrase::Query`:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class PostQuery < Paraphrase::Query
|
63
|
+
paraphrases Post
|
64
|
+
|
65
|
+
scope :by_user, :key => :author
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
Then in a controller you can use it in any of the following ways:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class PostsController < ApplicationController
|
73
|
+
respond_to :html, :json
|
74
|
+
|
75
|
+
def index
|
76
|
+
# Filters out relevant attributes
|
77
|
+
# and applies scopes relevant to each
|
78
|
+
# parameter
|
79
|
+
@posts = Post.paraphrase(params)
|
80
|
+
|
81
|
+
# Or
|
82
|
+
# @posts = Paraphrase.query(:post, params)
|
83
|
+
|
84
|
+
# If you created a subclass
|
85
|
+
# @posts = PostQuery.new(params)
|
86
|
+
|
87
|
+
respond_with(@posts)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
In any of these contexts, the `:key` option of the `:scope` method registers
|
93
|
+
attribute(s) to extract from the params supplied and what scope to pass them
|
94
|
+
to. An array of keys can be supplied to pass multiple attributes to a scope.
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
class Post < ActiveRecord::Base
|
98
|
+
register_mapping do
|
99
|
+
scope :by_user, :key => [:first_name, :last_name]
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.by_user(name)
|
103
|
+
joins(:user).where(:user => { :name => name })
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
If a key is required, pass `:require => true` to the options. This will
|
109
|
+
return an empty results set if value for that key is missing.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
class Post < ActiveRecord::Base
|
113
|
+
register_mapping do
|
114
|
+
scope :by_author, :key => :author, :require => true
|
115
|
+
scope :published_after, :key => :pub_date
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
Post.paraphrase(:pub_date => '2010-10-30') # => []
|
120
|
+
```
|
121
|
+
|
122
|
+
## Plans
|
123
|
+
|
124
|
+
* Enable requiring a subset of a compound key.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
scope :by_author, :key => [:first_name, :last_name], :require => :first_name
|
128
|
+
```
|
129
|
+
|
130
|
+
* Support nested hashes in params.
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
scope :by_author, :key => { :author => [:first_name, :last_name] }
|
134
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /Users/edd_d/src/paraphrase
|
3
|
+
specs:
|
4
|
+
paraphrase (0.1.0)
|
5
|
+
activemodel (~> 3.0)
|
6
|
+
activerecord (~> 3.0)
|
7
|
+
activesupport (~> 3.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (3.0.15)
|
13
|
+
activesupport (= 3.0.15)
|
14
|
+
builder (~> 2.1.2)
|
15
|
+
i18n (~> 0.5.0)
|
16
|
+
activerecord (3.0.15)
|
17
|
+
activemodel (= 3.0.15)
|
18
|
+
activesupport (= 3.0.15)
|
19
|
+
arel (~> 2.0.10)
|
20
|
+
tzinfo (~> 0.3.23)
|
21
|
+
activesupport (3.0.15)
|
22
|
+
appraisal (0.4.1)
|
23
|
+
bundler
|
24
|
+
rake
|
25
|
+
arel (2.0.10)
|
26
|
+
builder (2.1.2)
|
27
|
+
diff-lcs (1.1.3)
|
28
|
+
i18n (0.5.0)
|
29
|
+
rake (0.9.2.2)
|
30
|
+
redcarpet (2.1.1)
|
31
|
+
rspec (2.10.0)
|
32
|
+
rspec-core (~> 2.10.0)
|
33
|
+
rspec-expectations (~> 2.10.0)
|
34
|
+
rspec-mocks (~> 2.10.0)
|
35
|
+
rspec-core (2.10.1)
|
36
|
+
rspec-expectations (2.10.0)
|
37
|
+
diff-lcs (~> 1.1.3)
|
38
|
+
rspec-mocks (2.10.1)
|
39
|
+
sqlite3 (1.3.6)
|
40
|
+
tzinfo (0.3.33)
|
41
|
+
yard (0.8.2.1)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
activemodel (= 3.0.15)
|
48
|
+
activerecord (= 3.0.15)
|
49
|
+
activesupport (= 3.0.15)
|
50
|
+
appraisal
|
51
|
+
bundler (~> 1.0)
|
52
|
+
paraphrase!
|
53
|
+
rake
|
54
|
+
redcarpet
|
55
|
+
rspec (~> 2.10)
|
56
|
+
sqlite3
|
57
|
+
yard (~> 0.7)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /Users/edd_d/src/paraphrase
|
3
|
+
specs:
|
4
|
+
paraphrase (0.1.0)
|
5
|
+
activemodel (~> 3.0)
|
6
|
+
activerecord (~> 3.0)
|
7
|
+
activesupport (~> 3.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (3.1.6)
|
13
|
+
activesupport (= 3.1.6)
|
14
|
+
builder (~> 3.0.0)
|
15
|
+
i18n (~> 0.6)
|
16
|
+
activerecord (3.1.6)
|
17
|
+
activemodel (= 3.1.6)
|
18
|
+
activesupport (= 3.1.6)
|
19
|
+
arel (~> 2.2.3)
|
20
|
+
tzinfo (~> 0.3.29)
|
21
|
+
activesupport (3.1.6)
|
22
|
+
multi_json (>= 1.0, < 1.3)
|
23
|
+
appraisal (0.4.1)
|
24
|
+
bundler
|
25
|
+
rake
|
26
|
+
arel (2.2.3)
|
27
|
+
builder (3.0.0)
|
28
|
+
diff-lcs (1.1.3)
|
29
|
+
i18n (0.6.0)
|
30
|
+
multi_json (1.2.0)
|
31
|
+
rake (0.9.2.2)
|
32
|
+
redcarpet (2.1.1)
|
33
|
+
rspec (2.10.0)
|
34
|
+
rspec-core (~> 2.10.0)
|
35
|
+
rspec-expectations (~> 2.10.0)
|
36
|
+
rspec-mocks (~> 2.10.0)
|
37
|
+
rspec-core (2.10.1)
|
38
|
+
rspec-expectations (2.10.0)
|
39
|
+
diff-lcs (~> 1.1.3)
|
40
|
+
rspec-mocks (2.10.1)
|
41
|
+
sqlite3 (1.3.6)
|
42
|
+
tzinfo (0.3.33)
|
43
|
+
yard (0.8.2.1)
|
44
|
+
|
45
|
+
PLATFORMS
|
46
|
+
ruby
|
47
|
+
|
48
|
+
DEPENDENCIES
|
49
|
+
activemodel (= 3.1.6)
|
50
|
+
activerecord (= 3.1.6)
|
51
|
+
activesupport (= 3.1.6)
|
52
|
+
appraisal
|
53
|
+
bundler (~> 1.0)
|
54
|
+
paraphrase!
|
55
|
+
rake
|
56
|
+
redcarpet
|
57
|
+
rspec (~> 2.10)
|
58
|
+
sqlite3
|
59
|
+
yard (~> 0.7)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /Users/edd_d/src/paraphrase
|
3
|
+
specs:
|
4
|
+
paraphrase (0.1.0)
|
5
|
+
activemodel (~> 3.0)
|
6
|
+
activerecord (~> 3.0)
|
7
|
+
activesupport (~> 3.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (3.2.6)
|
13
|
+
activesupport (= 3.2.6)
|
14
|
+
builder (~> 3.0.0)
|
15
|
+
activerecord (3.2.6)
|
16
|
+
activemodel (= 3.2.6)
|
17
|
+
activesupport (= 3.2.6)
|
18
|
+
arel (~> 3.0.2)
|
19
|
+
tzinfo (~> 0.3.29)
|
20
|
+
activesupport (3.2.6)
|
21
|
+
i18n (~> 0.6)
|
22
|
+
multi_json (~> 1.0)
|
23
|
+
appraisal (0.4.1)
|
24
|
+
bundler
|
25
|
+
rake
|
26
|
+
arel (3.0.2)
|
27
|
+
builder (3.0.0)
|
28
|
+
diff-lcs (1.1.3)
|
29
|
+
i18n (0.6.0)
|
30
|
+
multi_json (1.3.6)
|
31
|
+
rake (0.9.2.2)
|
32
|
+
redcarpet (2.1.1)
|
33
|
+
rspec (2.10.0)
|
34
|
+
rspec-core (~> 2.10.0)
|
35
|
+
rspec-expectations (~> 2.10.0)
|
36
|
+
rspec-mocks (~> 2.10.0)
|
37
|
+
rspec-core (2.10.1)
|
38
|
+
rspec-expectations (2.10.0)
|
39
|
+
diff-lcs (~> 1.1.3)
|
40
|
+
rspec-mocks (2.10.1)
|
41
|
+
sqlite3 (1.3.6)
|
42
|
+
tzinfo (0.3.33)
|
43
|
+
yard (0.8.2.1)
|
44
|
+
|
45
|
+
PLATFORMS
|
46
|
+
ruby
|
47
|
+
|
48
|
+
DEPENDENCIES
|
49
|
+
activemodel (= 3.2.6)
|
50
|
+
activerecord (= 3.2.6)
|
51
|
+
activesupport (= 3.2.6)
|
52
|
+
appraisal
|
53
|
+
bundler (~> 1.0)
|
54
|
+
paraphrase!
|
55
|
+
rake
|
56
|
+
redcarpet
|
57
|
+
rspec (~> 2.10)
|
58
|
+
sqlite3
|
59
|
+
yard (~> 0.7)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
4
|
+
require 'active_model/naming'
|
5
|
+
require 'active_model/errors'
|
6
|
+
|
7
|
+
module Paraphrase
|
8
|
+
class Query
|
9
|
+
extend ActiveModel::Naming
|
10
|
+
|
11
|
+
# @!attribute [r] scopes
|
12
|
+
# @return [Array<ScopeMapping>] scopes for query
|
13
|
+
#
|
14
|
+
# @!attribute [r] source
|
15
|
+
# @return [ActiveRecord::Relation] source to apply scopes to
|
16
|
+
cattr_reader :scopes, :source
|
17
|
+
@@scopes = []
|
18
|
+
|
19
|
+
|
20
|
+
# Delegate enumerable methods to results
|
21
|
+
delegate :collect, :map, :each, :select, :to_a, :to_ary, :to => :results
|
22
|
+
|
23
|
+
|
24
|
+
# @!attribute [r] errors
|
25
|
+
# @return [ActiveModel::Errors] errors from determining results
|
26
|
+
#
|
27
|
+
# @!attribute [r] params
|
28
|
+
# @return [Hash] filters parameters based on keys defined in scopes
|
29
|
+
attr_reader :errors, :params
|
30
|
+
|
31
|
+
|
32
|
+
# Specify the ActiveRecord model to use as the source for queries
|
33
|
+
#
|
34
|
+
# @param [String, Symbol, ActiveRecord::Base] klass name of the class to
|
35
|
+
# use or the class itself
|
36
|
+
def self.paraphrases(klass)
|
37
|
+
if !klass.is_a?(Class)
|
38
|
+
klass = Object.const_get(klass.to_s.classify)
|
39
|
+
end
|
40
|
+
|
41
|
+
@@source = klass.scoped
|
42
|
+
|
43
|
+
Paraphrase.add(klass.name, self)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Add a {ScopeMapping} instance to {@@scopes .scopes}
|
48
|
+
#
|
49
|
+
# @see ScopeMapping#initialize
|
50
|
+
def self.scope(name, options)
|
51
|
+
if @@scopes.map(&:method_name).include?(name)
|
52
|
+
raise DuplicateScopeError, "scope :#{name} has already been added"
|
53
|
+
end
|
54
|
+
|
55
|
+
@@scopes << ScopeMapping.new(name, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# Filters out parameters irrelevant to the query
|
60
|
+
#
|
61
|
+
# @param [Hash] params query parameters
|
62
|
+
def initialize(params = {})
|
63
|
+
keys = scopes.map(&:param_keys).flatten
|
64
|
+
@params = params.dup
|
65
|
+
@params.select! { |key, value| keys.include?(key) }
|
66
|
+
@params.freeze
|
67
|
+
|
68
|
+
@errors = ActiveModel::Errors.new(self)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Loops through {#scopes} and apply scope methods to {#source}. If values
|
73
|
+
# are missing for a required key, an empty array is returned.
|
74
|
+
#
|
75
|
+
# @return [ActiveRecord::Relation, Array]
|
76
|
+
def results
|
77
|
+
results ||= scopes.inject(source) do |query, scope|
|
78
|
+
scope.chain(self, @params, query)
|
79
|
+
end
|
80
|
+
|
81
|
+
@results = @errors.any? ? [] : results
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
require 'paraphrase/scope_mapping'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Paraphrase
|
2
|
+
class ScopeMapping
|
3
|
+
# @!attribute [r] param_keys
|
4
|
+
# @return [Array<Symbol>] param keys to extract
|
5
|
+
#
|
6
|
+
# @!attribute [r] method_name
|
7
|
+
# @return [Symbol] scope method name
|
8
|
+
#
|
9
|
+
# @!attribute [r] options
|
10
|
+
# @return [Hash] configuration options
|
11
|
+
attr_reader :param_keys, :method_name, :options
|
12
|
+
|
13
|
+
|
14
|
+
# @param [Symbol] name name of the scope
|
15
|
+
# @param [Hash] options options to configure {ScopeMapping ScopeMapping} instance
|
16
|
+
# @option options [Symbol, Array<Symbol>] :key param key(s) to extract values from
|
17
|
+
# @option options [true] :require lists scope as required
|
18
|
+
def initialize(name, options)
|
19
|
+
@method_name = name
|
20
|
+
@param_keys = [options.delete(:key)].flatten
|
21
|
+
|
22
|
+
@options = options.freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Checks if scope is required for query
|
27
|
+
def required?
|
28
|
+
!options[:require].nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Sends {#method_name} to `chain`, extracting arguments from `params`. If
|
33
|
+
# values are missing for any {#param_keys}, return the `chain` unmodified.
|
34
|
+
# If {#required? required}, errors are added to the {Query} instance as
|
35
|
+
# well.
|
36
|
+
#
|
37
|
+
# @param [Query] query {Query} instance applying the scope
|
38
|
+
# @param [Hash] params hash of query parameters
|
39
|
+
# @param [ActiveRecord::Relation, ActiveRecord::Base] chain current model scope
|
40
|
+
# @return [ActiveRecord::Relation]
|
41
|
+
def chain(query, params, chain)
|
42
|
+
inputs = param_keys.map { |key| params[key] }
|
43
|
+
|
44
|
+
if inputs.include?(nil)
|
45
|
+
param_keys.each do |key|
|
46
|
+
query.errors.add(key, 'is required')
|
47
|
+
end if required?
|
48
|
+
|
49
|
+
chain
|
50
|
+
else
|
51
|
+
chain.send(method_name, *inputs)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Paraphrase
|
2
|
+
module Syntax
|
3
|
+
|
4
|
+
# Register a {Query} class mapped to `self`.
|
5
|
+
#
|
6
|
+
# @param [Proc] &block block to define scope mappings
|
7
|
+
def register_mapping(&block)
|
8
|
+
Paraphrase.register(self.name, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
# Instantiate the {Query} class that is mapped to `self`.
|
13
|
+
#
|
14
|
+
# @param [Hash] params query parameters
|
15
|
+
def paraphrase(params)
|
16
|
+
Paraphrase.query(self.name.underscore, params)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/paraphrase.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
module Paraphrase
|
6
|
+
|
7
|
+
@@mappings = {}.with_indifferent_access
|
8
|
+
|
9
|
+
# Allows for configuring multiple {Query} classes in a single block. Useful
|
10
|
+
# in an initializer.
|
11
|
+
def self.configure(&block)
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
# Retreive a registered Query subclass.
|
17
|
+
#
|
18
|
+
# @param [String, Symbol] name of the class underscored
|
19
|
+
# @return [Query]
|
20
|
+
def self.mapping(name)
|
21
|
+
@@mappings[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Add a new subclass of Paraprhase::Query. The provided block is evaluated in
|
26
|
+
# the context of a Query subclass to define scope mappings.
|
27
|
+
#
|
28
|
+
# @param [String, Symbol] name name of the model in any inflector form
|
29
|
+
# @param [Proc] block defining mappings of scopes to keys for Query subclass
|
30
|
+
def self.register(name, &block)
|
31
|
+
klass = Class.new(Query, &block)
|
32
|
+
klass.paraphrases(name.to_s.classify)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Register a subclass of Paraphrase::Query. Useful for manually subclassing
|
37
|
+
# Paraphrase::Query to add custom functionality.
|
38
|
+
#
|
39
|
+
# @param [String, Symbol] name name of the class in any ActiveSupport inflector form
|
40
|
+
# @param [Query] klass subclass of Paraphrase::Query
|
41
|
+
def self.add(name, klass)
|
42
|
+
name = name.to_s.underscore
|
43
|
+
|
44
|
+
if @@mappings[name]
|
45
|
+
raise DuplicateMappingError, "#{name.classify} has already been added"
|
46
|
+
end
|
47
|
+
|
48
|
+
@@mappings[name] = klass
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# Instantiate a new Query subclass using supplied params
|
53
|
+
#
|
54
|
+
# @param [String, Symbol] name name of the model in underscored form
|
55
|
+
# @param [Hash] params hash of query params
|
56
|
+
# @return [Query]
|
57
|
+
def self.query(name, params)
|
58
|
+
@@mappings[name].new(params)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
require 'paraphrase/errors'
|
63
|
+
require 'paraphrase/query'
|
64
|
+
require 'paraphrase/syntax'
|
65
|
+
require 'paraphrase/rails' if defined?(Rails)
|
data/paraphrase.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/paraphrase/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "paraphrase"
|
7
|
+
gem.version = Paraphrase::VERSION
|
8
|
+
gem.summary = %q{Map param keys to class scopes}
|
9
|
+
gem.description = %q{Map param keys to class scopes}
|
10
|
+
gem.license = "MIT"
|
11
|
+
gem.authors = ["Eduardo Gutierrez"]
|
12
|
+
gem.email = "edd_d@mit.edu"
|
13
|
+
gem.homepage = "https://rubygems.org/gems/paraphrase"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
|
20
|
+
gem.add_dependency 'activerecord', '~> 3.0'
|
21
|
+
gem.add_dependency 'activesupport', '~> 3.0'
|
22
|
+
gem.add_dependency 'activemodel', '~> 3.0'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'bundler', '~> 1.0'
|
25
|
+
gem.add_development_dependency 'yard', '~> 0.7'
|
26
|
+
gem.add_development_dependency 'rspec', '~> 2.10'
|
27
|
+
gem.add_development_dependency 'redcarpet'
|
28
|
+
gem.add_development_dependency 'rake'
|
29
|
+
gem.add_development_dependency 'sqlite3'
|
30
|
+
gem.add_development_dependency 'appraisal'
|
31
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Paraphrase
|
4
|
+
describe Query do
|
5
|
+
|
6
|
+
describe ".paraphrases" do
|
7
|
+
it "stores the class being queried" do
|
8
|
+
UserSearch.paraphrases :user
|
9
|
+
UserSearch.source.should eq User.scoped
|
10
|
+
end
|
11
|
+
|
12
|
+
it "registers the query in Paraphrase.querys" do
|
13
|
+
Paraphrase.mapping(:user).should eq UserSearch
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".scope" do
|
18
|
+
it "adds information to Query.scopes" do
|
19
|
+
UserSearch.instance_eval do
|
20
|
+
scope :name_like, :key => :name
|
21
|
+
end
|
22
|
+
|
23
|
+
UserSearch.scopes.should_not be_empty
|
24
|
+
end
|
25
|
+
|
26
|
+
it "raises an error if a scope is added twice" do
|
27
|
+
expect { UserSearch.instance_eval { scope :name_like, :key => :name } }.to raise_error Paraphrase::DuplicateScopeError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#initialize" do
|
32
|
+
it "filters out params not specified in scopes" do
|
33
|
+
query = UserSearch.new(:name => 'Tyrion Lannister', :nickname => 'Half Man')
|
34
|
+
|
35
|
+
query.params.should_not have_key :nickname
|
36
|
+
query.params.should have_key :name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#results" do
|
41
|
+
before :all do
|
42
|
+
UserSearch.instance_eval do
|
43
|
+
scope :title_like, :key => :title, :require => true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "loops through scope methods and applies them to source" do
|
48
|
+
User.should_receive(:title_like).and_return(User.scoped)
|
49
|
+
User.should_receive(:name_like).and_return(User.scoped)
|
50
|
+
|
51
|
+
query = UserSearch.new(:name => 'Jon Snow', :title => 'Wall Watcher')
|
52
|
+
query.results
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns empty array if errors were added" do
|
56
|
+
query = UserSearch.new
|
57
|
+
query.results.should eq []
|
58
|
+
query.errors.should_not be_empty
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Paraphrase
|
4
|
+
describe ScopeMapping do
|
5
|
+
let(:scope_mapping) do
|
6
|
+
ScopeMapping.new :name_like, :key => :name
|
7
|
+
end
|
8
|
+
|
9
|
+
it "removes keys from options" do
|
10
|
+
scope_mapping.options.should_not have_key :key
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#chain" do
|
14
|
+
let(:query) { double('query') }
|
15
|
+
|
16
|
+
it "applies scope method to query object with values from params hash" do
|
17
|
+
Account.should_receive(:name_like).with('Jon Snow')
|
18
|
+
scope_mapping.chain(query, { :name => 'Jon Snow' }, Account)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "does nothing if values are missing" do
|
22
|
+
Account.should_not_receive(:name_like).with('Jon Snow')
|
23
|
+
scope_mapping.chain(query, {}, Account)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "adds errors to query object if missing and required" do
|
27
|
+
errors = double('errors')
|
28
|
+
query.stub(:errors).and_return(errors)
|
29
|
+
required_mapping = ScopeMapping.new :name_like, :key => :name, :require => true
|
30
|
+
|
31
|
+
errors.should_receive(:add)
|
32
|
+
required_mapping.chain(query, {}, Account)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Paraphrase
|
4
|
+
describe Syntax do
|
5
|
+
describe ".register_mapping" do
|
6
|
+
it "forwards to Paraphrase.register" do
|
7
|
+
::Account.register_mapping {}
|
8
|
+
Paraphrase.mapping(:account).should_not be_nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ".paraphrase" do
|
13
|
+
it "forwards to Paraphrase.query" do
|
14
|
+
Paraphrase.should_receive(:query).with('account', {})
|
15
|
+
::Account.paraphrase({})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Paraphrase do
|
4
|
+
|
5
|
+
describe ".configure" do
|
6
|
+
it "is a convenience method for configuring multiple query classes" do
|
7
|
+
Paraphrase.configure do |mapping|
|
8
|
+
mapping.register(:person) {}
|
9
|
+
end
|
10
|
+
|
11
|
+
Paraphrase.mapping(:person).should_not be_nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".register" do
|
16
|
+
it "a sublcass of Paraphrase::Query to @@mappings" do
|
17
|
+
Paraphrase.register(:foobar) {}
|
18
|
+
Paraphrase.mapping(:foobar).should_not be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "adds the source to the new subclass" do
|
22
|
+
Paraphrase.mapping(:foobar).source.should eq Foobar.scoped
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises an error if mappings for a class are added twice" do
|
26
|
+
expect { Paraphrase.register(:foobar) {} }.to raise_error Paraphrase::DuplicateMappingError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".query" do
|
31
|
+
it "instantiates a new Query class" do
|
32
|
+
Paraphrase.query(:foobar, {}).should be_a Paraphrase::Query
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe ".add" do
|
37
|
+
it "adds class to mapping with specified name" do
|
38
|
+
klass = Class.new(Paraphrase::Query)
|
39
|
+
Paraphrase.add(:baz, klass)
|
40
|
+
Paraphrase.mapping(:baz).should eq klass
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'paraphrase'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(
|
6
|
+
:adapter => 'sqlite3',
|
7
|
+
:database => ':memory:'
|
8
|
+
)
|
9
|
+
|
10
|
+
ActiveRecord::Base.silence do
|
11
|
+
ActiveRecord::Migration.verbose = false
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define do
|
14
|
+
create_table(:users, :force => true) {}
|
15
|
+
create_table(:accounts, :force => true) {}
|
16
|
+
create_table(:foobars, :force => true) {}
|
17
|
+
create_table(:people, :force => true) {}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Person < ActiveRecord::Base
|
22
|
+
end
|
23
|
+
|
24
|
+
class Foobar < ActiveRecord::Base
|
25
|
+
end
|
26
|
+
|
27
|
+
class Account < ActiveRecord::Base
|
28
|
+
extend Paraphrase::Syntax
|
29
|
+
end
|
30
|
+
|
31
|
+
class User < ActiveRecord::Base
|
32
|
+
end
|
33
|
+
|
34
|
+
class UserSearch < Paraphrase::Query
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: paraphrase
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Eduardo Gutierrez
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &70178168974020 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70178168974020
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
requirement: &70178168972120 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70178168972120
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: activemodel
|
38
|
+
requirement: &70178168971600 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '3.0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70178168971600
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: &70178168971060 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70178168971060
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: yard
|
60
|
+
requirement: &70178168970400 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0.7'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70178168970400
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: &70178168969700 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.10'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70178168969700
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: redcarpet
|
82
|
+
requirement: &70178168969060 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70178168969060
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: rake
|
93
|
+
requirement: &70178168968360 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70178168968360
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: sqlite3
|
104
|
+
requirement: &70178168967600 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *70178168967600
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: appraisal
|
115
|
+
requirement: &70178168966640 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: *70178168966640
|
124
|
+
description: Map param keys to class scopes
|
125
|
+
email: edd_d@mit.edu
|
126
|
+
executables: []
|
127
|
+
extensions: []
|
128
|
+
extra_rdoc_files: []
|
129
|
+
files:
|
130
|
+
- .document
|
131
|
+
- .gitignore
|
132
|
+
- .rspec
|
133
|
+
- .yardopts
|
134
|
+
- Appraisals
|
135
|
+
- ChangeLog.md
|
136
|
+
- Gemfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- gemfiles/3.0.gemfile
|
141
|
+
- gemfiles/3.0.gemfile.lock
|
142
|
+
- gemfiles/3.1.gemfile
|
143
|
+
- gemfiles/3.1.gemfile.lock
|
144
|
+
- gemfiles/3.2.gemfile
|
145
|
+
- gemfiles/3.2.gemfile.lock
|
146
|
+
- lib/paraphrase.rb
|
147
|
+
- lib/paraphrase/errors.rb
|
148
|
+
- lib/paraphrase/query.rb
|
149
|
+
- lib/paraphrase/rails.rb
|
150
|
+
- lib/paraphrase/scope_mapping.rb
|
151
|
+
- lib/paraphrase/syntax.rb
|
152
|
+
- lib/paraphrase/version.rb
|
153
|
+
- paraphrase.gemspec
|
154
|
+
- spec/paraphrase/query_spec.rb
|
155
|
+
- spec/paraphrase/scope_mapping_spec.rb
|
156
|
+
- spec/paraphrase/syntax_spec.rb
|
157
|
+
- spec/paraphrase_spec.rb
|
158
|
+
- spec/spec_helper.rb
|
159
|
+
homepage: https://rubygems.org/gems/paraphrase
|
160
|
+
licenses:
|
161
|
+
- MIT
|
162
|
+
post_install_message:
|
163
|
+
rdoc_options: []
|
164
|
+
require_paths:
|
165
|
+
- lib
|
166
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
167
|
+
none: false
|
168
|
+
requirements:
|
169
|
+
- - ! '>='
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
none: false
|
174
|
+
requirements:
|
175
|
+
- - ! '>='
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
requirements: []
|
179
|
+
rubyforge_project:
|
180
|
+
rubygems_version: 1.8.11
|
181
|
+
signing_key:
|
182
|
+
specification_version: 3
|
183
|
+
summary: Map param keys to class scopes
|
184
|
+
test_files:
|
185
|
+
- spec/paraphrase/query_spec.rb
|
186
|
+
- spec/paraphrase/scope_mapping_spec.rb
|
187
|
+
- spec/paraphrase/syntax_spec.rb
|
188
|
+
- spec/paraphrase_spec.rb
|
189
|
+
- spec/spec_helper.rb
|
190
|
+
has_rdoc:
|