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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -6
  3. data/README.md +56 -7
  4. data/lib/arel/visitors_extensions.rb +89 -0
  5. data/lib/sexy_scopes.rb +33 -10
  6. data/lib/sexy_scopes/active_record.rb +0 -1
  7. data/lib/sexy_scopes/active_record/class_methods.rb +5 -16
  8. data/lib/sexy_scopes/active_record/dynamic_methods.rb +26 -21
  9. data/lib/sexy_scopes/arel.rb +10 -28
  10. data/lib/sexy_scopes/arel/expression_methods.rb +9 -0
  11. data/lib/sexy_scopes/arel/math.rb +28 -0
  12. data/lib/sexy_scopes/arel/nodes/regexp_matches.rb +12 -0
  13. data/lib/sexy_scopes/arel/predicate_methods.rb +13 -38
  14. data/lib/sexy_scopes/arel/predications.rb +67 -0
  15. data/lib/sexy_scopes/arel/typecasting.rb +21 -0
  16. data/lib/sexy_scopes/version.rb +1 -1
  17. metadata +53 -50
  18. data/.gitignore +0 -4
  19. data/.travis.yml +0 -19
  20. data/Gemfile +0 -2
  21. data/Rakefile +0 -13
  22. data/ci/Gemfile.ar-edge +0 -8
  23. data/ci/Gemfile.ar3.1 +0 -8
  24. data/ci/Gemfile.ar3.2 +0 -8
  25. data/lib/sexy_scopes/arel/boolean_methods.rb +0 -20
  26. data/lib/sexy_scopes/arel/math_methods.rb +0 -25
  27. data/lib/sexy_scopes/expression_wrappers.rb +0 -7
  28. data/lib/sexy_scopes/predicate_wrappers.rb +0 -6
  29. data/lib/sexy_scopes/wrappers.rb +0 -13
  30. data/sexy_scopes.gemspec +0 -32
  31. data/spec/active_record_spec.rb +0 -73
  32. data/spec/boolean_methods_spec.rb +0 -45
  33. data/spec/fixtures/models.rb +0 -2
  34. data/spec/fixtures/schema.rb +0 -9
  35. data/spec/matchers/be_extended_by.rb +0 -6
  36. data/spec/matchers/convert_to_sql.rb +0 -13
  37. data/spec/math_methods_spec.rb +0 -55
  38. data/spec/predicate_methods_spec.rb +0 -38
  39. data/spec/spec_helper.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce33d394e7388e54629f64eb7ffb6db752337088
4
- data.tar.gz: 08ed0052a528fba6aec3bdfc27ef53e36c881790
3
+ metadata.gz: ea9d2e3c3ca0c50058139d67222d97894fb0192c
4
+ data.tar.gz: aa1d5490fe8d78f9a3323a25bf10e78cdf072909
5
5
  SHA512:
6
- metadata.gz: fcf5492bd6d3717504dcf30371796781cd46d13ef5b9fc2de143925b93b6488141f9cf705348b20c0f77fe4e60dfc3b05b7bed8ce4a415a57407ecd381104d33
7
- data.tar.gz: 490a59156615aeef8e8e94bafb92877c20ebf30680bac27120e5883913ac34da5cf724c694e5d19ea758c17b6fe03ed5a4aa8678f0fa5f5d8edea2227e0a95b1
6
+ metadata.gz: 08a625a5ff5b18d1a0955efcb48bd66093a794382b4d86ee8ad8676fdf28ad34a8aaabd86fa3b8c403a3d328c2b1f06bf72139a12b871dfbeb2d449d64992bea
7
+ data.tar.gz: 5ec1351dc43eebefeed0defc5ea6da10a9d56cfb41f9812b59e57160f9ed18da9891a5ce0da1ee418b892bd3baef8d76789e461f6ad19e24164ee25613f0f757
data/CHANGELOG.md CHANGED
@@ -1,9 +1,24 @@
1
- ### 0.6.0
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
2
3
 
3
- - Drop support for Ruby < 1.9.2 and ActiveRecord < 3.1
4
- - Add support for JRuby and Rubinius
5
- - Integrate with Travis CI
4
+ ## 0.7.0 - 2014-07-23
6
5
 
7
- ### 0.5.1
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
- - Initial stable release
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
- [![Gem Version](https://badge.fury.io/rb/sexy_scopes.png)](https://rubygems.org/gems/sexy_scopes)
5
- [![Dependencies](https://gemnasium.com/samleb/sexy_scopes.png?travis)](https://gemnasium.com/samleb/sexy_scopes)
6
- [![Code Climate](https://codeclimate.com/github/samleb/sexy_scopes.png)](https://codeclimate.com/github/samleb/sexy_scopes)
7
- [![Build Status](https://api.travis-ci.org/samleb/sexy_scopes.png)](https://travis-ci.org/samleb/sexy_scopes)
8
- [![Coverage Status](https://coveralls.io/repos/samleb/sexy_scopes/badge.png)](https://coveralls.io/r/samleb/sexy_scopes)
4
+ [![Gem Version](http://img.shields.io/gem/v/sexy_scopes.svg?style=flat)](https://rubygems.org/gems/sexy_scopes)
5
+ [![Dependencies](https://img.shields.io/gemnasium/samleb/sexy_scopes.svg?style=flat)](https://gemnasium.com/samleb/sexy_scopes)
6
+ [![Code Climate](https://img.shields.io/codeclimate/github/samleb/sexy_scopes.svg?style=flat)](https://codeclimate.com/github/samleb/sexy_scopes)
7
+ [![Build Status](https://img.shields.io/travis/samleb/sexy_scopes.svg?style=flat)](https://travis-ci.org/samleb/sexy_scopes)
8
+ [![Coverage Status](https://img.shields.io/coveralls/samleb/sexy_scopes.svg?style=flat)](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
- $ bundle
171
+ bundle
172
172
 
173
173
  Or install it yourself as:
174
174
 
175
- $ gem install sexy_scopes
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/dependencies/autoload'
1
+ require 'active_support/lazy_load_hooks'
2
+ require 'sexy_scopes/version'
2
3
 
3
4
  module SexyScopes
4
- %w( Version VERSION ).each do |constant|
5
- autoload constant, 'sexy_scopes/version'
6
- end
7
-
8
- extend ActiveSupport::Autoload
5
+ autoload :Arel, 'sexy_scopes/arel'
9
6
 
10
- autoload :Wrappers
11
- autoload :ExpressionWrappers
12
- autoload :PredicateWrappers
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
- require 'sexy_scopes/active_record'
39
+ ActiveSupport.on_load :active_record do
40
+ require 'sexy_scopes/active_record'
41
+ end
19
42
  end
@@ -1,4 +1,3 @@
1
- require 'active_record'
2
1
  require 'sexy_scopes/active_record/class_methods'
3
2
  require 'sexy_scopes/active_record/dynamic_methods'
4
3
 
@@ -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
- # User.where(User.attribute(:score) > 1000)
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
- # def Circle.with_perimeter_smaller_than(perimeter)
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
- # # @!visibility private
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
- sexy_scopes_has_attribute?(method_name) || super
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 sexy_scopes_has_attribute?(name)
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 sexy_scopes_define_attribute_method(name)
50
- class_eval <<-EVAL, __FILE__, __LINE__ + 1
51
- def self.#{name} # def self.username
52
- attribute(:#{name}) # attribute(:username)
53
- end # end
54
- EVAL
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
- if equal?(::ActiveRecord::Base) || abstract_class? || !table_exists?
59
- false
60
- else
61
- column_names.include?(attribute_name.to_s)
62
- end
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
@@ -1,34 +1,16 @@
1
+ require 'arel/visitors_extensions'
2
+
1
3
  module SexyScopes
2
4
  module Arel
3
- extend ActiveSupport::Autoload
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
- # As <tt>SqlLiteral</tt> could be any arbitrary SQL expression, include <tt>Arel::Math</tt>
22
- # to allow common mathematic operations.
23
- #
24
- # @see SexyScopes::ActiveRecord#sql_literal
25
- class SqlLiteral
26
- include Arel::Math
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
@@ -0,0 +1,9 @@
1
+ module SexyScopes
2
+ module Arel
3
+ module ExpressionMethods
4
+ include ::Arel::Math
5
+ include Predications
6
+ include Math
7
+ end
8
+ end
9
+ end