scoped_from 0.1
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 +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
|