sexy_scopes 0.8.0 → 1.0.0.beta1

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