sexy_scopes 0.2.0 → 0.5.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.
- 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
|