scoped_from 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +82 -0
- data/MIT-LICENSE +20 -0
- data/README.mdown +135 -0
- data/Rakefile +10 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/scoped_from/active_record.rb +40 -0
- data/lib/scoped_from/query.rb +103 -0
- data/lib/scoped_from.rb +24 -0
- data/scoped_from.gemspec +24 -0
- data/spec/mocks/comment.rb +2 -0
- data/spec/mocks/comment_query.rb +2 -0
- data/spec/mocks/post.rb +2 -0
- data/spec/mocks/user.rb +14 -0
- data/spec/mocks/user_query.rb +2 -0
- data/spec/mocks/vote.rb +2 -0
- data/spec/mocks/vote_query.rb +2 -0
- data/spec/scoped_from/active_record_spec.rb +121 -0
- data/spec/scoped_from/query_spec.rb +426 -0
- data/spec/scoped_from_spec.rb +23 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/bootsrap/database.rb +11 -0
- data/spec/support/macros/user_macro.rb +13 -0
- metadata +178 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
scoped_from (0.1)
|
5
|
+
activerecord (~> 3.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
abstract (1.0.0)
|
11
|
+
actionpack (3.0.5)
|
12
|
+
activemodel (= 3.0.5)
|
13
|
+
activesupport (= 3.0.5)
|
14
|
+
builder (~> 2.1.2)
|
15
|
+
erubis (~> 2.6.6)
|
16
|
+
i18n (~> 0.4)
|
17
|
+
rack (~> 1.2.1)
|
18
|
+
rack-mount (~> 0.6.13)
|
19
|
+
rack-test (~> 0.5.7)
|
20
|
+
tzinfo (~> 0.3.23)
|
21
|
+
activemodel (3.0.5)
|
22
|
+
activesupport (= 3.0.5)
|
23
|
+
builder (~> 2.1.2)
|
24
|
+
i18n (~> 0.4)
|
25
|
+
activerecord (3.0.5)
|
26
|
+
activemodel (= 3.0.5)
|
27
|
+
activesupport (= 3.0.5)
|
28
|
+
arel (~> 2.0.2)
|
29
|
+
tzinfo (~> 0.3.23)
|
30
|
+
activesupport (3.0.5)
|
31
|
+
arel (2.0.9)
|
32
|
+
builder (2.1.2)
|
33
|
+
columnize (0.3.2)
|
34
|
+
diff-lcs (1.1.2)
|
35
|
+
erubis (2.6.6)
|
36
|
+
abstract (>= 1.0.0)
|
37
|
+
i18n (0.5.0)
|
38
|
+
linecache (0.43)
|
39
|
+
rack (1.2.2)
|
40
|
+
rack-mount (0.6.13)
|
41
|
+
rack (>= 1.0.0)
|
42
|
+
rack-test (0.5.7)
|
43
|
+
rack (>= 1.0)
|
44
|
+
railties (3.0.5)
|
45
|
+
actionpack (= 3.0.5)
|
46
|
+
activesupport (= 3.0.5)
|
47
|
+
rake (>= 0.8.7)
|
48
|
+
thor (~> 0.14.4)
|
49
|
+
rake (0.8.7)
|
50
|
+
rspec (2.5.0)
|
51
|
+
rspec-core (~> 2.5.0)
|
52
|
+
rspec-expectations (~> 2.5.0)
|
53
|
+
rspec-mocks (~> 2.5.0)
|
54
|
+
rspec-core (2.5.1)
|
55
|
+
rspec-expectations (2.5.0)
|
56
|
+
diff-lcs (~> 1.1.2)
|
57
|
+
rspec-mocks (2.5.0)
|
58
|
+
rspec-rails (2.5.0)
|
59
|
+
actionpack (~> 3.0)
|
60
|
+
activesupport (~> 3.0)
|
61
|
+
railties (~> 3.0)
|
62
|
+
rspec (~> 2.5.0)
|
63
|
+
ruby-debug (0.10.4)
|
64
|
+
columnize (>= 0.1)
|
65
|
+
ruby-debug-base (~> 0.10.4.0)
|
66
|
+
ruby-debug-base (0.10.4)
|
67
|
+
linecache (>= 0.3)
|
68
|
+
sqlite3 (1.3.3)
|
69
|
+
sqlite3-ruby (1.3.3)
|
70
|
+
sqlite3 (>= 1.3.3)
|
71
|
+
thor (0.14.6)
|
72
|
+
tzinfo (0.3.25)
|
73
|
+
|
74
|
+
PLATFORMS
|
75
|
+
ruby
|
76
|
+
|
77
|
+
DEPENDENCIES
|
78
|
+
rspec (~> 2.5.0)
|
79
|
+
rspec-rails (~> 2.5.0)
|
80
|
+
ruby-debug
|
81
|
+
scoped_from!
|
82
|
+
sqlite3-ruby
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Alexis Toulotte
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.mdown
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# ScopedFrom
|
2
|
+
|
3
|
+
Provides a simple mapping between scopes and controller parameters for
|
4
|
+
[Ruby On Rails 3](http://rubyonrails.org/).
|
5
|
+
|
6
|
+
Let see a simple example:
|
7
|
+
|
8
|
+
First, a model with some scopes:
|
9
|
+
|
10
|
+
class Post < ActiveRecord::Base
|
11
|
+
|
12
|
+
scope :commented, where('comments_count > 0')
|
13
|
+
|
14
|
+
scope :created_between, lambda { |after, before|
|
15
|
+
where('created_at >= ? AND created_at <= ?', after, before)
|
16
|
+
}
|
17
|
+
|
18
|
+
scope :search, lambda { |pattern|
|
19
|
+
where('body LIKE ?', "%#{pattern}%")
|
20
|
+
}
|
21
|
+
|
22
|
+
scope :with_category, lambda { |category_id|
|
23
|
+
where(:category_id, category_id)
|
24
|
+
}
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
After, a controller:
|
29
|
+
|
30
|
+
class PostsController < ActionController::Base
|
31
|
+
|
32
|
+
def index
|
33
|
+
@posts = Post.scoped_from(params)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
Then, it just filter your model from params:
|
39
|
+
|
40
|
+
/posts?commented=1
|
41
|
+
/posts?search=rails
|
42
|
+
/posts?search=rails&commented=1&with_category=42
|
43
|
+
|
44
|
+
## Accepted scopes
|
45
|
+
|
46
|
+
All scopes can be mapped with `scoped_from` method **except** scopes taking a
|
47
|
+
`lambda` (or a `Proc`) with an arity greater than 1 (for example:
|
48
|
+
`created_between` in the above code).
|
49
|
+
|
50
|
+
Scopes with no argument are invoked if parameter value is evaluated as `true`.
|
51
|
+
It includes `"true"`, `"yes"`, `"y"`, `"on"`, and `"1"` strings.
|
52
|
+
|
53
|
+
## Scopes restriction
|
54
|
+
|
55
|
+
You can restrict mapping to some scopes with `:only` option:
|
56
|
+
|
57
|
+
@posts = Post.scoped_from(params, :only => ['commented', 'search'])
|
58
|
+
|
59
|
+
You can also exclude some scopes from mapping with `:except` option:
|
60
|
+
|
61
|
+
@posts = Post.scoped_from(params, :except => 'commented')
|
62
|
+
|
63
|
+
## Mapping order
|
64
|
+
|
65
|
+
If you need to map an SQL order, just pass `order` parameter:
|
66
|
+
|
67
|
+
@posts = Post.scoped_from(:order => 'created_at')
|
68
|
+
|
69
|
+
Order direction can be specified using a dot, space or `:` as delimiter:
|
70
|
+
|
71
|
+
@posts = Post.scoped_from(:order => 'created_at.desc')
|
72
|
+
|
73
|
+
Note that order is SQL safe with `scoped_from` method (columns names are
|
74
|
+
checked).
|
75
|
+
|
76
|
+
## Some cool stuff
|
77
|
+
|
78
|
+
If your provide an array as parameter value, scope is invoked with each item
|
79
|
+
of the array:
|
80
|
+
|
81
|
+
@posts = Post.scoped_from(:search => ['bar', 'foo'])
|
82
|
+
|
83
|
+
is equivalent to
|
84
|
+
|
85
|
+
@posts = Post.search('bar').search('foo')
|
86
|
+
|
87
|
+
By default, blank parameter values are ignored, you can include them with
|
88
|
+
`:include_blank` option:
|
89
|
+
|
90
|
+
@posts = Post.scoped_from(params, :include_blank => true)
|
91
|
+
|
92
|
+
You may also want to filter on columns, just specify `:include_columns` option:
|
93
|
+
|
94
|
+
@posts = Post.scoped_from(params, :include_columns => true)
|
95
|
+
|
96
|
+
A query string can also be given to `scoped_from` method:
|
97
|
+
|
98
|
+
@posts = Post.scoped_from('with_category=24&search[]=foo&search[]=bar')
|
99
|
+
|
100
|
+
Returned scope from `scoped_from` method gives access to an internal query
|
101
|
+
object:
|
102
|
+
|
103
|
+
@posts = Post.scoped_from(params)
|
104
|
+
@query = @posts.query
|
105
|
+
|
106
|
+
This query provides you some convenience methods like `params`, `order_column`
|
107
|
+
and `order_direction`. This object can also be used to save user's search into
|
108
|
+
a database or other storage system.
|
109
|
+
|
110
|
+
But, you may also have to subclass this query class. You have to create a
|
111
|
+
subclass of `ScopedFrom::Query` named `#{RecordClassName}Query`. Here is an
|
112
|
+
example:
|
113
|
+
|
114
|
+
class PostQuery < ScopedFrom::Query
|
115
|
+
|
116
|
+
def category
|
117
|
+
Category.find_by_id(params[:with_category]) if params[:with_category]
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
This class has to be in load path.
|
123
|
+
|
124
|
+
Then into a view:
|
125
|
+
|
126
|
+
<% if @query.category %>
|
127
|
+
<p>All posts of category <%= @query.category.name %></p>
|
128
|
+
<% else %>
|
129
|
+
<p>All posts</p>
|
130
|
+
<% end %>
|
131
|
+
|
132
|
+
## Executing test suite
|
133
|
+
|
134
|
+
This project is fully tested with [Rspec 2](http://github.com/rspec/rspec).
|
135
|
+
Just run `rake` (after a `bundle install`).
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/lib/scoped_from')
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ScopedFrom
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def scope(name, scope_options, &block)
|
12
|
+
super
|
13
|
+
scope_arities[name] = scope_options.is_a?(Proc) ? scope_options.arity : -1
|
14
|
+
end
|
15
|
+
|
16
|
+
def scope_with_one_argument?(name)
|
17
|
+
scope_arities[name] == 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def scope_without_argument?(name)
|
21
|
+
[-1, 0].include?(scope_arities[name])
|
22
|
+
end
|
23
|
+
|
24
|
+
def scoped_from(params, options = {})
|
25
|
+
query_class = "#{name}Query".constantize rescue nil
|
26
|
+
query_class = Query unless query_class.is_a?(Class) && query_class.ancestors.include?(Query)
|
27
|
+
query_class.new(self, params, options).scope
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def scope_arities
|
33
|
+
read_inheritable_attribute(:scope_arities) || write_inheritable_attribute(:scope_arities, ActiveSupport::HashWithIndifferentAccess.new)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module ScopedFrom
|
2
|
+
|
3
|
+
class Query
|
4
|
+
|
5
|
+
ORDER_DIRECTIONS = %w( asc desc ).freeze
|
6
|
+
TRUE_VALUES = %w( true yes y on 1 ).freeze
|
7
|
+
|
8
|
+
attr_reader :params
|
9
|
+
|
10
|
+
# Available options are: - :only : to restrict to specified keys.
|
11
|
+
# - :except : to ignore specified keys.
|
12
|
+
# - :include_blank : to include blank values
|
13
|
+
# (default false).
|
14
|
+
def initialize(scope, params, options = {})
|
15
|
+
@scope = scope.scoped
|
16
|
+
@options = options
|
17
|
+
self.params = params
|
18
|
+
end
|
19
|
+
|
20
|
+
def order_column
|
21
|
+
parse_order(params['order'])[:column]
|
22
|
+
end
|
23
|
+
|
24
|
+
def order_direction
|
25
|
+
parse_order(params['order'])[:direction]
|
26
|
+
end
|
27
|
+
|
28
|
+
def scope
|
29
|
+
scope = @scope
|
30
|
+
params.each do |name, value|
|
31
|
+
[value].flatten.each do |value|
|
32
|
+
scope = scoped(scope, name, value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
decorate_scope(scope)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def decorate_scope(scope)
|
41
|
+
return scope if scope.respond_to?(:query)
|
42
|
+
def scope.query
|
43
|
+
@__query
|
44
|
+
end
|
45
|
+
scope.instance_variable_set('@__query', self)
|
46
|
+
scope
|
47
|
+
end
|
48
|
+
|
49
|
+
def order_to_sql(value)
|
50
|
+
order = parse_order(value)
|
51
|
+
"#{order[:column]} #{order[:direction].upcase}" if order.present?
|
52
|
+
end
|
53
|
+
|
54
|
+
def scoped(scope, name, value)
|
55
|
+
if name.to_s == 'order'
|
56
|
+
scope.order(order_to_sql(value))
|
57
|
+
elsif scope.scope_with_one_argument?(name)
|
58
|
+
scope.send(name, value)
|
59
|
+
elsif scope.scope_without_argument?(name)
|
60
|
+
scope.send(name)
|
61
|
+
elsif scope.column_names.include?(name.to_s)
|
62
|
+
scope.scoped(:conditions => { name => value })
|
63
|
+
else
|
64
|
+
scope
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def params=(params)
|
69
|
+
params = params.params if params.is_a?(self.class)
|
70
|
+
params = CGI.parse(params.to_s) unless params.is_a?(Hash)
|
71
|
+
@params = ActiveSupport::HashWithIndifferentAccess.new
|
72
|
+
params.each do |name, value|
|
73
|
+
values = [value].flatten
|
74
|
+
values.delete_if(&:blank?) unless @options[:include_blank]
|
75
|
+
next if values.empty?
|
76
|
+
if name.to_s == 'order'
|
77
|
+
order = parse_order(values.last)
|
78
|
+
@params[name] = "#{order[:column]}.#{order[:direction]}" if order.present?
|
79
|
+
elsif @scope.scope_without_argument?(name)
|
80
|
+
@params[name] = true if values.any? { |value| true?(value) }
|
81
|
+
elsif @scope.scope_with_one_argument?(name) || @options[:include_columns].present? && @scope.column_names.include?(name.to_s)
|
82
|
+
value = values.many? ? values : values.first
|
83
|
+
@params[name] = @params[name] ? [@params[name], value].flatten : value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
@params.slice!(*[@options[:only]].flatten) if @options[:only].present?
|
87
|
+
@params.except!(*[@options[:except]].flatten) if @options[:except].present?
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_order(value)
|
91
|
+
column, direction = value.to_s.split(/[\.:\s]+/, 2)
|
92
|
+
direction = direction.to_s.downcase
|
93
|
+
direction = ORDER_DIRECTIONS.first unless ORDER_DIRECTIONS.include?(direction)
|
94
|
+
@scope.column_names.include?(column) ? { :column => column, :direction => direction } : {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def true?(value)
|
98
|
+
TRUE_VALUES.include?(value.to_s.strip.downcase)
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/lib/scoped_from.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'cgi'
|
4
|
+
require 'active_record'
|
5
|
+
require 'active_support/core_ext/object/to_query'
|
6
|
+
|
7
|
+
module ScopedFrom
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def version
|
12
|
+
@@version ||= File.read(File.expand_path(File.dirname(__FILE__) + '/../VERSION')).strip.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
lib_path = File.expand_path(File.dirname(__FILE__) + '/scoped_from')
|
20
|
+
|
21
|
+
require "#{lib_path}/active_record"
|
22
|
+
require "#{lib_path}/query"
|
23
|
+
|
24
|
+
ActiveRecord::Base.send(:include, ScopedFrom::ActiveRecord)
|
data/scoped_from.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'scoped_from'
|
3
|
+
s.version = File.read(File.expand_path(File.dirname(__FILE__) + '/VERSION')).strip
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.author = 'Alexis Toulotte'
|
6
|
+
s.email = 'al@alweb.org'
|
7
|
+
s.homepage = 'https://github.com/alexistoulotte/scoped_from'
|
8
|
+
s.summary = 'Mapping between scopes and parameters for Rails'
|
9
|
+
s.description = 'Provides a simple mapping between Active Record scopes and controller parameters for Ruby On Rails 3'
|
10
|
+
|
11
|
+
s.rubyforge_project = 'scoped_from'
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ['lib']
|
17
|
+
|
18
|
+
s.add_dependency 'activerecord', '~> 3.0.0'
|
19
|
+
|
20
|
+
s.add_development_dependency 'rspec', '~> 2.5.0'
|
21
|
+
s.add_development_dependency 'rspec-rails', '~> 2.5.0'
|
22
|
+
s.add_development_dependency 'ruby-debug'
|
23
|
+
s.add_development_dependency 'sqlite3-ruby'
|
24
|
+
end
|
data/spec/mocks/post.rb
ADDED
data/spec/mocks/user.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class User < ActiveRecord::Base
|
2
|
+
|
3
|
+
scope :enabled, :conditions => { :enabled => true }
|
4
|
+
scope :search, lambda { |pattern|
|
5
|
+
where('firstname LIKE ? OR lastname LIKE ?', "%#{pattern}%", "%#{pattern}%")
|
6
|
+
}
|
7
|
+
scope :created_between, lambda { |after, before|
|
8
|
+
where('created_at >= ? AND created_at <= ?', after, before)
|
9
|
+
}
|
10
|
+
scope :latest, lambda {
|
11
|
+
where('created_at >= ?', 1.week.ago)
|
12
|
+
}
|
13
|
+
|
14
|
+
end
|
data/spec/mocks/vote.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScopedFrom::ActiveRecord do
|
4
|
+
|
5
|
+
describe '#scope_with_one_argument?' do
|
6
|
+
|
7
|
+
it 'is true if scope has one argument' do
|
8
|
+
User.should be_scope_with_one_argument(:search)
|
9
|
+
User.should be_scope_with_one_argument('search')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'is false if scope has no argument' do
|
13
|
+
User.should_not be_scope_with_one_argument(:latest)
|
14
|
+
User.should_not be_scope_with_one_argument('latest')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'is false if scope has more than one argument' do
|
18
|
+
User.should_not be_scope_with_one_argument(:created_between)
|
19
|
+
User.should_not be_scope_with_one_argument('created_between')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'is false if scope is not a proc' do
|
23
|
+
User.should_not be_scope_with_one_argument(:enabled)
|
24
|
+
User.should_not be_scope_with_one_argument('enabled')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'is false if scope does not exist' do
|
28
|
+
User.should_not be_scope_with_one_argument(:foo)
|
29
|
+
User.should_not be_scope_with_one_argument('foo')
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'scope_without_argument?' do
|
35
|
+
|
36
|
+
it 'is true if scope has no argument' do
|
37
|
+
User.should be_scope_without_argument(:latest)
|
38
|
+
User.should be_scope_without_argument('latest')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'is true if scope is not a proc' do
|
42
|
+
User.should be_scope_without_argument(:enabled)
|
43
|
+
User.should be_scope_without_argument('enabled')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'is false if scope has one argument' do
|
47
|
+
User.should_not be_scope_without_argument(:search)
|
48
|
+
User.should_not be_scope_without_argument('search')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'is false if scope has more than one argument' do
|
52
|
+
User.should_not be_scope_without_argument(:created_between)
|
53
|
+
User.should_not be_scope_without_argument('created_between')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'is false if scope does not exist' do
|
57
|
+
User.should_not be_scope_without_argument(:foo)
|
58
|
+
User.should_not be_scope_without_argument('foo')
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#scoped_from' do
|
64
|
+
|
65
|
+
it 'just build a new query and return its scope' do
|
66
|
+
query = mock(:query)
|
67
|
+
query.should_receive(:scope).and_return(42)
|
68
|
+
ScopedFrom::Query.should_receive(:new).with(User, 'foo', :except => 'bam').and_return(query)
|
69
|
+
User.scoped_from('foo', :except => 'bam').should == 42
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'build scopes' do
|
73
|
+
User.scoped_from(:search => 'jane').should == [users(:jane)]
|
74
|
+
User.scoped_from(:search => 'john').should == [users(:john)]
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'can be chained with other scopes' do
|
78
|
+
User.scoped_from(:search => 'jane').should == [users(:jane)]
|
79
|
+
User.enabled.scoped_from(:search => 'jane').should == []
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'can be used with order as parameter' do
|
83
|
+
User.scoped_from(:order => 'firstname').first.should == users(:jane)
|
84
|
+
User.scoped_from(:order => 'firstname.desc').first.should == users(:john)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'builds a ScopedFrom::Query' do
|
88
|
+
User.scoped_from({}).query.class.should be(ScopedFrom::Query)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'builds a ScopedFrom::Query if #{RecordClassName}Query is not defined' do
|
92
|
+
Post.scoped_from({}).query.class.should be(ScopedFrom::Query)
|
93
|
+
Object.const_defined?('PostQuery').should be_false
|
94
|
+
expect {
|
95
|
+
PostQuery
|
96
|
+
}.to raise_error(NameError, 'uninitialized constant PostQuery')
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'builds a #{Class}Query if #{RecordClassName}Query is defined and is a ScopedFrom::Query' do
|
100
|
+
Comment.scoped_from({}).query.class.should be(CommentQuery)
|
101
|
+
Comment.where(:foo => 'bar').scoped_from({}).query.class.should be(CommentQuery)
|
102
|
+
CommentQuery.should be_a(Class)
|
103
|
+
CommentQuery.ancestors.should include(ScopedFrom::Query)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'builds a ScopedFrom::Query if #{RecordClassName}Query is defined but not a subclass of ScopedFrom::Query' do
|
107
|
+
User.scoped_from({}).query.class.should be(ScopedFrom::Query)
|
108
|
+
Object.const_defined?('UserQuery').should be_true
|
109
|
+
UserQuery.should be_a(Class)
|
110
|
+
UserQuery.ancestors.should_not include(ScopedFrom::Query)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'builds a ScopedFrom::Query if #{RecordClassName}Query is defined but is a module' do
|
114
|
+
Vote.scoped_from({}).query.class.should be(ScopedFrom::Query)
|
115
|
+
Object.const_defined?('VoteQuery').should be_true
|
116
|
+
VoteQuery.should be_a(Module)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,426 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScopedFrom::Query do
|
4
|
+
|
5
|
+
def query(scope = User, params = {}, options = {})
|
6
|
+
ScopedFrom::Query.new(scope, params, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
|
11
|
+
it 'invokes #scoped method on specified scope' do
|
12
|
+
User.should_receive(:scoped)
|
13
|
+
ScopedFrom::Query.new(User, {})
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#order_column' do
|
19
|
+
|
20
|
+
it 'is column specified into "order" parameter' do
|
21
|
+
query(User, :order => 'firstname').order_column.should == 'firstname'
|
22
|
+
query(User, :order => 'lastname.desc').order_column.should == 'lastname'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'is nil if column does not exist' do
|
26
|
+
query(User, :order => 'foo').order_column.should be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'is nil if "order" param is not specified' do
|
30
|
+
query(User, :search => 'foo').order_column.should be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#order_direction' do
|
36
|
+
|
37
|
+
it 'is direction specified into "order" parameter' do
|
38
|
+
query(User, :order => 'firstname.asc').order_direction.should == 'asc'
|
39
|
+
query(User, :order => 'firstname.desc').order_direction.should == 'desc'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'is "asc" if direction is not specified' do
|
43
|
+
query(User, :order => 'firstname').order_direction.should == 'asc'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'is "asc" if direction is invalid' do
|
47
|
+
query(User, :order => 'firstname.foo').order_direction.should == 'asc'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'is direction even specified in another case' do
|
51
|
+
query(User, :order => 'firstname.ASc').order_direction.should == 'asc'
|
52
|
+
query(User, :order => 'firstname.DeSC').order_direction.should == 'desc'
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'is nil if column does not exist' do
|
56
|
+
query(User, :order => 'foo.desc').order_direction.should be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'is nil if "order" param is not specified' do
|
60
|
+
query(User, :search => 'foo').order_direction.should be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#params' do
|
66
|
+
|
67
|
+
it 'returns params specified at initialization' do
|
68
|
+
query(User, :search => 'foo', 'enabled' => true).params.should == { 'search' => 'foo', 'enabled' => true }
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns an hash with indifferent access' do
|
72
|
+
query(User, 'search' => 'bar').params.should be_a(ActiveSupport::HashWithIndifferentAccess)
|
73
|
+
query(User, 'search' => 'bar').params[:search].should == 'bar'
|
74
|
+
query(User, :search => 'bar').params['search'].should == 'bar'
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'can be converted to query string' do
|
78
|
+
query(User, :search => ['foo', 'bar'], 'enabled' => '1').params.to_query.should == 'enabled=true&search[]=foo&search[]=bar'
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#params=' do
|
84
|
+
|
85
|
+
it 'does not fails if nil is given' do
|
86
|
+
query(User, nil).params.should == {}
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'removes values that are not scopes' do
|
90
|
+
query(User, :foo => 'bar', 'search' => 'foo', :enabled => true).params.should == { 'search' => 'foo', 'enabled' => true }
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'removes blank values' do
|
94
|
+
query(User, 'enabled' => true, 'search' => " \n").params.should == { 'enabled' => true }
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'is case sensitive' do
|
98
|
+
query(User, 'Enabled' => true, "SEARCH" => 'bar').params.should be_empty
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'parse query string' do
|
102
|
+
query(User, 'search=foo%26baz&latest=true').params.should == { 'search' => 'foo&baz', 'latest' => true }
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'removes blank values from query string' do
|
106
|
+
query(User, 'search=baz&toto=&bar=%20').params.should == { 'search' => 'baz' }
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'unescapes UTF-8 chars' do
|
110
|
+
query(User, 'search=%C3%A9').params.should == { 'search' => 'é' }
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'can have multiple values (from hash)' do
|
114
|
+
query(User, :search => ['bar', 'baz']).params.should == { 'search' => ['bar', 'baz'] }
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'can have multiple values (from query string)' do
|
118
|
+
query(User, 'search=bar&search=baz').params.should == { 'search' => ['bar', 'baz'] }
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'removes blank values from array' do
|
122
|
+
query(User, :search => [nil, 'bar', "\n ", 'baz']).params.should == { 'search' => ['bar', 'baz'] }
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'converts value to true (or remove it) if scope takes no argument' do
|
126
|
+
query(User, :latest => 'y').params.should == { 'latest' => true }
|
127
|
+
query(User, :latest => 'no').params.should == {}
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'converts array value to true (or remove it) if scope takes no argument' do
|
131
|
+
query(User, :latest => ['no', 'yes']).params.should == { 'latest' => true }
|
132
|
+
query(User, :latest => ['no', nil]).params.should == {}
|
133
|
+
query(User, :latest => ['fo']).params.should == {}
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'flats array' do
|
137
|
+
query(User, :search => [nil, ['bar', '', 'foo', ["\n ", 'baz']]]).params.should == { 'search' => ['bar', 'foo', 'baz'] }
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'change array with a single value in one value' do
|
141
|
+
query(User, :search => [nil, 'bar', "\n"]).params.should == { 'search' => 'bar' }
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'does not modify given hash' do
|
145
|
+
hash = { :search => 'foo', :enabled => '1', :bar => 'foo' }
|
146
|
+
query(User, hash)
|
147
|
+
hash.should == { :search => 'foo', :enabled => '1', :bar => 'foo' }
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'does not modify given array' do
|
151
|
+
items = ['bar', 'foo', nil]
|
152
|
+
query(User, :search => items)
|
153
|
+
items.should == ['bar', 'foo', nil]
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'accepts :only option' do
|
157
|
+
query(User, { :search => 'bar', :enabled => 'true' }, :only => [:search]).params.should == { 'search' => 'bar' }
|
158
|
+
query(User, { :search => 'bar', :enabled => 'true' }, :only => 'search').params.should == { 'search' => 'bar' }
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'accepts :except option' do
|
162
|
+
query(User, { :search => 'bar', :enabled => true }, :except => [:search]).params.should == { 'enabled' => true }
|
163
|
+
query(User, { :search => 'bar', :enabled => true }, :except => 'search').params.should == { 'enabled' => true }
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'accepts a query instance' do
|
167
|
+
query(User, query(User, :search => 'toto')).params.should == { 'search' => 'toto' }
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'preserve blank values if :include_blank option is true' do
|
171
|
+
query(User, { :search => "\n ", 'enabled' => true }, :include_blank => true).params.should == { 'search' => "\n ", 'enabled' => true }
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'preserve blank values from array if :include_blank option is true' do
|
175
|
+
query(User, { 'search' => ["\n ", 'toto', 'titi'] }, :include_blank => true).params.should == { 'search' => ["\n ", 'toto', 'titi'] }
|
176
|
+
query(User, { 'search' => [] }, :include_blank => true).params.should == {}
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'also preserve blank on query string if :include_blank option is true' do
|
180
|
+
query(User, 'search=%20&enabled=true&search=foo', :include_blank => true).params.should == { 'search' => [' ', 'foo'], 'enabled' => true }
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'removes column values' do
|
184
|
+
query(User, 'firstname' => 'Jane', 'foo' => 'bar').params.should == {}
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'include column values if :include_columns option is specified' do
|
188
|
+
query(User, { 'firstname' => 'Jane', 'foo' => 'bar' }, :include_columns => true).params.should == { 'firstname' => 'Jane' }
|
189
|
+
query(User, { :firstname => 'Jane', :foo => 'bar' }, :include_columns => true).params.should == { 'firstname' => 'Jane' }
|
190
|
+
query(User, { 'firstname' => ['Jane', 'John'], 'foo' => 'bar' }, :include_columns => true).params.should == { 'firstname' => ['Jane', 'John'] }
|
191
|
+
query(User, { 'firstname' => "\n ", 'foo' => 'bar' }, :include_columns => true).params.should == {}
|
192
|
+
query(User, { 'firstname' => "\n ", 'foo' => 'bar' }, :include_columns => true, :include_blank => true).params.should == { 'firstname' => "\n " }
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'maps an "order"' do
|
196
|
+
query(User, { 'order' => 'firstname.asc' }).params.should == { 'order' => 'firstname.asc' }
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'does not map "order" if column is invalid' do
|
200
|
+
query(User, { 'order' => 'foo.asc' }).params.should == {}
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'use "asc" order direction by default' do
|
204
|
+
query(User, { 'order' => 'firstname' }).params.should == { 'order' => 'firstname.asc' }
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'use "asc" order direction if invalid' do
|
208
|
+
query(User, { 'order' => 'firstname.bar' }).params.should == { 'order' => 'firstname.asc' }
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'use "desc" order direction if specified' do
|
212
|
+
query(User, { 'order' => 'firstname.desc' }).params.should == { 'order' => 'firstname.desc' }
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'order direction is case insensitive' do
|
216
|
+
query(User, { 'order' => 'firstname.Asc' }).params.should == { 'order' => 'firstname.asc' }
|
217
|
+
query(User, { 'order' => 'firstname.DESC' }).params.should == { 'order' => 'firstname.desc' }
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'order can be specified as symbol' do
|
221
|
+
query(User, { :order => 'firstname.desc' }).params.should == { 'order' => 'firstname.desc' }
|
222
|
+
end
|
223
|
+
|
224
|
+
it "order is case sensitive" do
|
225
|
+
query(User, { 'Order' => 'firstname.desc' }).params.should == {}
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'use last "order" if many are specified' do
|
229
|
+
query(User, { 'order' => ['firstname.Asc', 'lastname.DESC'] }).params.should == { 'order' => 'lastname.desc' }
|
230
|
+
query(User, { 'order' => ['firstname.Asc', 'lastname.DESC', 'firstname.desc'] }).params.should == { 'order' => 'firstname.desc' }
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'order can be delimited by a space' do
|
234
|
+
query(User, { 'order' => 'firstname ASC' }).params.should == { 'order' => 'firstname.asc' }
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'order can be delimited by any white space' do
|
238
|
+
query(User, { 'order' => "firstname\nASC" }).params.should == { 'order' => 'firstname.asc' }
|
239
|
+
query(User, { 'order' => "firstname\t ASC" }).params.should == { 'order' => 'firstname.asc' }
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'order can be delimited by a ":"' do
|
243
|
+
query(User, { 'order' => "firstname:ASC" }).params.should == { 'order' => 'firstname.asc' }
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'order can be delimited by more than one delimiter' do
|
247
|
+
query(User, { 'order' => "firstname :. ASC" }).params.should == { 'order' => 'firstname.asc' }
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
describe '#scope' do
|
253
|
+
|
254
|
+
it 'does not execute any query' do
|
255
|
+
User.should_not_receive(:connection)
|
256
|
+
query(User, :enabled => true).scope
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'works with scopes with a lambda without arguments' do
|
260
|
+
users(:jane).update_attribute(:created_at, 10.days.ago)
|
261
|
+
query(User, :latest => true).scope.should == [users(:john)]
|
262
|
+
query(User, :latest => false).scope.should == [users(:john), users(:jane)]
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'does not modify scope specified at initialization' do
|
266
|
+
scope = User.search('foo')
|
267
|
+
q = query(scope, :enabled => true)
|
268
|
+
expect {
|
269
|
+
expect {
|
270
|
+
q.scope
|
271
|
+
}.to_not change { q.instance_variable_get('@scope') }
|
272
|
+
}.to_not change { scope }
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'returns scope (#scoped) specified at construction if params are empty' do
|
276
|
+
query.scope.should_not == User
|
277
|
+
query.scope.should == User.scoped
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'invokes many times scope if an array is given' do
|
281
|
+
query(User, :search => ['John', 'Doe']).scope.should == [users(:john)]
|
282
|
+
query(User, :search => ['John', 'Done']).scope.should == []
|
283
|
+
query(User, :search => ['John', 'Doe']).params.should == { 'search' => ['John', 'Doe'] }
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'invokes many times scope if given twice (as string & symbol)' do
|
287
|
+
query(User, :search => 'John', 'search' => 'Done').params['search'].size.should be(2)
|
288
|
+
query(User, :search => 'John', 'search' => 'Done').params['search'].should include('John', 'Done')
|
289
|
+
|
290
|
+
|
291
|
+
query(User, :search => 'John', 'search' => ['Did', 'Done']).params['search'].size.should be(3)
|
292
|
+
query(User, :search => 'John', 'search' => ['Did', 'Done']).params['search'].should include('John', 'Did', 'Done')
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'invokes last order if an array is given' do
|
296
|
+
query(User, :order => ['lastname', 'firstname']).scope.should == [users(:jane), users(:john)]
|
297
|
+
query(User, :order => ['lastname', 'firstname.desc']).scope.should == [users(:john), users(:jane)]
|
298
|
+
query(User, :order => ['firstname.desc', 'lastname']).scope.order_values.should == ['lastname ASC']
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'defines #query method on returned scoped' do
|
302
|
+
query(User).scope.should respond_to(:query)
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'does not defines #query method on scope if already defined' do
|
306
|
+
class User
|
307
|
+
def self.query
|
308
|
+
42
|
309
|
+
end
|
310
|
+
end
|
311
|
+
begin
|
312
|
+
User.query.should be(42)
|
313
|
+
query(User).scope.query.should be(42)
|
314
|
+
ensure
|
315
|
+
class User
|
316
|
+
class << self
|
317
|
+
remove_method :query
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'does not define #query method for future scopes' do
|
324
|
+
query(User).scope.query.should be_present
|
325
|
+
User.should_not respond_to(:query)
|
326
|
+
User.scoped.should_not respond_to(:query)
|
327
|
+
User.enabled.should_not respond_to(:query)
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'defined #query method returns query' do
|
331
|
+
q = query(User)
|
332
|
+
q.scope.query.should be_a(ScopedFrom::Query)
|
333
|
+
q.scope.query.should be(q)
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
describe '#scoped' do
|
339
|
+
|
340
|
+
it 'returns given scope if it has no scope with specified name' do
|
341
|
+
query.send(:scoped, User, :foo, true).should == User
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'returns given scope if scope takes more than 1 argument' do
|
345
|
+
query.send(:scoped, User, :created_between, true).should == User
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'invokes scope without arguments if scope takes not argument' do
|
349
|
+
query.send(:scoped, User.scoped, :enabled, true).should == [users(:john)]
|
350
|
+
query.send(:scoped, User.scoped, :enabled, ' 1 ').should == [users(:john)]
|
351
|
+
query.send(:scoped, User.scoped, :enabled, 'off').should == [users(:john)]
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'invokes scope with value has argument if scope takes one argument' do
|
355
|
+
query.send(:scoped, User.scoped, :search, 'doe').should == [users(:john), users(:jane)]
|
356
|
+
query.send(:scoped, User.scoped, :search, 'john').should == [users(:john)]
|
357
|
+
query.send(:scoped, User.scoped, :search, 'jane').should == [users(:jane)]
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'scope on column conditions' do
|
361
|
+
query.send(:scoped, User.scoped, :firstname, 'Jane').should == [users(:jane)]
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'invokes "order"' do
|
365
|
+
query.send(:scoped, User.scoped, :order, 'firstname.asc').should == [users(:jane), users(:john)]
|
366
|
+
query.send(:scoped, User.scoped, :order, 'firstname.desc').should == [users(:john), users(:jane)]
|
367
|
+
end
|
368
|
+
|
369
|
+
end
|
370
|
+
|
371
|
+
describe '#true?' do
|
372
|
+
|
373
|
+
it 'is true if true is given' do
|
374
|
+
query.send(:true?, true).should be_true
|
375
|
+
end
|
376
|
+
|
377
|
+
it 'is true if "true" is given' do
|
378
|
+
query.send(:true?, 'true').should be_true
|
379
|
+
query.send(:true?, 'True').should be_true
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'is true if "1" is given' do
|
383
|
+
query.send(:true?, '1').should be_true
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'is true if "on" is given' do
|
387
|
+
query.send(:true?, 'on').should be_true
|
388
|
+
query.send(:true?, 'ON ').should be_true
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'is true if "yes" is given' do
|
392
|
+
query.send(:true?, 'yes').should be_true
|
393
|
+
query.send(:true?, ' Yes ').should be_true
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'is true if "y" is given' do
|
397
|
+
query.send(:true?, 'y').should be_true
|
398
|
+
query.send(:true?, 'Y ').should be_true
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'is false if false is given' do
|
402
|
+
query.send(:true?, false).should be_false
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'is false if "false" is given' do
|
406
|
+
query.send(:true?, 'false').should be_false
|
407
|
+
query.send(:true?, 'FsALSE').should be_false
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'is false if "0" is given' do
|
411
|
+
query.send(:true?, '0').should be_false
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'is false if "off" is given' do
|
415
|
+
query.send(:true?, "off").should be_false
|
416
|
+
query.send(:true?, "Off").should be_false
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'is false otherwise' do
|
420
|
+
query.send(:true?, 42).should be_false
|
421
|
+
query.send(:true?, 'bam').should be_false
|
422
|
+
end
|
423
|
+
|
424
|
+
end
|
425
|
+
|
426
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScopedFrom do
|
4
|
+
|
5
|
+
describe '.version' do
|
6
|
+
|
7
|
+
it 'is a string' do
|
8
|
+
ScopedFrom.version.should be_a(String)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'is with correct format' do
|
12
|
+
ScopedFrom.version.should match(/^\d+\.\d+(\.\d+)?/)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'is freezed' do
|
16
|
+
expect {
|
17
|
+
ScopedFrom.version.gsub!('.', '#')
|
18
|
+
}.to raise_error(TypeError, "can't modify frozen string")
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
ENV["RAILS_ENV"] ||= 'test'
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/../lib/scoped_from'
|
4
|
+
|
5
|
+
# Support
|
6
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
7
|
+
|
8
|
+
# Mocks
|
9
|
+
ActiveSupport::Dependencies.autoload_paths << "#{File.dirname(__FILE__)}/mocks"
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.mock_with(:rspec)
|
13
|
+
|
14
|
+
config.include(UserMacro)
|
15
|
+
|
16
|
+
config.before(:each) do
|
17
|
+
Comment.delete_all
|
18
|
+
Post.delete_all
|
19
|
+
User.delete_all
|
20
|
+
Vote.delete_all
|
21
|
+
|
22
|
+
create_user(:john, :firstname => 'John', :lastname => 'Doe', :enabled => true)
|
23
|
+
create_user(:jane, :firstname => 'Jane', :lastname => 'Doe', :enabled => false)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => "#{File.dirname(__FILE__)}/../../test.sqlite3", :timeout => 5000)
|
2
|
+
|
3
|
+
ActiveRecord::Base.connection.create_table(:comments, :force => true)
|
4
|
+
ActiveRecord::Base.connection.create_table(:posts, :force => true)
|
5
|
+
ActiveRecord::Base.connection.create_table(:users, :force => true) do |t|
|
6
|
+
t.string :firstname, :null => false
|
7
|
+
t.string :lastname, :null => false
|
8
|
+
t.boolean :enabled, :null => false
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
ActiveRecord::Base.connection.create_table(:votes, :force => true)
|
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scoped_from
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 9
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: "0.1"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Alexis Toulotte
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-03-18 00:00:00 +11:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activerecord
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 7
|
29
|
+
segments:
|
30
|
+
- 3
|
31
|
+
- 0
|
32
|
+
- 0
|
33
|
+
version: 3.0.0
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 27
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 5
|
48
|
+
- 0
|
49
|
+
version: 2.5.0
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: rspec-rails
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 27
|
61
|
+
segments:
|
62
|
+
- 2
|
63
|
+
- 5
|
64
|
+
- 0
|
65
|
+
version: 2.5.0
|
66
|
+
type: :development
|
67
|
+
version_requirements: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: ruby-debug
|
70
|
+
prerelease: false
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
type: :development
|
81
|
+
version_requirements: *id004
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: sqlite3-ruby
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
type: :development
|
95
|
+
version_requirements: *id005
|
96
|
+
description: Provides a simple mapping between Active Record scopes and controller parameters for Ruby On Rails 3
|
97
|
+
email: al@alweb.org
|
98
|
+
executables: []
|
99
|
+
|
100
|
+
extensions: []
|
101
|
+
|
102
|
+
extra_rdoc_files: []
|
103
|
+
|
104
|
+
files:
|
105
|
+
- .gitignore
|
106
|
+
- .rspec
|
107
|
+
- Gemfile
|
108
|
+
- Gemfile.lock
|
109
|
+
- MIT-LICENSE
|
110
|
+
- README.mdown
|
111
|
+
- Rakefile
|
112
|
+
- VERSION
|
113
|
+
- init.rb
|
114
|
+
- lib/scoped_from.rb
|
115
|
+
- lib/scoped_from/active_record.rb
|
116
|
+
- lib/scoped_from/query.rb
|
117
|
+
- scoped_from.gemspec
|
118
|
+
- spec/mocks/comment.rb
|
119
|
+
- spec/mocks/comment_query.rb
|
120
|
+
- spec/mocks/post.rb
|
121
|
+
- spec/mocks/user.rb
|
122
|
+
- spec/mocks/user_query.rb
|
123
|
+
- spec/mocks/vote.rb
|
124
|
+
- spec/mocks/vote_query.rb
|
125
|
+
- spec/scoped_from/active_record_spec.rb
|
126
|
+
- spec/scoped_from/query_spec.rb
|
127
|
+
- spec/scoped_from_spec.rb
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
- spec/support/bootsrap/database.rb
|
130
|
+
- spec/support/macros/user_macro.rb
|
131
|
+
has_rdoc: true
|
132
|
+
homepage: https://github.com/alexistoulotte/scoped_from
|
133
|
+
licenses: []
|
134
|
+
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
hash: 3
|
146
|
+
segments:
|
147
|
+
- 0
|
148
|
+
version: "0"
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
hash: 3
|
155
|
+
segments:
|
156
|
+
- 0
|
157
|
+
version: "0"
|
158
|
+
requirements: []
|
159
|
+
|
160
|
+
rubyforge_project: scoped_from
|
161
|
+
rubygems_version: 1.5.1
|
162
|
+
signing_key:
|
163
|
+
specification_version: 3
|
164
|
+
summary: Mapping between scopes and parameters for Rails
|
165
|
+
test_files:
|
166
|
+
- spec/mocks/comment.rb
|
167
|
+
- spec/mocks/comment_query.rb
|
168
|
+
- spec/mocks/post.rb
|
169
|
+
- spec/mocks/user.rb
|
170
|
+
- spec/mocks/user_query.rb
|
171
|
+
- spec/mocks/vote.rb
|
172
|
+
- spec/mocks/vote_query.rb
|
173
|
+
- spec/scoped_from/active_record_spec.rb
|
174
|
+
- spec/scoped_from/query_spec.rb
|
175
|
+
- spec/scoped_from_spec.rb
|
176
|
+
- spec/spec_helper.rb
|
177
|
+
- spec/support/bootsrap/database.rb
|
178
|
+
- spec/support/macros/user_macro.rb
|