sexy_scopes 0.8.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a8538efe07dea982f302925e0e126c6dd04680d
4
- data.tar.gz: 5d5de790bf16294721550ab1166938b3ad01047a
3
+ metadata.gz: 9b42939a28bfedb23b35b8d097662a57daecfb10
4
+ data.tar.gz: b68a65eba99c7816b27ac9ea56d8af4db8af33f2
5
5
  SHA512:
6
- metadata.gz: 95337a57c0814a748d3053174028c0d735798694cc1ca3af048c33d97f79997fa819d7876a44299ba23fabada0601c7a674a93ca0ec8f88ce2d216b472adfc97
7
- data.tar.gz: 5a65f9b79859ced79a5bf51f4181464e6402e8e0d2fa1f20f96bda160ebfc2917a2d574d38ca205a564200ddd018891aca0339514ac7e89bad4b390019e5e43c
6
+ metadata.gz: 21c6566a6ee33cfcda27e03555b219c60de5eb70de9ec688a71f5ca1618c4cae154c185f3c9fcf50518ed7d8bc9174d26b7170aefff1d181e9f2a315d45e8a3f
7
+ data.tar.gz: 713d10f8ab03c8a7b600c2f1d96c19fcc29ce89d8eea7188dd62b3cc60e3b956fd676c68d2f045b610a7a720b9a99cbf0b0d88479d07d3635b78c6ffee0e8339
data/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## 1.0.0.beta1 - 2017-09-17
5
+
6
+ ### Added
7
+ - Support for ActiveRecord 5
8
+ - Support for Ruby >= 2.2
9
+ - Support for Rubinius 3
10
+ - Alternative `where` syntax not implemented using `instance_exec` (relation is passed when block takes an argument)
11
+ - Support for block syntax with `where.not`
12
+
13
+ ### Removed
14
+ - Support for ActiveRecord < 4.2
15
+ - Support for Ruby 1.9.3
16
+
4
17
  ## 0.8.0 - 2015-01-18
5
18
 
6
19
  ### Added
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,47 @@
1
+ Contributing
2
+ ------------
3
+
4
+ ### Setup a development environment
5
+
6
+ - Create the database config file from the sample:
7
+
8
+ cp spec/config.example.yml spec/config.yml
9
+
10
+ - Install the required gems:
11
+
12
+ bundle install
13
+ bundle exec appraisal install
14
+
15
+ - Edit `spec/config.yml` to reflect the configuration of all your database adapters (similar to `database.yml`)
16
+
17
+ - To run the test suite, do:
18
+
19
+ bundle exec rake
20
+
21
+ - By default, this will only run the tests with the latest version of ActiveRecord and using an in-memory SQLite3 database.
22
+ This is the simplest way to ensure you've not broken anything.
23
+
24
+ - If you want to run the test suite using a particular database, do:
25
+
26
+ bundle exec rake spec:mysql
27
+ bundle exec rake spec:postgresql
28
+
29
+ - Before committing your changes, ensure they're compatible with all adapters, *and* all ActiveRecord versions by running:
30
+
31
+ bundle exec appraisal rake spec:all
32
+
33
+ ### Working with GitHub
34
+
35
+ Report bugs or suggest features using [GitHub issues](https://github.com/samleb/sexy_scopes).
36
+
37
+ 1. Fork it
38
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
39
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
40
+ 4. Push to the branch (`git push origin my-new-feature`)
41
+ 5. Create new Pull Request
42
+
43
+ TODO
44
+ ----
45
+
46
+ - Document the `sql_literal` method and how it can be used to create complex subqueries
47
+ - Handle associations (i.e. `Post.comments == Comment.joins(:posts)` ?)
data/README.md CHANGED
@@ -13,7 +13,7 @@ SexyScopes is a gem that adds syntactic sugar for creating ActiveRecord scopes
13
13
  in Ruby instead of SQL.
14
14
  This allows for more expressive, less error-prone and database independent conditions.
15
15
 
16
- **WARNING**: This gem requires Ruby >= 1.9.2 and ActiveRecord >= 3.1.
16
+ **WARNING**: This gem requires Ruby >= 2.0.0 and ActiveRecord >= 4.2
17
17
 
18
18
  * [Source Code](https://github.com/samleb/sexy_scopes)
19
19
  * [Rubygem](http://rubygems.org/gems/sexy_scopes)
@@ -58,12 +58,14 @@ Let's take a look at another example with these relations:
58
58
 
59
59
  ```ruby
60
60
  # rating: integer
61
+ # body: text
61
62
  class Post < ActiveRecord::Base
62
63
  has_many :comments
63
64
  end
64
65
 
65
66
  # post_id: integer
66
67
  # rating: integer
68
+ # body: text
67
69
  class Comment < ActiveRecord::Base
68
70
  belongs_to :post
69
71
  end
@@ -75,7 +77,12 @@ Now let's find posts having comments with a rating greater than a given rating i
75
77
 
76
78
  ```ruby
77
79
  @posts = Post.joins(:comments).where('rating > ?', params[:rating])
78
- # ActiveRecord::StatementInvalid: ambiguous column name: rating
80
+ ```
81
+
82
+ This expression, while syntactically valid, raises the following exception:
83
+
84
+ ```
85
+ ActiveRecord::StatementInvalid: ambiguous column name: rating
79
86
  ```
80
87
 
81
88
  Because both `Post` and `Comment` have a `rating` column, you have to give the table name explicitly:
@@ -84,12 +91,12 @@ Because both `Post` and `Comment` have a `rating` column, you have to give the t
84
91
  @posts = Post.joins(:comments).where('comments.rating > ?', params[:rating])
85
92
  ```
86
93
 
87
- Not very DRY, is it?
88
-
89
94
  **With SexyScopes**
90
95
 
96
+ Since `Comment.rating` represents the `rating` column of the `Comment` model, the above can be rewritten as such:
97
+
91
98
  ```ruby
92
- @posts = Post.joins(:comments).where Comment.rating > params[:rating]
99
+ @posts = Post.joins(:comments).where { rating > params[:rating] }
93
100
  ```
94
101
 
95
102
  Here you have it, clear as day, still protected from SQL injection.
@@ -145,6 +152,42 @@ Here is a complete list of operators, and their `Arel::Attribute` equivalents:
145
152
  - `|`: `or`
146
153
  - `~`: `not`
147
154
 
155
+ Block syntax
156
+ ------------
157
+
158
+ SexyScopes introduces a new block syntax for the `where` clause, which can be used in 2 different forms:
159
+
160
+ * With no argument, the block is evaluated in the context of the relation
161
+
162
+ ```ruby
163
+ # `price` is `Product.price`
164
+ Product.where { price < 500 }
165
+
166
+ # `body` is `post.comments.body`
167
+ post.comments.where { body =~ "%ruby%" }
168
+ ```
169
+
170
+ * With an argument, block is called with the relation as argument
171
+
172
+ ```ruby
173
+ # `p` is the `Product` relation
174
+ Product.where { |p| p.price < 500 }
175
+
176
+ # `c` is the `post.comments` relation
177
+ post.comments.where { |c| c.body =~ "%ruby%" }
178
+ ```
179
+
180
+ These 2 forms are functionally equivalent.
181
+ The former, while being more concise, is internally implemented using `instance_eval`, which will prevent you from calling method on the receiver (`self`).
182
+
183
+ *Tip*: Try switching to the later form if you encounter `NoMethodError` exceptions.
184
+
185
+ Note that you can also use this syntax with `where.not`:
186
+
187
+ ```ruby
188
+ Product.where.not { price > 200 }
189
+ ```
190
+
148
191
  Regular Expressions
149
192
  -------------------
150
193
 
@@ -176,7 +219,8 @@ you could do it:
176
219
  ```ruby
177
220
  class Admin::UsersController
178
221
  def index
179
- @users = User.where(User.username =~ Regexp.compile(params[:query]))
222
+ query = Regexp.compile(params[:query])
223
+ @users = User.where { username =~ query }
180
224
  respond_with @users
181
225
  end
182
226
  end
@@ -210,9 +254,9 @@ class Circle < ActiveRecord::Base
210
254
  end
211
255
  end
212
256
 
213
- Circle.where Circle.perimeter > 42
257
+ Circle.where { perimeter > 42 }
214
258
  # SQL: SELECT `circles`.* FROM `circles` WHERE (6.283185307179586 * `circles`.`radius` > 42)
215
- Circle.where Circle.area < 42
259
+ Circle.where { area < 42 }
216
260
  # SQL: SELECT `circles`.* FROM `circles` WHERE (3.141592653589793 * `circles`.`radius` * `circles`.`radius` < 42)
217
261
 
218
262
  class Product < ActiveRecord::Base
@@ -229,23 +273,13 @@ end
229
273
  Contributing
230
274
  ------------
231
275
 
232
- Report bugs or suggest features using [GitHub issues](https://github.com/samleb/sexy_scopes).
233
-
234
- 1. Fork it
235
- 2. Create your feature branch (`git checkout -b my-new-feature`)
236
- 3. Commit your changes (`git commit -am 'Add some feature'`)
237
- 4. Push to the branch (`git push origin my-new-feature`)
238
- 5. Create new Pull Request
239
-
240
- TODO
241
- ----
276
+ All suggestions, ideas and contributions are very welcome.
242
277
 
243
- - Document the `sql_literal` method and how it can be used to create complex subqueries
244
- - Handle associations (i.e. `Post.comments == Comment.joins(:posts)` ?)
278
+ If you want to contribute, please follow the steps described in [CONTRIBUTING.md](CONTRIBUTING.md)
245
279
 
246
280
  Copyright
247
281
  ---------
248
282
 
249
283
  SexyScopes is released under the [MIT License](http://www.opensource.org/licenses/MIT).
250
284
 
251
- Copyright (c) 2010-2014 Samuel Lebeau, See LICENSE for details.
285
+ Copyright (c) 2010-2017 Samuel Lebeau, See LICENSE for details.
@@ -2,87 +2,56 @@ require 'arel/visitors'
2
2
 
3
3
  module Arel
4
4
  module Visitors
5
+ # TODO: Extract these methods into modules
5
6
  class ToSql
6
7
  private
7
8
 
8
- def visit_Regexp(regexp, arg = nil)
9
+ def visit_Regexp(regexp, collector)
9
10
  source = SexyScopes.quote(regexp.source)
10
- sexy_scopes_visit source, arg
11
- end
12
-
13
- # Arel >= 4.0
14
- if instance_method(:visit).arity > 1
15
- alias_method :sexy_scopes_visit, :visit
16
- else
17
- def sexy_scopes_visit(node, arg = nil)
18
- visit(node)
19
- end
20
- end
21
-
22
- # Arel >= 6.0
23
- if instance_method(:accept).arity > 1
24
- def reduce_visitor?
25
- true
26
- end
27
- else
28
- def reduce_visitor?
29
- false
30
- end
11
+ visit source, collector
31
12
  end
32
13
  end
33
14
 
34
15
  class MySQL
35
16
  private
36
17
 
37
- def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, arg = nil)
18
+ def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, collector)
38
19
  regexp = o.right
39
20
  right = SexyScopes.quote(regexp.source)
40
21
  right = Arel::Nodes::Bin.new(right) unless regexp.casefold?
41
- if reduce_visitor?
42
- visit o.left, arg
43
- arg << ' REGEXP '
44
- visit right, arg
45
- else
46
- "#{sexy_scopes_visit o.left, arg} REGEXP #{visit right}"
47
- end
22
+ visit o.left, collector
23
+ collector << ' REGEXP '
24
+ visit right, collector
48
25
  end
49
26
  end
50
27
 
51
28
  class PostgreSQL
52
29
  private
53
30
 
54
- def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, arg = nil)
31
+ def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, collector)
55
32
  regexp = o.right
56
33
  operator = regexp.casefold? ? '~*' : '~'
57
34
  right = SexyScopes.quote(regexp.source)
58
- if reduce_visitor?
59
- visit o.left, arg
60
- arg << SPACE << operator << SPACE
61
- visit right, arg
62
- else
63
- "#{sexy_scopes_visit o.left, arg} #{operator} #{visit right}"
64
- end
35
+ visit o.left, collector
36
+ collector << SPACE << operator << SPACE
37
+ visit right, collector
65
38
  end
66
39
  end
67
40
 
68
41
  class Oracle
69
42
  private
70
43
 
71
- def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, arg = nil)
44
+ def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, collector)
72
45
  regexp = o.right
73
46
  flags = regexp.casefold? ? 'i' : 'c'
74
47
  flags << 'm' if regexp.options & Regexp::MULTILINE == Regexp::MULTILINE
75
- if reduce_visitor?
76
- arg << 'REGEXP_LIKE('
77
- visit o.left, arg
78
- arg << COMMA
79
- visit regexp.source, arg
80
- arg << COMMA
81
- visit flags, arg
82
- arg << ')'
83
- else
84
- "REGEXP_LIKE(#{sexy_scopes_visit o.left, arg}, #{visit regexp.source}, #{visit flags})"
85
- end
48
+ collector << 'REGEXP_LIKE('
49
+ visit o.left, collector
50
+ collector << COMMA
51
+ visit regexp.source, collector
52
+ collector << COMMA
53
+ visit flags, collector
54
+ collector << ')'
86
55
  end
87
56
  end
88
57
  end
data/lib/sexy_scopes.rb CHANGED
@@ -13,16 +13,12 @@ module SexyScopes
13
13
  predicate.extend(Arel::PredicateMethods)
14
14
  end
15
15
 
16
- def arel_6?
17
- @arel_6 ||= ::Arel::VERSION >= '6.0.0'
16
+ def arel_9?
17
+ @arel_9 ||= ::Arel::VERSION >= '9.0.0'
18
18
  end
19
19
 
20
20
  def quote(node, attribute = nil)
21
- if arel_6?
22
- ::Arel::Nodes.build_quoted(node, attribute)
23
- else
24
- node
25
- end
21
+ ::Arel::Nodes.build_quoted(node, attribute)
26
22
  end
27
23
 
28
24
  alias_method :type_cast, :quote
@@ -4,4 +4,7 @@ require 'sexy_scopes/active_record/query_methods'
4
4
 
5
5
  ActiveRecord::Base.extend SexyScopes::ActiveRecord::ClassMethods
6
6
  ActiveRecord::Base.extend SexyScopes::ActiveRecord::DynamicMethods
7
+
7
8
  ActiveRecord::Relation.send(:include, SexyScopes::ActiveRecord::QueryMethods)
9
+
10
+ ActiveRecord::QueryMethods::WhereChain.send(:prepend, SexyScopes::ActiveRecord::QueryMethods::WhereChainMethods)
@@ -4,9 +4,9 @@ module SexyScopes
4
4
  # Adds support for blocks to ActiveRecord `where`.
5
5
  #
6
6
  # @example
7
- # User.where { username == 'bob' }
7
+ # User.where { username =~ 'bob%' }
8
8
  # # is equivalent to:
9
- # User.where(User.username == 'bob')
9
+ # User.where(User.username =~ 'bob%')
10
10
  #
11
11
  # The block is evaluated in the context of the current relation, and the resulting expression
12
12
  # is used as an argument to `where`, thus allowing a cleaner syntax where you don't have to write
@@ -21,15 +21,46 @@ module SexyScopes
21
21
  #
22
22
  # @raise [ArgumentError] if both arguments and a block are passed
23
23
  #
24
+ # @see WhereChainMethods#not
25
+ #
24
26
  def where(*args, &block)
25
27
  if block
26
- raise ArgumentError, "You can't use both arguments and a block" if args.any?
27
- conditions = instance_exec(&block)
28
- super(conditions)
28
+ super(sexy_scopes_build_conditions_from_block(args, block))
29
29
  else
30
30
  super
31
31
  end
32
32
  end
33
+
34
+ protected
35
+
36
+ def sexy_scopes_build_conditions_from_block(args, block)
37
+ raise ArgumentError, "You can't use both arguments and a block" if args.any?
38
+ if block.arity.zero?
39
+ instance_eval(&block)
40
+ else
41
+ block.call(self)
42
+ end
43
+ end
44
+
45
+ module WhereChainMethods
46
+ # Adds support for blocks to ActiveRecord `where.not`.
47
+ #
48
+ # @example
49
+ # User.where.not { username =~ 'bob%' }
50
+ #
51
+ # @raise [ArgumentError] if both arguments and a block are passed
52
+ #
53
+ # @see QueryMethods#where
54
+ #
55
+ def not(*args, &block)
56
+ if block
57
+ conditions = @scope.send(:sexy_scopes_build_conditions_from_block, args, block)
58
+ @scope.where(conditions.not)
59
+ else
60
+ super
61
+ end
62
+ end
63
+ end
33
64
  end
34
65
  end
35
66
  end
@@ -1,7 +1,7 @@
1
1
  module SexyScopes
2
2
  module Arel
3
3
  module Math
4
- include Typecasting if SexyScopes.arel_6?
4
+ include Typecasting
5
5
 
6
6
  def *(other)
7
7
  SexyScopes.extend_expression(super)
@@ -1,10 +1,11 @@
1
1
  module SexyScopes
2
2
  module Version
3
- MAJOR = 0
4
- MINOR = 8
3
+ MAJOR = 1
4
+ MINOR = 0
5
5
  TINY = 0
6
+ PRE = 'beta1'
6
7
 
7
- STRING = [MAJOR, MINOR, TINY].join('.')
8
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
8
9
 
9
10
  class << self
10
11
  # Allows {Version} to display ({to_s}) and behave ({to_str}) as a string
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sexy_scopes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Lebeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-18 00:00:00.000000000 Z
11
+ date: 2017-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,34 +16,34 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.1'
19
+ version: '4.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '5'
22
+ version: '5.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '3.1'
29
+ version: '4.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '5'
32
+ version: '5.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: appraisal
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.0'
39
+ version: '2.1'
40
40
  type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '1.0'
46
+ version: '2.1'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: bundler
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -106,14 +106,14 @@ dependencies:
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '0.3'
109
+ version: 0.4.9
110
110
  type: :development
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: '0.3'
116
+ version: 0.4.9
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: pg
119
119
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +178,7 @@ extensions: []
178
178
  extra_rdoc_files: []
179
179
  files:
180
180
  - CHANGELOG.md
181
+ - CONTRIBUTING.md
181
182
  - LICENSE
182
183
  - README.md
183
184
  - lib/arel/visitors_extensions.rb
@@ -207,17 +208,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
207
208
  requirements:
208
209
  - - ">="
209
210
  - !ruby/object:Gem::Version
210
- version: 1.9.3
211
+ version: 2.0.0
211
212
  required_rubygems_version: !ruby/object:Gem::Requirement
212
213
  requirements:
213
- - - ">="
214
+ - - ">"
214
215
  - !ruby/object:Gem::Version
215
- version: '0'
216
+ version: 1.3.1
216
217
  requirements: []
217
218
  rubyforge_project:
218
- rubygems_version: 2.2.2
219
+ rubygems_version: 2.6.8
219
220
  signing_key:
220
221
  specification_version: 4
221
222
  summary: Write beautiful and expressive ActiveRecord scopes without SQL.
222
223
  test_files: []
223
- has_rdoc: