queryable_with 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|