sexy_scopes 0.2.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/Gemfile +2 -0
- data/README.md +109 -30
- data/Rakefile +14 -31
- data/lib/sexy_scopes.rb +8 -19
- data/lib/sexy_scopes/active_record.rb +92 -0
- data/lib/sexy_scopes/arel.rb +35 -0
- data/lib/sexy_scopes/arel/expression_methods.rb +21 -0
- data/lib/sexy_scopes/arel/expression_wrappers.rb +9 -0
- data/lib/sexy_scopes/arel/predicate_methods.rb +50 -0
- data/lib/sexy_scopes/arel/predicate_wrappers.rb +22 -0
- data/lib/sexy_scopes/railtie.rb +9 -0
- data/lib/sexy_scopes/version.rb +11 -0
- data/lib/sexy_scopes/wrappers.rb +13 -0
- data/sexy_scopes.gemspec +26 -55
- data/spec/expression_methods_spec.rb +47 -0
- data/spec/predicate_methods_spec.rb +44 -0
- data/spec/sexy_scopes_spec.rb +31 -30
- data/spec/spec_helper.rb +30 -6
- metadata +149 -86
- data/VERSION +0 -1
- data/rails/init.rb +0 -1
- data/spec/spec.opts +0 -1
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,34 +1,113 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
1
|
+
SexyScopes
|
2
|
+
==========
|
3
|
+
|
4
|
+
**Write beautiful and expressive ActiveRecord scopes without SQL**
|
5
|
+
|
6
|
+
* [Source Code](https://github.com/samleb/sexy_scopes)
|
7
|
+
* [Rubygem](http://rubygems.org/gems/sexy_scopes)
|
8
|
+
|
9
|
+
SexyScopes is a gem that adds syntactic sugar for creating scopes in Rails 3.
|
10
|
+
|
11
|
+
Usage & Examples
|
12
|
+
----------------
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class Product < ActiveRecord::Base
|
16
|
+
# `self.price` represents the `price` column
|
17
|
+
def self.cheaper_than(price)
|
18
|
+
where self.price < price
|
19
|
+
end
|
20
|
+
|
21
|
+
scope :visible, (category != nil) & (draft == false)
|
22
|
+
|
23
|
+
# can't use `name` directly here as `Product.name` method already exists (== "Product")
|
24
|
+
def self.search(term)
|
25
|
+
where attribute(:name) =~ "%#{term}%"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
Classic `Arel::Attribute` methods (`lt`, `in`, `matches`, `not`, etc.) are still
|
31
|
+
available and predicates can be chained using special operators `&` (`and`),
|
32
|
+
`|` (`or`), and `~` (`not`):
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class User < ActiveRecord::Base
|
36
|
+
scope :recently_signed_in, lambda {
|
37
|
+
where last_sign_in_at > 10.days.ago
|
38
|
+
}
|
39
|
+
|
40
|
+
(score + 20 == 40).to_sql
|
41
|
+
# => ("users"."score" + 20) = 40
|
42
|
+
|
43
|
+
((username == "Bob") | (username != "Alice")).to_sql
|
44
|
+
# => ("users"."username" = 'Bob' OR "users"."username" != 'Alice')
|
45
|
+
end
|
46
|
+
|
47
|
+
class Product < ActiveRecord::Base
|
48
|
+
predicate = (attribute(:name) == nil) & ~category.in(%w( shoes shirts ))
|
49
|
+
predicate.to_sql
|
50
|
+
# => "products"."name" IS NULL AND NOT ("products"."category" IN ('shoes', 'shirts'))
|
51
|
+
|
52
|
+
# These predicates can be used as arguments to `where`
|
53
|
+
where(predicate).all
|
54
|
+
# => SELECT "products".* FROM "products" WHERE "products"."name" IS NULL AND
|
55
|
+
# NOT ("products"."category" IN ('shoes', 'shirts'))
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
Here is a complete list of Arel method aliases:
|
60
|
+
|
61
|
+
* For predicates:
|
62
|
+
- `==`: `eq`
|
63
|
+
- `=~`: `matches`
|
64
|
+
- `!~`: `does_not_match`
|
65
|
+
- `>=`: `gteq`
|
66
|
+
- `>` : `gt`
|
67
|
+
- `<` : `lt`
|
68
|
+
- `<=`: `lteq`
|
69
|
+
- `!=`: `not_eq`
|
70
|
+
|
71
|
+
|
72
|
+
* For combination
|
73
|
+
- `&`: `and`
|
74
|
+
- `|`: `or`
|
75
|
+
- `~`: `not` (unfortunately, unary prefix `!` doesn't work with ActiveRecord)
|
76
|
+
|
77
|
+
|
78
|
+
Installation
|
79
|
+
------------
|
80
|
+
|
81
|
+
Add this line to your application's Gemfile:
|
82
|
+
|
83
|
+
gem 'sexy_scopes'
|
84
|
+
|
85
|
+
And then execute:
|
86
|
+
|
87
|
+
$ bundle
|
88
|
+
|
89
|
+
Or install it yourself as:
|
90
|
+
|
91
|
+
$ gem install sexy_scopes
|
92
|
+
|
93
|
+
Then require it in your application code:
|
94
|
+
|
95
|
+
require 'sexy_scopes'
|
96
|
+
|
97
|
+
|
98
|
+
Contributing
|
99
|
+
------------
|
100
|
+
|
101
|
+
Report bugs or suggest features using [GitHub issues](https://github.com/samleb/sexy_scopes).
|
102
|
+
|
103
|
+
1. Fork it
|
104
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
105
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
106
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
107
|
+
5. Create new Pull Request
|
108
|
+
|
30
109
|
|
31
110
|
Copyright
|
32
111
|
---------
|
33
112
|
|
34
|
-
Copyright (c) 2010 Samuel Lebeau. See LICENSE for details.
|
113
|
+
Copyright (c) 2010-2012 Samuel Lebeau. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,36 +1,19 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "sexy_scopes"
|
8
|
-
gem.summary = %Q{Small DSL to create ActiveRecord attribute predicates without writing SQL.}
|
9
|
-
gem.description = %Q{Small DSL to create ActiveRecord attribute predicates without writing SQL.}
|
10
|
-
gem.email = "samuel.lebeau@gmail.com"
|
11
|
-
gem.homepage = "http://github.com/samleb/sexy_scopes"
|
12
|
-
gem.authors = ["Samuel Lebeau"]
|
13
|
-
gem.add_dependency "activerecord", ">= 3.0.0.beta"
|
14
|
-
gem.add_development_dependency "rspec", ">= 1.2.9"
|
15
|
-
gem.add_development_dependency "yard", ">= 0"
|
16
|
-
end
|
17
|
-
Jeweler::GemcutterTasks.new
|
18
|
-
rescue LoadError
|
19
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
-
end
|
21
|
-
|
22
|
-
require 'spec/rake/spectask'
|
23
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
-
spec.libs << 'lib' << 'spec'
|
25
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
-
end
|
4
|
+
desc "Run specifications"
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
27
6
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
7
|
+
desc "Measure test coverage"
|
8
|
+
if RUBY_VERSION >= '1.9'
|
9
|
+
task :cov do
|
10
|
+
ENV['COVERAGE'] = '1'
|
11
|
+
Rake::Task['spec'].invoke
|
12
|
+
end
|
13
|
+
else
|
14
|
+
RSpec::Core::RakeTask.new(:cov) do |spec|
|
15
|
+
spec.rcov = true
|
16
|
+
end
|
32
17
|
end
|
33
18
|
|
34
|
-
task :spec => :check_dependencies
|
35
|
-
|
36
19
|
task :default => :spec
|
data/lib/sexy_scopes.rb
CHANGED
@@ -1,22 +1,11 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
require 'active_record'
|
3
|
-
require 'arel'
|
4
|
-
|
5
1
|
module SexyScopes
|
6
|
-
|
7
|
-
|
8
|
-
Attribute.new(arel_table[name])
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
class Attribute < DelegateClass(Arel::Attribute)
|
13
|
-
alias_method :<, :lt
|
14
|
-
alias_method :<=, :lteq
|
15
|
-
alias_method :==, :eq
|
16
|
-
alias_method :>=, :gteq
|
17
|
-
alias_method :>, :gt
|
18
|
-
alias_method :=~, :matches
|
2
|
+
%w( Version VERSION ).each do |constant|
|
3
|
+
autoload constant, 'sexy_scopes/version'
|
19
4
|
end
|
20
|
-
|
21
|
-
|
5
|
+
end
|
6
|
+
|
7
|
+
if defined? Rails::Railtie
|
8
|
+
require 'sexy_scopes/railtie'
|
9
|
+
else
|
10
|
+
require 'sexy_scopes/active_record'
|
22
11
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'active_record'
|
3
|
+
require 'sexy_scopes/wrappers'
|
4
|
+
require 'sexy_scopes/arel'
|
5
|
+
|
6
|
+
module SexyScopes
|
7
|
+
module ActiveRecord
|
8
|
+
include Wrappers
|
9
|
+
|
10
|
+
# Creates and extends an Arel <tt>Attribute</tt> representing the table's column with
|
11
|
+
# the given <tt>name</tt>.
|
12
|
+
#
|
13
|
+
# @param [String, Symbol] name The attribute name
|
14
|
+
#
|
15
|
+
# @note Please note that no exception is raised if no such column actually exists.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# User.where(User.attribute(:score) > 1000)
|
19
|
+
# # => SELECT "users".* FROM "users" WHERE ("users"."score" > 1000)
|
20
|
+
#
|
21
|
+
def attribute(name)
|
22
|
+
attribute = arel_table[name]
|
23
|
+
extend_expression(attribute)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates and extends an Arel <tt>SqlLiteral</tt> instance for the given <tt>expression</tt>,
|
27
|
+
# first converted to a string using <tt>to_s</tt>.
|
28
|
+
#
|
29
|
+
# @param [String, #to_s] expression Any SQL expression.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# def Circle.with_perimeter_smaller_than(perimeter)
|
33
|
+
# where sql(2 * Math::PI) * radius < perimeter
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Circle.with_perimeter_smaller_than(20)
|
37
|
+
# # => SELECT "circles".* FROM "circles" WHERE (6.283185307179586 * "circles"."radius" < 20)
|
38
|
+
#
|
39
|
+
def sql_literal(expression)
|
40
|
+
::Arel.sql(expression.to_s).tap do |literal|
|
41
|
+
extend_expression(literal)
|
42
|
+
extend_predicate(literal)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
alias_method :sql, :sql_literal
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
def respond_to?(method_name, include_private = false) # :nodoc:
|
49
|
+
super || column_names.include?(method_name.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
# Equivalent to calling {#attribute} with the missing method's <tt>name</tt> if the table
|
54
|
+
# has a column with that name.
|
55
|
+
#
|
56
|
+
# Delegates to superclass implementation otherwise, eventually raising <tt>NoMethodError</tt>.
|
57
|
+
#
|
58
|
+
# @see #attribute
|
59
|
+
#
|
60
|
+
# @note Due to the way this works, be careful not to use this syntactic sugar with existing
|
61
|
+
# <tt>ActiveRecord::Base</tt> methods (see last example).
|
62
|
+
#
|
63
|
+
# @raise [NoMethodError] if the table has no corresponding column
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# # Suppose the "users" table has an "email" column, then these are equivalent:
|
67
|
+
# User.email
|
68
|
+
# User.attribute(:email)
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# # Here is the previous example (from `attribute`) rewritten:
|
72
|
+
# User.where(User.score > 1000)
|
73
|
+
# # => SELECT "users".* FROM "users" WHERE ("users"."score" > 1000)
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# # Don't use it with existing `ActiveRecord::Base` methods, i.e. `name`:
|
77
|
+
# User.name # => "User"
|
78
|
+
# # In these cases you'll have to use `attribute` explicitely
|
79
|
+
# User.attribute(:name)
|
80
|
+
#
|
81
|
+
def method_missing(name, *args)
|
82
|
+
if column_names.include?(name.to_s)
|
83
|
+
attribute(name)
|
84
|
+
else
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Add these methods to Active Record
|
91
|
+
::ActiveRecord::Base.extend SexyScopes::ActiveRecord
|
92
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SexyScopes
|
2
|
+
module Arel
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :ExpressionWrappers
|
6
|
+
autoload :PredicateWrappers
|
7
|
+
autoload :ExpressionMethods
|
8
|
+
autoload :PredicateMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Arel
|
13
|
+
module Nodes
|
14
|
+
# <tt>Grouping</tt> nodes didn't include <tt>Arel::Predications</tt> before
|
15
|
+
# {https://github.com/rails/arel/commit/c78227d9b219933f54cecefb99c72bb231fbb8f2 this commit}.
|
16
|
+
#
|
17
|
+
# Here they are included explicitely just in case they're missing.
|
18
|
+
class Grouping
|
19
|
+
include Arel::Predications
|
20
|
+
end
|
21
|
+
|
22
|
+
# As <tt>SqlLiteral</tt> could be any arbitrary SQL expression, include <tt>Arel::Math</tt>
|
23
|
+
# to allow common mathematic operations.
|
24
|
+
#
|
25
|
+
# @see SexyScopes::ActiveRecord#sql_literal
|
26
|
+
class SqlLiteral
|
27
|
+
include Arel::Math
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @!visibility private
|
32
|
+
class SqlLiteral # :nodoc:
|
33
|
+
include Arel::Math
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SexyScopes
|
2
|
+
module Arel
|
3
|
+
module ExpressionMethods
|
4
|
+
def *(other)
|
5
|
+
extend_expression(super)
|
6
|
+
end
|
7
|
+
|
8
|
+
def +(other)
|
9
|
+
extend_expression(super)
|
10
|
+
end
|
11
|
+
|
12
|
+
def -(other)
|
13
|
+
extend_expression(super)
|
14
|
+
end
|
15
|
+
|
16
|
+
def /(other)
|
17
|
+
extend_expression(super)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module SexyScopes
|
2
|
+
module Arel
|
3
|
+
module PredicateMethods
|
4
|
+
def eq(other)
|
5
|
+
extend_predicate(super)
|
6
|
+
end
|
7
|
+
alias == eq
|
8
|
+
|
9
|
+
def in(other)
|
10
|
+
extend_predicate(super)
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches(other)
|
14
|
+
extend_predicate(super)
|
15
|
+
end
|
16
|
+
alias =~ matches
|
17
|
+
|
18
|
+
def does_not_match(other)
|
19
|
+
extend_predicate(super)
|
20
|
+
end
|
21
|
+
alias !~ does_not_match
|
22
|
+
|
23
|
+
def gteq(other)
|
24
|
+
extend_predicate(super)
|
25
|
+
end
|
26
|
+
alias >= gteq
|
27
|
+
|
28
|
+
def gt(other)
|
29
|
+
extend_predicate(super)
|
30
|
+
end
|
31
|
+
alias > gt
|
32
|
+
|
33
|
+
def lt(other)
|
34
|
+
extend_predicate(super)
|
35
|
+
end
|
36
|
+
alias < lt
|
37
|
+
|
38
|
+
def lteq(other)
|
39
|
+
extend_predicate(super)
|
40
|
+
end
|
41
|
+
alias <= lteq
|
42
|
+
|
43
|
+
def not_eq(other)
|
44
|
+
extend_predicate(super)
|
45
|
+
end
|
46
|
+
alias != not_eq
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SexyScopes
|
2
|
+
module Arel
|
3
|
+
module PredicateWrappers
|
4
|
+
include Wrappers
|
5
|
+
|
6
|
+
def not
|
7
|
+
extend_predicate(super)
|
8
|
+
end
|
9
|
+
alias ~ not
|
10
|
+
|
11
|
+
def or(other)
|
12
|
+
extend_predicate(super)
|
13
|
+
end
|
14
|
+
alias | or
|
15
|
+
|
16
|
+
def and(other)
|
17
|
+
extend_predicate(super)
|
18
|
+
end
|
19
|
+
alias & and
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SexyScopes
|
2
|
+
# @!visibility private
|
3
|
+
module Wrappers # :nodoc:
|
4
|
+
private
|
5
|
+
def extend_expression(expression)
|
6
|
+
expression.extend(Arel::ExpressionWrappers)
|
7
|
+
end
|
8
|
+
|
9
|
+
def extend_predicate(predicate)
|
10
|
+
predicate.extend(Arel::PredicateWrappers)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/sexy_scopes.gemspec
CHANGED
@@ -1,61 +1,32 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/sexy_scopes/version', __FILE__)
|
5
3
|
|
6
|
-
Gem::Specification.new do |
|
7
|
-
|
8
|
-
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = 'sexy_scopes'
|
6
|
+
gem.version = SexyScopes::VERSION
|
7
|
+
|
8
|
+
gem.summary = %{Write beautiful and expressive ActiveRecord scopes without SQL.}
|
9
|
+
gem.description = %{Small DSL to create ActiveRecord (>= 3) attribute predicates without writing SQL.}
|
10
|
+
|
11
|
+
gem.authors = ['Samuel Lebeau']
|
12
|
+
gem.email = 'samuel.lebeau@gmail.com'
|
13
|
+
gem.homepage = 'https://github.com/samleb/sexy_scopes'
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
"VERSION",
|
25
|
-
"lib/sexy_scopes.rb",
|
26
|
-
"rails/init.rb",
|
27
|
-
"sexy_scopes.gemspec",
|
28
|
-
"spec/sexy_scopes_spec.rb",
|
29
|
-
"spec/spec.opts",
|
30
|
-
"spec/spec_helper.rb"
|
31
|
-
]
|
32
|
-
s.homepage = %q{http://github.com/samleb/sexy_scopes}
|
33
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
-
s.require_paths = ["lib"]
|
35
|
-
s.rubygems_version = %q{1.3.7}
|
36
|
-
s.summary = %q{Small DSL to create ActiveRecord attribute predicates without writing SQL.}
|
37
|
-
s.test_files = [
|
38
|
-
"spec/sexy_scopes_spec.rb",
|
39
|
-
"spec/spec_helper.rb"
|
40
|
-
]
|
41
|
-
|
42
|
-
if s.respond_to? :specification_version then
|
43
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
-
s.specification_version = 3
|
45
|
-
|
46
|
-
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
-
s.add_runtime_dependency(%q<activerecord>, [">= 3.0.0.beta"])
|
48
|
-
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
49
|
-
s.add_development_dependency(%q<yard>, [">= 0"])
|
50
|
-
else
|
51
|
-
s.add_dependency(%q<activerecord>, [">= 3.0.0.beta"])
|
52
|
-
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
53
|
-
s.add_dependency(%q<yard>, [">= 0"])
|
54
|
-
end
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
|
18
|
+
gem.licenses = ['MIT']
|
19
|
+
|
20
|
+
gem.add_dependency 'activerecord', '~> 3.0'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'bundler', '~> 1.0'
|
23
|
+
gem.add_development_dependency 'rake'
|
24
|
+
gem.add_development_dependency 'rails', '~> 3.0'
|
25
|
+
gem.add_development_dependency 'rspec', '~> 2.0'
|
26
|
+
gem.add_development_dependency 'sqlite3'
|
27
|
+
if RUBY_VERSION >= '1.9'
|
28
|
+
gem.add_development_dependency 'simplecov'
|
55
29
|
else
|
56
|
-
|
57
|
-
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
58
|
-
s.add_dependency(%q<yard>, [">= 0"])
|
30
|
+
gem.add_development_dependency 'rcov'
|
59
31
|
end
|
60
32
|
end
|
61
|
-
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
shared_examples "an expression method" do
|
4
|
+
it "should return an Arel node" do
|
5
|
+
subject.class.name.should =~ /^Arel::/
|
6
|
+
end
|
7
|
+
|
8
|
+
it { should be_extended_by SexyScopes::Arel::ExpressionWrappers }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe SexyScopes::Arel::ExpressionMethods do
|
12
|
+
before do
|
13
|
+
@attribute = User.attribute(:score)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "the method `*`" do
|
17
|
+
subject { @attribute * 42.0 }
|
18
|
+
|
19
|
+
it_behaves_like "an expression method"
|
20
|
+
|
21
|
+
it { should convert_to_sql %{"users"."score" * 42.0} }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "the method `+`" do
|
25
|
+
subject { @attribute + 42.0 }
|
26
|
+
|
27
|
+
it_behaves_like "an expression method"
|
28
|
+
|
29
|
+
it { should convert_to_sql %{("users"."score" + 42.0)} }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "the method `-`" do
|
33
|
+
subject { @attribute - 42.0 }
|
34
|
+
|
35
|
+
it_behaves_like "an expression method"
|
36
|
+
|
37
|
+
it { should convert_to_sql %{("users"."score" - 42.0)} }
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "the method `/`" do
|
41
|
+
subject { @attribute / 42.0 }
|
42
|
+
|
43
|
+
it_behaves_like "an expression method"
|
44
|
+
|
45
|
+
it { should convert_to_sql %{"users"."score" / 42.0} }
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
shared_examples "a predicate method" do
|
4
|
+
it "should return an Arel node" do
|
5
|
+
subject.class.name.should =~ /^Arel::/
|
6
|
+
end
|
7
|
+
|
8
|
+
it { should be_extended_by SexyScopes::Arel::PredicateWrappers }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe SexyScopes::Arel::PredicateMethods do
|
12
|
+
before do
|
13
|
+
@attribute = User.attribute(:score)
|
14
|
+
end
|
15
|
+
|
16
|
+
METHODS = {
|
17
|
+
# Arel method => [ Ruby operator, SQL operator ]
|
18
|
+
:eq => [ '==', '= %s' ],
|
19
|
+
:in => [ nil, 'IN (%s)' ],
|
20
|
+
:matches => [ '=~', 'LIKE %s' ],
|
21
|
+
:does_not_match => [ '!~', 'NOT LIKE %s' ],
|
22
|
+
:gteq => '>=',
|
23
|
+
:gt => '>',
|
24
|
+
:lt => '<',
|
25
|
+
:lteq => '<=',
|
26
|
+
:not_eq => '!='
|
27
|
+
}
|
28
|
+
|
29
|
+
METHODS.each do |method, (operator, sql_operator)|
|
30
|
+
sql_operator ||= "#{operator} %s"
|
31
|
+
|
32
|
+
describe "the method `#{method}`" do
|
33
|
+
subject { @attribute.send(method, 42.0) }
|
34
|
+
|
35
|
+
it_behaves_like "a predicate method"
|
36
|
+
|
37
|
+
it { should convert_to_sql %{"users"."score" #{sql_operator % 42.0}} }
|
38
|
+
|
39
|
+
it "is aliased as `#{operator}`" do
|
40
|
+
@attribute.method(operator).should == @attribute.method(method)
|
41
|
+
end if operator
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/sexy_scopes_spec.rb
CHANGED
@@ -1,46 +1,47 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
describe SexyScopes do
|
4
|
-
it "
|
5
|
-
ActiveRecord::Base.
|
3
|
+
describe SexyScopes::ActiveRecord do
|
4
|
+
it "should extend ActiveRecord::Base" do
|
5
|
+
ActiveRecord::Base.should be_extended_by SexyScopes::ActiveRecord
|
6
6
|
end
|
7
7
|
|
8
|
-
describe "
|
9
|
-
|
10
|
-
@attribute = User.attribute(:username)
|
11
|
-
end
|
8
|
+
describe ".attribute(name)" do
|
9
|
+
subject { User.attribute(:username) }
|
12
10
|
|
13
|
-
it "
|
14
|
-
|
15
|
-
@attribute.name.should == :username
|
11
|
+
it "should return an Arel attribute for the given name" do
|
12
|
+
subject.should eql User.arel_table[:username]
|
16
13
|
end
|
14
|
+
|
15
|
+
it { should be_extended_by SexyScopes::Arel::ExpressionWrappers }
|
17
16
|
end
|
18
17
|
|
19
|
-
describe
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
describe ".sql_literal(expression)" do
|
19
|
+
subject { User.sql_literal('NOW()') }
|
20
|
+
|
21
|
+
it "should return an Arel literal for given expression" do
|
22
|
+
subject.should eql(::Arel.sql('NOW()'))
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
:>= => :gteq,
|
30
|
-
:> => :gt,
|
31
|
-
:=~ => :matches
|
32
|
-
}
|
25
|
+
it "should be aliased as `sql`" do
|
26
|
+
SexyScopes::ActiveRecord.instance_method(:sql).should ==
|
27
|
+
SexyScopes::ActiveRecord.instance_method(:sql_literal)
|
28
|
+
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
30
|
+
it { should be_extended_by SexyScopes::Arel::ExpressionWrappers }
|
31
|
+
|
32
|
+
it { should be_extended_by SexyScopes::Arel::PredicateWrappers }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "dynamic method handling (method_missing/respond_to?)" do
|
36
|
+
it "should delegate to `attribute` when the method name is the name of an existing column" do
|
37
|
+
User.should respond_to(:username)
|
38
|
+
User.should_receive(:attribute).with(:username).once.and_return(:ok)
|
39
|
+
User.username.should == :ok
|
38
40
|
end
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
42
|
+
it "should raise NoMethodError otherwise" do
|
43
|
+
User.should_not respond_to(:foobar)
|
44
|
+
lambda { User.foobar }.should raise_error NoMethodError
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,16 +1,40 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
if ENV['COVERAGE']
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'rspec'
|
3
7
|
require 'active_record'
|
4
8
|
require 'sexy_scopes'
|
5
|
-
|
6
|
-
|
9
|
+
|
10
|
+
RSpec::Matchers.define :be_extended_by do |expected|
|
11
|
+
match do |actual|
|
12
|
+
extended_modules = actual.singleton_class.included_modules
|
13
|
+
extended_modules.include?(expected)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec::Matchers.define :convert_to_sql do |expected|
|
18
|
+
match do |actual|
|
19
|
+
actual.to_sql == expected
|
20
|
+
end
|
21
|
+
|
22
|
+
description do
|
23
|
+
"convert to the following SQL: #{expected}"
|
24
|
+
end
|
25
|
+
|
26
|
+
failure_message_for_should do |actual|
|
27
|
+
"expected generated SQL to be \n #{expected}\ngot\n #{actual.to_sql}"
|
28
|
+
end
|
29
|
+
end
|
7
30
|
|
8
31
|
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
9
32
|
|
10
33
|
ActiveRecord::Schema.verbose = false
|
11
|
-
ActiveRecord::Schema.define
|
34
|
+
ActiveRecord::Schema.define do
|
12
35
|
create_table :users do |t|
|
13
|
-
t.string
|
36
|
+
t.string :username
|
37
|
+
t.integer :score
|
14
38
|
end
|
15
39
|
end
|
16
40
|
|
metadata
CHANGED
@@ -1,125 +1,188 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: sexy_scopes
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 0
|
10
|
-
version: 0.2.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Samuel Lebeau
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-10-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: activerecord
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
segments:
|
31
|
-
- 3
|
32
|
-
- 0
|
33
|
-
- 0
|
34
|
-
- beta
|
35
|
-
version: 3.0.0.beta
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
36
22
|
type: :runtime
|
37
|
-
|
38
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rails
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '3.0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
39
79
|
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '2.0'
|
86
|
+
type: :development
|
40
87
|
prerelease: false
|
41
|
-
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
89
|
none: false
|
43
|
-
requirements:
|
44
|
-
- -
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '2.0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: sqlite3
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
52
102
|
type: :development
|
53
|
-
version_requirements: *id002
|
54
|
-
- !ruby/object:Gem::Dependency
|
55
|
-
name: yard
|
56
103
|
prerelease: false
|
57
|
-
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: simplecov
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
58
113
|
none: false
|
59
|
-
requirements:
|
60
|
-
- -
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
|
63
|
-
segments:
|
64
|
-
- 0
|
65
|
-
version: "0"
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
66
118
|
type: :development
|
67
|
-
|
68
|
-
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: Small DSL to create ActiveRecord (>= 3) attribute predicates without
|
127
|
+
writing SQL.
|
69
128
|
email: samuel.lebeau@gmail.com
|
70
129
|
executables: []
|
71
|
-
|
72
130
|
extensions: []
|
73
|
-
|
74
|
-
|
75
|
-
- LICENSE
|
76
|
-
- README.md
|
77
|
-
files:
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
78
133
|
- .gitignore
|
134
|
+
- Gemfile
|
79
135
|
- LICENSE
|
80
136
|
- README.md
|
81
137
|
- Rakefile
|
82
|
-
- VERSION
|
83
138
|
- lib/sexy_scopes.rb
|
84
|
-
-
|
139
|
+
- lib/sexy_scopes/active_record.rb
|
140
|
+
- lib/sexy_scopes/arel.rb
|
141
|
+
- lib/sexy_scopes/arel/expression_methods.rb
|
142
|
+
- lib/sexy_scopes/arel/expression_wrappers.rb
|
143
|
+
- lib/sexy_scopes/arel/predicate_methods.rb
|
144
|
+
- lib/sexy_scopes/arel/predicate_wrappers.rb
|
145
|
+
- lib/sexy_scopes/railtie.rb
|
146
|
+
- lib/sexy_scopes/version.rb
|
147
|
+
- lib/sexy_scopes/wrappers.rb
|
85
148
|
- sexy_scopes.gemspec
|
149
|
+
- spec/expression_methods_spec.rb
|
150
|
+
- spec/predicate_methods_spec.rb
|
86
151
|
- spec/sexy_scopes_spec.rb
|
87
|
-
- spec/spec.opts
|
88
152
|
- spec/spec_helper.rb
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
153
|
+
homepage: https://github.com/samleb/sexy_scopes
|
154
|
+
licenses:
|
155
|
+
- MIT
|
93
156
|
post_install_message:
|
94
|
-
rdoc_options:
|
95
|
-
|
96
|
-
require_paths:
|
157
|
+
rdoc_options: []
|
158
|
+
require_paths:
|
97
159
|
- lib
|
98
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
161
|
none: false
|
100
|
-
requirements:
|
101
|
-
- -
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
|
104
|
-
segments:
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
segments:
|
105
167
|
- 0
|
106
|
-
|
107
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
hash: 953557397482339219
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
170
|
none: false
|
109
|
-
requirements:
|
110
|
-
- -
|
111
|
-
- !ruby/object:Gem::Version
|
112
|
-
|
113
|
-
segments:
|
171
|
+
requirements:
|
172
|
+
- - ! '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
segments:
|
114
176
|
- 0
|
115
|
-
|
177
|
+
hash: 953557397482339219
|
116
178
|
requirements: []
|
117
|
-
|
118
179
|
rubyforge_project:
|
119
|
-
rubygems_version: 1.
|
180
|
+
rubygems_version: 1.8.23
|
120
181
|
signing_key:
|
121
182
|
specification_version: 3
|
122
|
-
summary:
|
123
|
-
test_files:
|
183
|
+
summary: Write beautiful and expressive ActiveRecord scopes without SQL.
|
184
|
+
test_files:
|
185
|
+
- spec/expression_methods_spec.rb
|
186
|
+
- spec/predicate_methods_spec.rb
|
124
187
|
- spec/sexy_scopes_spec.rb
|
125
188
|
- spec/spec_helper.rb
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.2.0
|
data/rails/init.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'sexy_scopes'
|
data/spec/spec.opts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--color
|