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 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