sexy_scopes 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -6
- data/README.md +56 -7
- data/lib/arel/visitors_extensions.rb +89 -0
- data/lib/sexy_scopes.rb +33 -10
- data/lib/sexy_scopes/active_record.rb +0 -1
- data/lib/sexy_scopes/active_record/class_methods.rb +5 -16
- data/lib/sexy_scopes/active_record/dynamic_methods.rb +26 -21
- data/lib/sexy_scopes/arel.rb +10 -28
- data/lib/sexy_scopes/arel/expression_methods.rb +9 -0
- data/lib/sexy_scopes/arel/math.rb +28 -0
- data/lib/sexy_scopes/arel/nodes/regexp_matches.rb +12 -0
- data/lib/sexy_scopes/arel/predicate_methods.rb +13 -38
- data/lib/sexy_scopes/arel/predications.rb +67 -0
- data/lib/sexy_scopes/arel/typecasting.rb +21 -0
- data/lib/sexy_scopes/version.rb +1 -1
- metadata +53 -50
- data/.gitignore +0 -4
- data/.travis.yml +0 -19
- data/Gemfile +0 -2
- data/Rakefile +0 -13
- data/ci/Gemfile.ar-edge +0 -8
- data/ci/Gemfile.ar3.1 +0 -8
- data/ci/Gemfile.ar3.2 +0 -8
- data/lib/sexy_scopes/arel/boolean_methods.rb +0 -20
- data/lib/sexy_scopes/arel/math_methods.rb +0 -25
- data/lib/sexy_scopes/expression_wrappers.rb +0 -7
- data/lib/sexy_scopes/predicate_wrappers.rb +0 -6
- data/lib/sexy_scopes/wrappers.rb +0 -13
- data/sexy_scopes.gemspec +0 -32
- data/spec/active_record_spec.rb +0 -73
- data/spec/boolean_methods_spec.rb +0 -45
- data/spec/fixtures/models.rb +0 -2
- data/spec/fixtures/schema.rb +0 -9
- data/spec/matchers/be_extended_by.rb +0 -6
- data/spec/matchers/convert_to_sql.rb +0 -13
- data/spec/math_methods_spec.rb +0 -55
- data/spec/predicate_methods_spec.rb +0 -38
- data/spec/spec_helper.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea9d2e3c3ca0c50058139d67222d97894fb0192c
|
4
|
+
data.tar.gz: aa1d5490fe8d78f9a3323a25bf10e78cdf072909
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 08a625a5ff5b18d1a0955efcb48bd66093a794382b4d86ee8ad8676fdf28ad34a8aaabd86fa3b8c403a3d328c2b1f06bf72139a12b871dfbeb2d449d64992bea
|
7
|
+
data.tar.gz: 5ec1351dc43eebefeed0defc5ea6da10a9d56cfb41f9812b59e57160f9ed18da9891a5ce0da1ee418b892bd3baef8d76789e461f6ad19e24164ee25613f0f757
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,24 @@
|
|
1
|
-
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
2
3
|
|
3
|
-
|
4
|
-
- Add support for JRuby and Rubinius
|
5
|
-
- Integrate with Travis CI
|
4
|
+
## 0.7.0 - 2014-07-23
|
6
5
|
|
7
|
-
###
|
6
|
+
### Added
|
7
|
+
- Support for Regular Expressions with the `=~` and `!~` operators (PostgreSQL, MySQL & SQLite3)
|
8
|
+
- Support for ActiveRecord 4.1
|
9
|
+
- Support for Arel 6
|
10
|
+
- Integration with Travis CI (https://travis-ci.org/samleb/sexy_scopes)
|
8
11
|
|
9
|
-
|
12
|
+
### Fixed
|
13
|
+
- Existing class methods no longer overriden by dynamic method generation
|
14
|
+
|
15
|
+
### Removed
|
16
|
+
- Support for Ruby 1.9.2
|
17
|
+
|
18
|
+
## 0.6.0 - 2013-05-29
|
19
|
+
|
20
|
+
### Added
|
21
|
+
- Support for JRuby and Rubinius
|
22
|
+
|
23
|
+
### Removed
|
24
|
+
- Support for Ruby < 1.9.2 and ActiveRecord < 3.1
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
SexyScopes
|
2
2
|
==========
|
3
3
|
|
4
|
-
[](https://rubygems.org/gems/sexy_scopes)
|
5
|
+
[](https://gemnasium.com/samleb/sexy_scopes)
|
6
|
+
[](https://codeclimate.com/github/samleb/sexy_scopes)
|
7
|
+
[](https://travis-ci.org/samleb/sexy_scopes)
|
8
|
+
[](https://coveralls.io/r/samleb/sexy_scopes)
|
9
9
|
|
10
10
|
**Write beautiful and expressive ActiveRecord scopes without SQL**
|
11
11
|
|
@@ -168,16 +168,65 @@ Add this line to your application's Gemfile:
|
|
168
168
|
|
169
169
|
And then execute:
|
170
170
|
|
171
|
-
|
171
|
+
bundle
|
172
172
|
|
173
173
|
Or install it yourself as:
|
174
174
|
|
175
|
-
|
175
|
+
gem install sexy_scopes
|
176
176
|
|
177
177
|
Then require it in your application code:
|
178
178
|
|
179
179
|
require 'sexy_scopes'
|
180
180
|
|
181
|
+
Regular Expressions
|
182
|
+
-------------------
|
183
|
+
|
184
|
+
Did you know that most RDBMS come with pretty good support for regular expressions?
|
185
|
+
|
186
|
+
One reason they're quite unpopular in Rails applications is that their syntax is really different amongst
|
187
|
+
databases implementations.
|
188
|
+
Let's say you're using SQLite3 in development, and PostgreSQL in testing/production, well that's quite a good reason
|
189
|
+
not to use database-specific code, isn't it?
|
190
|
+
|
191
|
+
Once again, SexyScopes comes to the rescue:
|
192
|
+
The `=~` and `!~` operators when called with a regular expression will generate the SQL you don't want to know about.
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
predicate = User.username =~ /^john\b(.*\b)?doe$/i
|
196
|
+
|
197
|
+
# In development, using SQLite3:
|
198
|
+
predicate.to_sql
|
199
|
+
# => "users"."username" REGEXP '^john\b(.*\b)?doe$'
|
200
|
+
|
201
|
+
# In testing/production, using PostgreSQL
|
202
|
+
predicate.to_sql
|
203
|
+
# => "users"."username" ~* '^john\b(.*\b)?doe$'
|
204
|
+
```
|
205
|
+
|
206
|
+
Now let's suppose that you want to give your admin a powerful regexp based search upon usernames, here's how
|
207
|
+
you could do it:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
class Admin::UsersController
|
211
|
+
def index
|
212
|
+
@users = User.where(User.username =~ Regexp.compile(params[:query]))
|
213
|
+
respond_with @users
|
214
|
+
end
|
215
|
+
end
|
216
|
+
```
|
217
|
+
|
218
|
+
Let's see what happens in our production logs (SQL included) when they try this new feature:
|
219
|
+
|
220
|
+
```
|
221
|
+
Started GET "/admin/users?query=bob%7Calice" for xx.xx.xx.xx at 2014-03-31 16:00:50 +0200
|
222
|
+
Processing by Admin::UsersController#index as HTML
|
223
|
+
Parameters: {"query"=>"bob|alice"}
|
224
|
+
User Load (0.1ms) SELECT "users".* FROM "users" WHERE ("users"."username" ~ 'bob|alice')
|
225
|
+
```
|
226
|
+
|
227
|
+
The proper SQL is generated, protected from SQL injection BTW, and from now on you have reusable code for
|
228
|
+
both you development and your production environment.
|
229
|
+
|
181
230
|
Contributing
|
182
231
|
------------
|
183
232
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'arel/visitors'
|
2
|
+
|
3
|
+
module Arel
|
4
|
+
module Visitors
|
5
|
+
class ToSql
|
6
|
+
private
|
7
|
+
|
8
|
+
def visit_Regexp(regexp, arg = nil)
|
9
|
+
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
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class MySQL
|
35
|
+
private
|
36
|
+
|
37
|
+
def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, arg = nil)
|
38
|
+
regexp = o.right
|
39
|
+
right = SexyScopes.quote(regexp.source)
|
40
|
+
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
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class PostgreSQL
|
52
|
+
private
|
53
|
+
|
54
|
+
def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, arg = nil)
|
55
|
+
regexp = o.right
|
56
|
+
operator = regexp.casefold? ? '~*' : '~'
|
57
|
+
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
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Oracle
|
69
|
+
private
|
70
|
+
|
71
|
+
def visit_SexyScopes_Arel_Nodes_RegexpMatches(o, arg = nil)
|
72
|
+
regexp = o.right
|
73
|
+
flags = regexp.casefold? ? 'i' : 'c'
|
74
|
+
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
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/sexy_scopes.rb
CHANGED
@@ -1,19 +1,42 @@
|
|
1
|
-
require 'active_support/
|
1
|
+
require 'active_support/lazy_load_hooks'
|
2
|
+
require 'sexy_scopes/version'
|
2
3
|
|
3
4
|
module SexyScopes
|
4
|
-
|
5
|
-
autoload constant, 'sexy_scopes/version'
|
6
|
-
end
|
7
|
-
|
8
|
-
extend ActiveSupport::Autoload
|
5
|
+
autoload :Arel, 'sexy_scopes/arel'
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
class << self
|
8
|
+
AREL_6 = ::Arel::VERSION >= '6.0.0'
|
9
|
+
|
10
|
+
def extend_expression(expression)
|
11
|
+
expression.extend(Arel::ExpressionMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
def extend_predicate(predicate)
|
15
|
+
predicate.extend(Arel::PredicateMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
def arel_6?
|
19
|
+
AREL_6
|
20
|
+
end
|
21
|
+
|
22
|
+
if AREL_6
|
23
|
+
def quote(node, attribute = nil)
|
24
|
+
::Arel::Nodes.build_quoted(node, attribute)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
def quote(node, attribute = nil)
|
28
|
+
node
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :type_cast, :quote
|
33
|
+
end
|
13
34
|
end
|
14
35
|
|
15
36
|
if defined? Rails::Railtie
|
16
37
|
require 'sexy_scopes/railtie'
|
17
38
|
else
|
18
|
-
|
39
|
+
ActiveSupport.on_load :active_record do
|
40
|
+
require 'sexy_scopes/active_record'
|
41
|
+
end
|
19
42
|
end
|
@@ -1,11 +1,6 @@
|
|
1
|
-
require 'sexy_scopes/arel'
|
2
|
-
require 'sexy_scopes/wrappers'
|
3
|
-
|
4
1
|
module SexyScopes
|
5
2
|
module ActiveRecord
|
6
3
|
module ClassMethods
|
7
|
-
include Wrappers
|
8
|
-
|
9
4
|
# Creates and extends an Arel <tt>Attribute</tt> representing the table's column with
|
10
5
|
# the given <tt>name</tt>.
|
11
6
|
#
|
@@ -14,12 +9,11 @@ module SexyScopes
|
|
14
9
|
# @note Please note that no exception is raised if no such column actually exists.
|
15
10
|
#
|
16
11
|
# @example
|
17
|
-
#
|
18
|
-
# # => SELECT "users".* FROM "users" WHERE ("users"."score" > 1000)
|
12
|
+
# where attribute(:score) > 1000
|
19
13
|
#
|
20
14
|
def attribute(name)
|
21
15
|
attribute = arel_table[name]
|
22
|
-
extend_expression(attribute)
|
16
|
+
SexyScopes.extend_expression(attribute)
|
23
17
|
end
|
24
18
|
|
25
19
|
# Creates and extends an Arel <tt>SqlLiteral</tt> instance for the given <tt>expression</tt>,
|
@@ -28,17 +22,12 @@ module SexyScopes
|
|
28
22
|
# @param [String, #to_s] expression Any SQL expression.
|
29
23
|
#
|
30
24
|
# @example
|
31
|
-
#
|
32
|
-
# where sql(2 * Math::PI) * radius < perimeter
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# Circle.with_perimeter_smaller_than(20)
|
36
|
-
# # => SELECT "circles".* FROM "circles" WHERE (6.283185307179586 * "circles"."radius" < 20)
|
25
|
+
# where sql('LENGTH(email)') > 200
|
37
26
|
#
|
38
27
|
def sql_literal(expression)
|
39
28
|
::Arel.sql(expression.to_s).tap do |literal|
|
40
|
-
extend_expression(literal)
|
41
|
-
extend_predicate(literal)
|
29
|
+
SexyScopes.extend_expression(literal)
|
30
|
+
SexyScopes.extend_predicate(literal)
|
42
31
|
end
|
43
32
|
end
|
44
33
|
alias_method :sql, :sql_literal
|
@@ -2,11 +2,14 @@ module SexyScopes
|
|
2
2
|
module ActiveRecord
|
3
3
|
module DynamicMethods
|
4
4
|
private
|
5
|
-
|
6
|
-
def respond_to_missing?(method_name, include_private = false) # :nodoc:
|
5
|
+
def respond_to_missing?(method_name, *)
|
7
6
|
# super currently resolve to Object#respond_to_missing? which return false,
|
8
7
|
# but future version of ActiveRecord::Base might implement respond_to_missing?
|
9
|
-
|
8
|
+
if @sexy_scopes_attribute_methods_generated
|
9
|
+
super
|
10
|
+
else
|
11
|
+
sexy_scopes_has_attribute?(method_name)
|
12
|
+
end
|
10
13
|
end
|
11
14
|
|
12
15
|
# Equivalent to calling {#attribute} with the missing method's <tt>name</tt> if the table
|
@@ -37,29 +40,31 @@ module SexyScopes
|
|
37
40
|
# # In these cases you'll have to use `attribute` explicitely
|
38
41
|
# User.attribute(:name)
|
39
42
|
#
|
40
|
-
def method_missing(name, *args)
|
41
|
-
if
|
42
|
-
sexy_scopes_define_attribute_method(name)
|
43
|
-
attribute(name)
|
44
|
-
else
|
43
|
+
def method_missing(name, *args, &block)
|
44
|
+
if @sexy_scopes_attribute_methods_generated
|
45
45
|
super
|
46
|
+
else
|
47
|
+
sexy_scopes_define_attribute_methods
|
48
|
+
send(name, *args, &block)
|
46
49
|
end
|
47
50
|
end
|
48
|
-
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
|
52
|
+
def sexy_scopes_define_attribute_methods
|
53
|
+
@sexy_scopes_attribute_methods_generated = true
|
54
|
+
return unless sexy_scopes_is_table?
|
55
|
+
column_names.each do |name|
|
56
|
+
unless respond_to?(name, true)
|
57
|
+
define_singleton_method(name) { attribute(name) }
|
58
|
+
end
|
59
|
+
end
|
55
60
|
end
|
56
|
-
|
61
|
+
|
57
62
|
def sexy_scopes_has_attribute?(attribute_name)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
+
sexy_scopes_is_table? && column_names.include?(attribute_name.to_s)
|
64
|
+
end
|
65
|
+
|
66
|
+
def sexy_scopes_is_table?
|
67
|
+
!(equal?(::ActiveRecord::Base) || abstract_class?) && table_exists?
|
63
68
|
end
|
64
69
|
end
|
65
70
|
end
|
data/lib/sexy_scopes/arel.rb
CHANGED
@@ -1,34 +1,16 @@
|
|
1
|
+
require 'arel/visitors_extensions'
|
2
|
+
|
1
3
|
module SexyScopes
|
2
4
|
module Arel
|
3
|
-
|
4
|
-
|
5
|
-
autoload :MathMethods
|
6
|
-
autoload :PredicateMethods
|
7
|
-
autoload :BooleanMethods
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
module Arel
|
12
|
-
module Nodes
|
13
|
-
# <tt>Grouping</tt> nodes didn't include <tt>Arel::Predications</tt> before
|
14
|
-
# {https://github.com/rails/arel/commit/c78227d9b219933f54cecefb99c72bb231fbb8f2 this commit}.
|
15
|
-
#
|
16
|
-
# Here they are included explicitely just in case they're missing.
|
17
|
-
class Grouping
|
18
|
-
include Arel::Predications
|
19
|
-
end
|
5
|
+
autoload :ExpressionMethods, 'sexy_scopes/arel/expression_methods'
|
6
|
+
autoload :PredicateMethods, 'sexy_scopes/arel/predicate_methods'
|
20
7
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
8
|
+
autoload :Predications, 'sexy_scopes/arel/predications'
|
9
|
+
autoload :Typecasting, 'sexy_scopes/arel/typecasting'
|
10
|
+
autoload :Math, 'sexy_scopes/arel/math'
|
11
|
+
|
12
|
+
module Nodes
|
13
|
+
autoload :RegexpMatches, 'sexy_scopes/arel/nodes/regexp_matches'
|
27
14
|
end
|
28
15
|
end
|
29
|
-
|
30
|
-
# @!visibility private
|
31
|
-
class SqlLiteral # :nodoc:
|
32
|
-
include Arel::Math
|
33
|
-
end
|
34
16
|
end
|