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 CHANGED
@@ -1,3 +1,4 @@
1
+ Gemfile.lock
1
2
  coverage
2
- rdoc
3
3
  pkg
4
+ doc
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md CHANGED
@@ -1,34 +1,113 @@
1
- sexy_scopes
2
- ===========
3
-
4
- sexy_scope is a small wrapper around `Arel::Attribute` that adds a little syntactic
5
- sugar when creating scopes in ActiveRecord.
6
- It adds an `attribute` class method which takes an attribute name and returns an
7
- `Arel::Attribute` wrapper, which responds to common operators to return predicates
8
- objects that can be used as arguments to `ActiveRecord::Base.where`.
9
-
10
- Examples
11
- --------
12
-
13
- class Product < ActiveRecord::Base
14
- scope :untitled, where(attribute(:name) == nil)
15
-
16
- def self.cheaper_than(price)
17
- where attribute(:price) < price
18
- end
19
-
20
- def self.search(term)
21
- where attribute(:name) =~ "%#{term}%"
22
- end
23
- end
24
-
25
- Classic `Arel::Attribute` methods (`lt`, `in`, `matches`, `not`, etc.) are still available
26
- and predicates can be chained using `and` and `or`:
27
-
28
- (Product.attribute(:name) == nil).and(Product.attribute(:category).in(["foo", "bar"])).to_sql
29
- # => ("products"."name" IS NULL AND "products"."category" IN ('foo', 'bar'))
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 'rubygems'
2
- require 'rake'
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
- begin
5
- require 'jeweler'
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
- Spec::Rake::SpecTask.new(:rcov) do |spec|
29
- spec.libs << 'lib' << 'spec'
30
- spec.pattern = 'spec/**/*_spec.rb'
31
- spec.rcov = true
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
- module ClassMethods
7
- def attribute(name)
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
- ActiveRecord::Base.extend ClassMethods
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,9 @@
1
+ module SexyScopes
2
+ module Arel
3
+ module ExpressionWrappers
4
+ include Wrappers
5
+ include PredicateMethods
6
+ include ExpressionMethods
7
+ end
8
+ end
9
+ 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,9 @@
1
+ module SexyScopes
2
+ class Railtie < Rails::Railtie
3
+ initializer 'sexy_scopes' do |app|
4
+ ActiveSupport.on_load :active_record do
5
+ require 'sexy_scopes/active_record'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module SexyScopes
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 5
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+
10
+ VERSION = Version::STRING
11
+ 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 |s|
7
- s.name = %q{sexy_scopes}
8
- s.version = "0.2.0"
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
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Samuel Lebeau"]
12
- s.date = %q{2010-05-28}
13
- s.description = %q{Small DSL to create ActiveRecord attribute predicates without writing SQL.}
14
- s.email = %q{samuel.lebeau@gmail.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE",
17
- "README.md"
18
- ]
19
- s.files = [
20
- ".gitignore",
21
- "LICENSE",
22
- "README.md",
23
- "Rakefile",
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
- s.add_dependency(%q<activerecord>, [">= 3.0.0.beta"])
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
@@ -1,46 +1,47 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- describe SexyScopes do
4
- it "extends `ActiveRecord::Base` with `SexyScopes::ClassMethods`" do
5
- ActiveRecord::Base.singleton_class.included_modules.should include(SexyScopes::ClassMethods)
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 "the `attribute` class method" do
9
- before do
10
- @attribute = User.attribute(:username)
11
- end
8
+ describe ".attribute(name)" do
9
+ subject { User.attribute(:username) }
12
10
 
13
- it "returns a `SexyScopes::Attribute` instance for the given attribute" do
14
- @attribute.should be_a(SexyScopes::Attribute)
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 SexyScopes::Attribute do
20
- before do
21
- @attribute = User.attribute(:username)
22
- @arel_attribute = User.arel_table[:username]
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
- AREL_ALIASES = {
26
- :< => :lt,
27
- :<= => :lteq,
28
- :== => :eq,
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
- AREL_ALIASES.each do |method, arel_method|
35
- it "aliases `Arel::Attribute##{arel_method}` with operator `#{method}`" do
36
- @attribute.send(method, :any_value).should == @arel_attribute.send(arel_method, :any_value)
37
- end
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
- (AREL_ALIASES.values + ['in']).each do |arel_method|
41
- it "behaves like an `Arel::Attribute` when sent `#{arel_method}`" do
42
- @attribute.send(arel_method, :any_value).should == @arel_attribute.send(arel_method, :any_value)
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
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
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
- require 'spec'
6
- require 'spec/autorun'
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(:version => 1) do
34
+ ActiveRecord::Schema.define do
12
35
  create_table :users do |t|
13
- t.string :username
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
- hash: 23
5
- prerelease: false
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
- date: 2010-05-28 00:00:00 +02:00
19
- default_executable:
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
- prerelease: false
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
- hash: 31098225
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
- version_requirements: *id001
38
- - !ruby/object:Gem::Dependency
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
- requirement: &id002 !ruby/object:Gem::Requirement
88
+ version_requirements: !ruby/object:Gem::Requirement
42
89
  none: false
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- hash: 13
47
- segments:
48
- - 1
49
- - 2
50
- - 9
51
- version: 1.2.9
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
- requirement: &id003 !ruby/object:Gem::Requirement
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
- hash: 3
63
- segments:
64
- - 0
65
- version: "0"
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
66
118
  type: :development
67
- version_requirements: *id003
68
- description: Small DSL to create ActiveRecord attribute predicates without writing SQL.
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
- extra_rdoc_files:
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
- - rails/init.rb
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
- has_rdoc: true
90
- homepage: http://github.com/samleb/sexy_scopes
91
- licenses: []
92
-
153
+ homepage: https://github.com/samleb/sexy_scopes
154
+ licenses:
155
+ - MIT
93
156
  post_install_message:
94
- rdoc_options:
95
- - --charset=UTF-8
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
- hash: 3
104
- segments:
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ segments:
105
167
  - 0
106
- version: "0"
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
- hash: 3
113
- segments:
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ segments:
114
176
  - 0
115
- version: "0"
177
+ hash: 953557397482339219
116
178
  requirements: []
117
-
118
179
  rubyforge_project:
119
- rubygems_version: 1.3.7
180
+ rubygems_version: 1.8.23
120
181
  signing_key:
121
182
  specification_version: 3
122
- summary: Small DSL to create ActiveRecord attribute predicates without writing SQL.
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