queryable_with 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -0
- data/README.rdoc +88 -0
- data/Rakefile +33 -0
- data/lib/queryable_with.rb +194 -0
- data/lib/queryable_with/version.rb +9 -0
- data/spec/queryable_with_spec.rb +239 -0
- data/spec/spec_helper.rb +37 -0
- metadata +93 -0
data/CHANGELOG
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
= QueryableWith
|
2
|
+
|
3
|
+
QueryableWith is an ActiveRecord library for creating and combining reusable sets of scopes. Instead of building up
|
4
|
+
your query using if-conditions checking for the existence of certain parameters--a common use case in reporting and
|
5
|
+
controller query logic--QueryableWith combines them together, and only uses the ones the params you pass need.
|
6
|
+
|
7
|
+
For example,
|
8
|
+
|
9
|
+
class User < ActiveRecord::Base
|
10
|
+
named_scope :born_after, lambda {|date|
|
11
|
+
{ :conditions => ["birthdate >= ?", date] }
|
12
|
+
}
|
13
|
+
|
14
|
+
named_scope :active, :conditions => { :active => true }
|
15
|
+
|
16
|
+
query_set :filter do
|
17
|
+
add_scope :active
|
18
|
+
queryable_with :email
|
19
|
+
queryable_with(:born_after) { |d| Date.parse(d) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
will add a method, <tt>User.filter</tt>, that knows how to filter on the <tt>email</tt> and <tt>born_after</tt>
|
24
|
+
parameters by, respectively, performing an equality search against that column, and using the defined name
|
25
|
+
scope (after passing the given date value, in this case a string, through the block). It will always add the
|
26
|
+
<tt>active</tt> scope. For example,
|
27
|
+
|
28
|
+
User.filter
|
29
|
+
#=> User.active
|
30
|
+
|
31
|
+
User.filter(:email => "gthreepwood@melee.gov")
|
32
|
+
#=> User.active.scoped(:conditions => { :email => "gthreepwood@melee.gov"})
|
33
|
+
|
34
|
+
User.filter(:born_after => "10/4/2010")
|
35
|
+
#=> User.active.born_after(#<Date: 4910947/2,0,2299161>)
|
36
|
+
|
37
|
+
See QueryableWith::ClassMethods#query_set for more information.
|
38
|
+
|
39
|
+
== Another Example
|
40
|
+
|
41
|
+
class User < ActiveRecord::Base
|
42
|
+
query_set :search do
|
43
|
+
# If queried by :name, execute a LIKE query on both the :first_name and :last_name columns.
|
44
|
+
# User.scoped(:conditions => ["(users.first_name LIKE ?) OR (users.last_name LIKE ?)", ...])
|
45
|
+
queryable_with :name, :columns => [ :first_name, :last_name ], :wildcard => true
|
46
|
+
|
47
|
+
# If queried by :username, execute a LIKE query on the the email column.
|
48
|
+
# User.scoped(:conditions => ["(users.email LIKE ?)", ...])
|
49
|
+
queryable_with :username, :column => :email, :wildcard => true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
User.search(:name => ["Guy", "Three"])
|
54
|
+
#=> User.scoped(:conditions => ["(users.first_name LIKE ?) OR (users.last_name LIKE ?) OR ...", "%Guy%", "%Three%", "%Guy%", "%Three%"]
|
55
|
+
|
56
|
+
User.search(:username => "gthree")
|
57
|
+
#=> User.scoped(:conditions => { :email => "%gthree%"})
|
58
|
+
|
59
|
+
== Installation and usage
|
60
|
+
|
61
|
+
QueryableWith is available as a gem. It has a dependency on ActiveRecord, which it will automatically extend with
|
62
|
+
the necessary class methods when required. It is not yet compatible with Rails3.
|
63
|
+
|
64
|
+
gem install queryable_with
|
65
|
+
require 'queryable_with'
|
66
|
+
|
67
|
+
== License
|
68
|
+
|
69
|
+
Copyright (c) 2010 Brian Guthrie
|
70
|
+
|
71
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
72
|
+
a copy of this software and associated documentation files (the
|
73
|
+
"Software"), to deal in the Software without restriction, including
|
74
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
75
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
76
|
+
permit persons to whom the Software is furnished to do so, subject to
|
77
|
+
the following conditions:
|
78
|
+
|
79
|
+
The above copyright notice and this permission notice shall be
|
80
|
+
included in all copies or substantial portions of the Software.
|
81
|
+
|
82
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
83
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
84
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
85
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
86
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
87
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
88
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
8
|
+
require "queryable_with/version"
|
9
|
+
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
Spec::Rake::SpecTask.new do |t|
|
13
|
+
t.spec_opts = ['--colour', '--format progress', '--backtrace']
|
14
|
+
end
|
15
|
+
|
16
|
+
Rake::RDocTask.new do |rd|
|
17
|
+
rd.main = "README.rdoc"
|
18
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
19
|
+
end
|
20
|
+
|
21
|
+
namespace :gem do
|
22
|
+
task :clean do
|
23
|
+
system "rm -f *.gem"
|
24
|
+
end
|
25
|
+
|
26
|
+
task :build => :clean do
|
27
|
+
system "gem build queryable_with.gemspec"
|
28
|
+
end
|
29
|
+
|
30
|
+
task :release => :build do
|
31
|
+
system "gem push queryable_with-#{QueryableWith::Version::STRING}"
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
# See README.rdoc for an extended introduction. The QueryableWith::ClassMethods#query_set,
|
4
|
+
# QueryableWith::QuerySet#queryable_with and QueryableWith::QuerySet#add_scope methods may also
|
5
|
+
# be of interest.
|
6
|
+
module QueryableWith
|
7
|
+
|
8
|
+
def self.included(active_record) # :nodoc:
|
9
|
+
active_record.send :extend, QueryableWith::ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def query_sets # :nodoc:
|
14
|
+
read_inheritable_attribute(:query_sets) || write_inheritable_hash(:query_sets, {})
|
15
|
+
end
|
16
|
+
|
17
|
+
# Defines a query set with the given name. This has the effect of defining a method that calls
|
18
|
+
# every implicit or explicit scope added to that query set.
|
19
|
+
#
|
20
|
+
# When a block is given, that block is evaluated in the context of either a newly-created query
|
21
|
+
# set or (in the case of inherited classes) the pre-existing set of that name. See
|
22
|
+
# QueryableWith::QuerySet#queryable_with and QueryableWith::QuerySet#add_scope for more details.
|
23
|
+
#
|
24
|
+
# When <tt>parent</tt> is given as an option, the new query set will inherit all of the scopes from
|
25
|
+
# the named parent.
|
26
|
+
def query_set(set_name, options={}, &block) # :yields:
|
27
|
+
set = query_set_for(set_name, options).tap { |s| s.instance_eval(&block) if block_given? }
|
28
|
+
|
29
|
+
class_eval <<-RUBY
|
30
|
+
def self.#{set_name}(params={})
|
31
|
+
self.query_sets["#{set_name}"].query(self, params)
|
32
|
+
end
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def query_set_for(set_name, options) # :nodoc:
|
39
|
+
query_sets[set_name.to_s] || query_sets.store(set_name.to_s, QueryableWith::QuerySet.new(options))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class QuerySet
|
44
|
+
|
45
|
+
def initialize(options={}) # :nodoc:
|
46
|
+
@queryables = []
|
47
|
+
|
48
|
+
if options.has_key?(:parent)
|
49
|
+
@queryables << QueryableWith::ImplicitScopeParameter.new(options[:parent])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Make this QuerySet queryable with the named parameter(s). If no other options are given,
|
54
|
+
# this will result in a query on either the column or (if defined) scope of the same
|
55
|
+
# name of the base scope and values passed to #query. It also accepts the following options:
|
56
|
+
#
|
57
|
+
# * <tt>scope</tt> - Map the incoming parameter to this scope. The argument by be a symbol (name of the
|
58
|
+
# scope), a Hash (scope conditions) or a lambda.
|
59
|
+
# * <tt>column</tt> - Map the incoming parameter to this column.
|
60
|
+
# * <tt>default</tt> - Default the incoming parameter to this value even if it isn't provided.
|
61
|
+
# * <tt>wildcard</tt> - If true, generate a SQL LIKE query with the incoming parameter. Used only
|
62
|
+
# if the <tt>scope</tt> option is absent or a block is not provided.
|
63
|
+
# * <tt>allow_blank</tt> - If true, treat incoming parameters mapped to nil or a blank string as
|
64
|
+
# IS NULL for the purposes of SQL query generation. Used only if the <tt>scope</tt> option is absent.
|
65
|
+
#
|
66
|
+
# If a block is provided, incoming parameters to the query will be passed through that function first.
|
67
|
+
# For example,
|
68
|
+
#
|
69
|
+
# queryable_with(:company_name, :scope => :by_company) do |name|
|
70
|
+
# Company.find_by_name(name)
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# will attempt to look up a company name first, then pass it to a pre-defined scope called
|
74
|
+
# <tt>by_company</tt>.
|
75
|
+
def queryable_with(*expected_parameters, &block)
|
76
|
+
options = expected_parameters.extract_options!
|
77
|
+
|
78
|
+
@queryables += expected_parameters.map do |parameter|
|
79
|
+
QueryableWith::QueryableParameter.new(parameter, options, &block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Add a scope that is always applied to any calls to #query. This may be a symbol (the name of
|
84
|
+
# the scope to add), a Hash (scope conditions) or a lambda. Useful when, for example, you only
|
85
|
+
# ever want to see records with an <tt>active</tt> flag set to true.
|
86
|
+
#
|
87
|
+
# add_scope :active
|
88
|
+
# add_scope :conditions => { :active => true }
|
89
|
+
def add_scope(scope)
|
90
|
+
@queryables << QueryableWith::ImplicitScopeParameter.new(scope)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Applies all of the defined queryable and added scopes in this query set to the given base scope
|
94
|
+
# (usually an ActiveRecord class, but can also be a pre-existing NamedScope object) based on the
|
95
|
+
# query parameters and returns a new scope, ready to be queried.
|
96
|
+
def query(base_scope, params={}) # :nodoc:
|
97
|
+
@queryables.inject(base_scope) do |scope, queryer|
|
98
|
+
queryer.query(scope, params)
|
99
|
+
end.scoped({})
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
class ImplicitScopeParameter # :nodoc:
|
105
|
+
|
106
|
+
def initialize(scope)
|
107
|
+
@scope = scope
|
108
|
+
end
|
109
|
+
|
110
|
+
def query(queryer, params={})
|
111
|
+
if @scope.is_a? Symbol
|
112
|
+
queryer.send @scope, params
|
113
|
+
else
|
114
|
+
queryer.scoped @scope
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
class QueryableParameter # :nodoc:
|
121
|
+
attr_reader :expected_parameter, :column_name
|
122
|
+
|
123
|
+
def initialize(expected_parameter, options={}, &block)
|
124
|
+
@scope, @wildcard, @default_value, @allow_blank = options.values_at(:scope, :wildcard, :default, :allow_blank)
|
125
|
+
@expected_parameter = expected_parameter.to_sym
|
126
|
+
@column_name = options[:column] || @expected_parameter.to_s
|
127
|
+
@value_mapper = block || lambda {|o| o}
|
128
|
+
end
|
129
|
+
|
130
|
+
def scoped?; !@scope.blank?; end
|
131
|
+
def wildcard?; @wildcard == true; end
|
132
|
+
def blank_allowed?; @allow_blank == true; end
|
133
|
+
def has_default?; !@default_value.nil?; end
|
134
|
+
def scope_name; @scope || self.expected_parameter; end
|
135
|
+
|
136
|
+
def query(queryer, params={})
|
137
|
+
params = params.with_indifferent_access
|
138
|
+
params_contain_queryable_value, queried_value = determine_queried_value(params[@expected_parameter])
|
139
|
+
|
140
|
+
return queryer unless params_contain_queryable_value
|
141
|
+
queried_value = @value_mapper.call(queried_value)
|
142
|
+
|
143
|
+
if scoped? || queryer_scoped?(queryer)
|
144
|
+
queryer.send scope_name, queried_value
|
145
|
+
else
|
146
|
+
queryer.scoped(:conditions => conditions_for(queryer, queried_value))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
protected
|
151
|
+
|
152
|
+
def determine_queried_value(value_in_params)
|
153
|
+
if value_in_params.blank? && value_in_params != false
|
154
|
+
if blank_allowed?
|
155
|
+
return true, nil
|
156
|
+
elsif has_default?
|
157
|
+
return true, @default_value
|
158
|
+
else
|
159
|
+
return false, nil
|
160
|
+
end
|
161
|
+
else
|
162
|
+
return true, value_in_params
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def queryer_scoped?(queryer)
|
167
|
+
queryer.scopes.keys.include?(@expected_parameter)
|
168
|
+
end
|
169
|
+
|
170
|
+
def conditions_for(queryer, value)
|
171
|
+
query_string = if wildcard?
|
172
|
+
"(#{queryer.table_name}.#{self.column_name} LIKE ?)"
|
173
|
+
else
|
174
|
+
"(#{queryer.table_name}.#{self.column_name} = ?)"
|
175
|
+
end
|
176
|
+
|
177
|
+
final_values = [ value ].flatten.map do |value|
|
178
|
+
wildcard? ? "%#{value}%" : value
|
179
|
+
end
|
180
|
+
|
181
|
+
final_query_string = ( [ query_string ] * final_values.size ).join(" OR ")
|
182
|
+
|
183
|
+
[ final_query_string ] + final_values
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
module ActiveRecord # :nodoc:
|
191
|
+
class Base # :nodoc:
|
192
|
+
include QueryableWith
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
describe QueryableWith do
|
4
|
+
before :each do
|
5
|
+
User.delete_all
|
6
|
+
User.query_sets.clear
|
7
|
+
end
|
8
|
+
|
9
|
+
it "includes itself in ActiveRecord" do
|
10
|
+
ActiveRecord::Base.ancestors.should include(QueryableWith)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "query_set" do
|
14
|
+
it "exposes a new method named after the defined query set" do
|
15
|
+
User.query_set :test
|
16
|
+
User.methods.should include("test")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns a scope that points to an empty result set if the query set is empty" do
|
20
|
+
User.query_set :test
|
21
|
+
User.test.should == [ ]
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ":parent => [query_set_name]" do
|
25
|
+
it "applies a parent set that adds a scope" do
|
26
|
+
User.query_set(:test) { add_scope(:conditions => { :active => true }) }
|
27
|
+
User.query_set(:supertest, :parent => :test) { add_scope(:conditions => { :name => "Guybrush" }) }
|
28
|
+
|
29
|
+
active_guy = User.create! :name => "Guybrush", :active => true
|
30
|
+
inactive_guy = User.create! :name => "Guybrush", :active => false
|
31
|
+
|
32
|
+
User.supertest.should == [ active_guy ]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "applies a parent set that is itself queryable" do
|
36
|
+
User.query_set(:test) { queryable_with(:name) }
|
37
|
+
User.query_set(:supertest, :parent => :test) { add_scope(:conditions => { :active => true }) }
|
38
|
+
|
39
|
+
active_guy = User.create! :name => "Guybrush", :active => true
|
40
|
+
inactive_guy = User.create! :name => "Guybrush", :active => false
|
41
|
+
active_gal = User.create! :name => "Elaine", :active => true
|
42
|
+
|
43
|
+
User.supertest(:name => "Guybrush").should == [ active_guy ]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "queryable_with" do
|
49
|
+
it "maps a parameter directly to a column" do
|
50
|
+
guybrush = User.create! :name => "Guybrush"
|
51
|
+
elaine = User.create! :name => "Elaine"
|
52
|
+
User.query_set(:test) { queryable_with(:name) }
|
53
|
+
|
54
|
+
User.test(:name => "Guybrush").should == [ guybrush ]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "maps multiple parameters, each to their own column" do
|
58
|
+
guybrush = User.create! :name => "Guybrush", :active => true
|
59
|
+
elaine = User.create! :name => "Elaine", :active => false
|
60
|
+
User.query_set(:test) { queryable_with(:name, :active) }
|
61
|
+
|
62
|
+
# User.test(:name => "Guybrush").should == [ guybrush ]
|
63
|
+
User.test(:active => false).should == [ elaine ]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "uses the correct table name when referring to columns with potentially conflicting names" do
|
67
|
+
# The employers table also has a name column.
|
68
|
+
User.query_set(:test) { queryable_with(:name) }
|
69
|
+
|
70
|
+
lambda do
|
71
|
+
User.test(:name => "Guybrush").all(:joins => :employer)
|
72
|
+
end.should_not raise_error(ActiveRecord::StatementInvalid)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "selects any one of a given list of values" do
|
76
|
+
guybrush = User.create! :name => "Guybrush"
|
77
|
+
elaine = User.create! :name => "Elaine"
|
78
|
+
User.query_set(:test) { queryable_with(:name) }
|
79
|
+
|
80
|
+
User.test(:name => ["Guybrush", "Elaine"]).should include(guybrush, elaine)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns a scope that points to an empty result set if queried with an empty set of params" do
|
84
|
+
User.query_set(:test) { queryable_with(:name) }
|
85
|
+
User.test.should == [ ]
|
86
|
+
end
|
87
|
+
|
88
|
+
it "applies multiple parameters if multiple parameters are queried" do
|
89
|
+
active_bob = User.create! :name => "Bob", :active => true
|
90
|
+
lazy_bob = User.create! :name => "Bob", :active => false
|
91
|
+
User.query_set(:test) { queryable_with(:name, :active) }
|
92
|
+
|
93
|
+
User.test(:name => "Bob", :active => true).should == [ active_bob ]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "delegates the underlying filter to a pre-existing named scope if one of that name is defined" do
|
97
|
+
guybrush = User.create! :name => "Guybrush"
|
98
|
+
User.named_scope(:naym, lambda { |name| { :conditions => { :name => name } } })
|
99
|
+
User.query_set(:test) { queryable_with(:naym) }
|
100
|
+
|
101
|
+
User.test(:naym => "Guybrush").should == [ guybrush ]
|
102
|
+
User.test(:naym => "Herrman").should == [ ]
|
103
|
+
end
|
104
|
+
|
105
|
+
it "ignores blank values" do
|
106
|
+
guybrush = User.create! :name => "Guybrush"
|
107
|
+
User.query_set(:test) { queryable_with(:name) }
|
108
|
+
User.test(:name => "").should == [ guybrush ]
|
109
|
+
User.test(:name => nil).should == [ guybrush ]
|
110
|
+
end
|
111
|
+
|
112
|
+
describe ":scope => [scope_name]" do
|
113
|
+
it "delegates the underlying filter to a pre-existing scope if passed as an option" do
|
114
|
+
guybrush = User.create! :name => "Guybrush"
|
115
|
+
User.named_scope(:naym, lambda { |name| { :conditions => { :name => name } } })
|
116
|
+
User.query_set(:test) { queryable_with(:nizame, :scope => :naym) }
|
117
|
+
|
118
|
+
User.test(:nizame => "Guybrush").should == [ guybrush ]
|
119
|
+
User.test(:nizame => "Herrman").should == [ ]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe ":wildcard => true" do
|
124
|
+
it "wildcards the given value" do
|
125
|
+
guybrush = User.create! :name => "Guybrush"
|
126
|
+
User.query_set(:test) { queryable_with(:name, :wildcard => true) }
|
127
|
+
|
128
|
+
User.test(:name => "uybru").should == [ guybrush ]
|
129
|
+
User.test(:name => "Elaine").should == [ ]
|
130
|
+
end
|
131
|
+
|
132
|
+
it "ORs multiple wildcarded values" do
|
133
|
+
guybrush = User.create! :name => "Guybrush"
|
134
|
+
elaine = User.create! :name => "Elaine"
|
135
|
+
User.query_set(:test) { queryable_with(:name, :wildcard => true) }
|
136
|
+
|
137
|
+
User.test(:name => ["uybru", "lain"]).should include(guybrush, elaine)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe ":column => [column_name]" do
|
142
|
+
it "maps the parameter to the given column name" do
|
143
|
+
guybrush = User.create! :name => "Guybrush"
|
144
|
+
elaine = User.create! :name => "Elaine"
|
145
|
+
User.query_set(:test) { queryable_with(:naym, :column => :name) }
|
146
|
+
|
147
|
+
User.test(:naym => "Guybrush").should == [ guybrush ]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe ":default => [value]" do
|
152
|
+
it "provides a default value if none is given in the query" do
|
153
|
+
guybrush = User.create! :name => "Guybrush"
|
154
|
+
elaine = User.create! :name => "Elaine"
|
155
|
+
User.query_set(:test) { queryable_with(:name, :default => "Guybrush") }
|
156
|
+
|
157
|
+
User.test.should == [ guybrush ]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe ":allow_blank => [boolean]" do
|
162
|
+
it "accepts, and queries on, blank values" do
|
163
|
+
guybrush = User.create! :name => "Guybrush"
|
164
|
+
User.query_set(:test) { queryable_with(:name, :allow_blank => true) }
|
165
|
+
User.test(:name => "").should == [ ]
|
166
|
+
User.test(:name => nil).should == [ ]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "with a block" do
|
171
|
+
it "permits you to transform the incoming value with a standard column lookup" do
|
172
|
+
guybrush = User.create! :name => "Guybrush"
|
173
|
+
elaine = User.create! :name => "Elaine"
|
174
|
+
User.query_set(:test) { queryable_with(:name) { |str| str.gsub(/fawkes/, "brush") } }
|
175
|
+
|
176
|
+
User.test(:name => "Guyfawkes").should == [ guybrush ]
|
177
|
+
end
|
178
|
+
|
179
|
+
it "permits you to transform the incoming value with a scope" do
|
180
|
+
guybrush1 = User.create! :name => "Guybrush", :employer => Employer.create!(:name => "Threepwood Nautical Services LLC")
|
181
|
+
guybrush2 = User.create! :name => "Guybrush", :employer => Employer.create!(:name => "LeChuck LeLumber, Inc.")
|
182
|
+
|
183
|
+
User.named_scope :by_employer, lambda { |employer|
|
184
|
+
{ :conditions => { :employer_id => employer } }
|
185
|
+
}
|
186
|
+
|
187
|
+
User.query_set(:test) do
|
188
|
+
queryable_with :employer_name, :scope => :by_employer do |name|
|
189
|
+
Employer.find_by_name(name)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
User.test(:employer_name => "Threepwood Nautical Services LLC").should == [ guybrush1 ]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "add_scope" do
|
199
|
+
it "adds the given named scope to every query" do
|
200
|
+
active = User.create! :active => true
|
201
|
+
inactive = User.create! :active => false
|
202
|
+
User.named_scope(:only_active, :conditions => { :active => true })
|
203
|
+
User.query_set(:test) { add_scope(:only_active) }
|
204
|
+
|
205
|
+
User.test.all.should == [ active ]
|
206
|
+
end
|
207
|
+
|
208
|
+
it "adds the given ad hoc scope to every query" do
|
209
|
+
active = User.create! :active => true
|
210
|
+
inactive = User.create! :active => false
|
211
|
+
User.query_set(:test) { add_scope(:conditions => { :active => true }) }
|
212
|
+
|
213
|
+
User.test.all.should == [ active ]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe "(subclassed)" do
|
218
|
+
it "inherits the query sets from its superclass" do
|
219
|
+
User.query_set(:test) { add_scope(:conditions => { :active => true }) }
|
220
|
+
class Pirate < User; end
|
221
|
+
active = Pirate.create! :active => true
|
222
|
+
inactive = Pirate.create! :active => false
|
223
|
+
|
224
|
+
Pirate.test.should == [ active ]
|
225
|
+
end
|
226
|
+
|
227
|
+
it "allows query sets to be extended by the subclass" do
|
228
|
+
User.query_set(:test) { add_scope(:conditions => { :active => true }) }
|
229
|
+
class Pirate < User; end
|
230
|
+
Pirate.query_set(:test) { add_scope(:conditions => { :name => "Guybrush" }) }
|
231
|
+
|
232
|
+
active_guy = Pirate.create! :name => "Guybrush", :active => true
|
233
|
+
inactive_guy = Pirate.create! :name => "Guybrush", :active => false
|
234
|
+
|
235
|
+
Pirate.test.should == [ active_guy ]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'spec'
|
5
|
+
require 'queryable_with'
|
6
|
+
|
7
|
+
LOGFILE = File.open(File.dirname(__FILE__) + '/../tmp/database.log', 'a')
|
8
|
+
ActiveRecord::Base.logger = Logger.new(LOGFILE)
|
9
|
+
ActiveRecord::Base.configurations = true
|
10
|
+
ActiveRecord::Schema.verbose = false
|
11
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define(:version => 1) do
|
14
|
+
create_table :users do |t|
|
15
|
+
t.string "name"
|
16
|
+
t.date "birthdate"
|
17
|
+
t.string "email"
|
18
|
+
t.string "join_date"
|
19
|
+
t.integer "income"
|
20
|
+
t.integer "employer_id"
|
21
|
+
t.string "type"
|
22
|
+
t.boolean "active"
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table :employers do |t|
|
26
|
+
t.string "name"
|
27
|
+
t.string "email"
|
28
|
+
t.timestamps
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class User < ActiveRecord::Base
|
33
|
+
belongs_to :employer
|
34
|
+
end
|
35
|
+
|
36
|
+
class Employer < ActiveRecord::Base
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: queryable_with
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Brian Guthrie
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-16 00:00:00 +05:30
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activerecord
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 3
|
30
|
+
- 0
|
31
|
+
version: 2.3.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :development
|
45
|
+
version_requirements: *id002
|
46
|
+
description: Tie query parameters to scopes, or create dynamic scopes on the fly. Define sets of reusable scopes for use in reporting and filtering.
|
47
|
+
email:
|
48
|
+
- btguthrie@gmail.com
|
49
|
+
executables: []
|
50
|
+
|
51
|
+
extensions: []
|
52
|
+
|
53
|
+
extra_rdoc_files: []
|
54
|
+
|
55
|
+
files:
|
56
|
+
- lib/queryable_with/version.rb
|
57
|
+
- lib/queryable_with.rb
|
58
|
+
- README.rdoc
|
59
|
+
- CHANGELOG
|
60
|
+
- Rakefile
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://github.com/bguthrie/queryable_with
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.3.6
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: An ActiveRecord library for creating reusable sets of scopes.
|
91
|
+
test_files:
|
92
|
+
- spec/queryable_with_spec.rb
|
93
|
+
- spec/spec_helper.rb
|