aub-record_filter 0.1.4 → 0.2.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/VERSION.yml +2 -2
- data/lib/record_filter/active_record.rb +2 -2
- data/lib/record_filter/conjunctions.rb +12 -8
- data/lib/record_filter/dsl/conjunction.rb +2 -2
- data/lib/record_filter/dsl/conjunction_dsl.rb +7 -2
- data/lib/record_filter/dsl/join.rb +3 -3
- data/lib/record_filter/filter.rb +75 -12
- data/lib/record_filter/group_by.rb +1 -1
- data/lib/record_filter/join.rb +8 -1
- data/lib/record_filter/order.rb +1 -1
- data/lib/record_filter/query.rb +11 -2
- data/lib/record_filter/restrictions.rb +2 -2
- data/lib/record_filter/table.rb +22 -11
- data/spec/exception_spec.rb +4 -1
- data/spec/explicit_join_spec.rb +28 -1
- data/spec/implicit_join_spec.rb +91 -1
- data/spec/limits_and_ordering_spec.rb +3 -3
- data/spec/models.rb +70 -0
- data/spec/named_filter_spec.rb +80 -14
- data/spec/proxying_spec.rb +49 -0
- data/spec/restrictions_spec.rb +3 -3
- data/spec/select_spec.rb +58 -0
- data/spec/spec_helper.rb +9 -3
- data/spec/test.db +0 -0
- metadata +8 -16
- data/spec/models/blog.rb +0 -5
- data/spec/models/comment.rb +0 -5
- data/spec/models/feature.rb +0 -5
- data/spec/models/photo.rb +0 -3
- data/spec/models/post.rb +0 -10
- data/spec/models/review.rb +0 -5
- data/spec/models/tag.rb +0 -3
data/VERSION.yml
CHANGED
@@ -3,7 +3,7 @@ module RecordFilter
|
|
3
3
|
module ClassMethods
|
4
4
|
|
5
5
|
def filter(&block)
|
6
|
-
Filter.new(self, nil,
|
6
|
+
Filter.new(self, nil, &block)
|
7
7
|
end
|
8
8
|
|
9
9
|
def named_filter(name, &block)
|
@@ -15,7 +15,7 @@ module RecordFilter
|
|
15
15
|
|
16
16
|
(class << self; self; end).instance_eval do
|
17
17
|
define_method(name.to_s) do |*args|
|
18
|
-
Filter.new(self, name,
|
18
|
+
Filter.new(self, name, *args)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -18,7 +18,7 @@ module RecordFilter
|
|
18
18
|
when DSL::Conjunction
|
19
19
|
result.add_conjunction(create_from(step, table))
|
20
20
|
when DSL::Join
|
21
|
-
join = result.add_join_on_association(step.association)
|
21
|
+
join = result.add_join_on_association(step.association, step.join_type)
|
22
22
|
result.add_conjunction(create_from(step.conjunction, join.right_table))
|
23
23
|
when DSL::ClassJoin
|
24
24
|
join = result.add_join_on_class(
|
@@ -55,14 +55,14 @@ module RecordFilter
|
|
55
55
|
conjunction
|
56
56
|
end
|
57
57
|
|
58
|
-
def add_join_on_association(association_name)
|
58
|
+
def add_join_on_association(association_name, join_type)
|
59
59
|
table = @table
|
60
60
|
while association_name.is_a?(Hash)
|
61
|
-
result = table.join_association(association_name.keys[0])
|
61
|
+
result = table.join_association(association_name.keys[0], join_type)
|
62
62
|
table = result.right_table
|
63
63
|
association_name = association_name.values[0]
|
64
64
|
end
|
65
|
-
table.join_association(association_name)
|
65
|
+
table.join_association(association_name, join_type)
|
66
66
|
end
|
67
67
|
|
68
68
|
def add_join_on_class(join_class, join_type, table_alias, conditions)
|
@@ -94,16 +94,20 @@ module RecordFilter
|
|
94
94
|
else
|
95
95
|
@restrictions.map do |restriction|
|
96
96
|
conditions = restriction.to_conditions
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
if conditions
|
98
|
+
conditions[0] = "(#{conditions[0]})"
|
99
|
+
conditions
|
100
|
+
else
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end.compact.inject do |conditions, new_conditions|
|
100
104
|
conditions.first << " #{conjunctor} #{new_conditions.shift}"
|
101
105
|
conditions.concat(new_conditions)
|
102
106
|
conditions
|
103
107
|
end
|
104
108
|
end
|
105
109
|
end
|
106
|
-
result[0] = "
|
110
|
+
result[0] = "NOT (#{result[0]})" if (negated && !result.nil? && !result[0].nil?)
|
107
111
|
result
|
108
112
|
end
|
109
113
|
|
@@ -19,10 +19,10 @@ module RecordFilter
|
|
19
19
|
@steps << dsl.conjunction
|
20
20
|
end
|
21
21
|
|
22
|
-
def add_join(association, &block)
|
22
|
+
def add_join(association, join_type, &block)
|
23
23
|
dsl = ConjunctionDSL.new(@model_class, Conjunction.new(@model_class, :all_of))
|
24
24
|
dsl.instance_eval(&block) if block
|
25
|
-
@steps << Join.new(association, dsl.conjunction)
|
25
|
+
@steps << Join.new(association, join_type, dsl.conjunction)
|
26
26
|
dsl
|
27
27
|
end
|
28
28
|
|
@@ -37,8 +37,13 @@ module RecordFilter
|
|
37
37
|
end
|
38
38
|
|
39
39
|
# join
|
40
|
-
def having(association, &block)
|
41
|
-
|
40
|
+
def having(join_type_or_association, association=nil, &block)
|
41
|
+
if association.nil?
|
42
|
+
association, join_type = join_type_or_association, nil
|
43
|
+
else
|
44
|
+
join_type = join_type_or_association
|
45
|
+
end
|
46
|
+
@conjunction.add_join(association, join_type, &block)
|
42
47
|
end
|
43
48
|
|
44
49
|
def join(clazz, join_type, table_alias=nil, &block)
|
@@ -2,10 +2,10 @@ module RecordFilter
|
|
2
2
|
module DSL
|
3
3
|
class Join
|
4
4
|
|
5
|
-
attr_reader :association, :conjunction
|
5
|
+
attr_reader :association, :join_type, :conjunction
|
6
6
|
|
7
|
-
def initialize(association, conjunction)
|
8
|
-
@association, @conjunction = association, conjunction
|
7
|
+
def initialize(association, join_type, conjunction)
|
8
|
+
@association, @join_type, @conjunction = association, join_type, conjunction
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/record_filter/filter.rb
CHANGED
@@ -1,35 +1,92 @@
|
|
1
1
|
module RecordFilter
|
2
2
|
class Filter
|
3
3
|
|
4
|
-
|
4
|
+
NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?)
|
5
|
+
[].methods.each do |m|
|
6
|
+
unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
|
7
|
+
delegate m, :to => :loaded_data
|
8
|
+
end
|
9
|
+
end
|
5
10
|
|
6
|
-
def initialize(clazz, named_filter,
|
11
|
+
def initialize(clazz, named_filter, *args, &block)
|
12
|
+
@current_scoped_methods = clazz.send(:current_scoped_methods)
|
7
13
|
@clazz = clazz
|
8
14
|
|
9
15
|
@dsl = dsl_for_named_filter(@clazz, named_filter)
|
10
16
|
@dsl.instance_eval(&block) if block
|
11
17
|
@dsl.send(named_filter, *args) if named_filter && @dsl.respond_to?(named_filter)
|
12
|
-
@dsl.conjunction
|
18
|
+
@query = Query.new(@clazz, @dsl.conjunction)
|
19
|
+
end
|
20
|
+
|
21
|
+
def first(*args)
|
22
|
+
if args.first.kind_of?(Integer)
|
23
|
+
loaded_data.first(*args)
|
24
|
+
else
|
25
|
+
do_with_scope do
|
26
|
+
@clazz.find(:first, *args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def last(*args)
|
32
|
+
if args.first.kind_of?(Integer)
|
33
|
+
loaded_data.last(*args)
|
34
|
+
else
|
35
|
+
do_with_scope do
|
36
|
+
@clazz.find(:last, *args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def size
|
42
|
+
@loaded_data ? @loaded_data.length : count
|
43
|
+
end
|
44
|
+
|
45
|
+
def empty?
|
46
|
+
@loaded_data ? @loaded_data.empty? : count.zero?
|
47
|
+
end
|
48
|
+
|
49
|
+
def any?
|
50
|
+
if block_given?
|
51
|
+
loaded_data.any? { |*block_args| yield(*block_args) }
|
52
|
+
else
|
53
|
+
!empty?
|
54
|
+
end
|
13
55
|
end
|
14
56
|
|
15
57
|
def filter(&block)
|
16
|
-
|
58
|
+
do_with_scope do
|
59
|
+
Filter.new(@clazz, nil, &block)
|
60
|
+
end
|
17
61
|
end
|
18
62
|
|
63
|
+
def proxy_options(count_query=false)
|
64
|
+
@query.to_find_params(count_query)
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
19
69
|
def method_missing(method, *args, &block)
|
20
70
|
if @clazz.named_filters.include?(method)
|
21
|
-
|
71
|
+
do_with_scope do
|
72
|
+
Filter.new(@clazz, method, *args)
|
73
|
+
end
|
22
74
|
else
|
23
|
-
|
75
|
+
do_with_scope(method == :count) do
|
76
|
+
@clazz.send(method, *args, &block)
|
77
|
+
end
|
24
78
|
end
|
25
79
|
end
|
26
80
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
81
|
+
def do_with_scope(count_query=false, &block)
|
82
|
+
@clazz.send(:with_scope, { :find => proxy_options(count_query), :create => proxy_options(count_query) }, :reverse_merge) do
|
83
|
+
if @current_scoped_methods
|
84
|
+
@clazz.send(:with_scope, @current_scoped_methods) do
|
85
|
+
block.call
|
86
|
+
end
|
87
|
+
else
|
88
|
+
block.call
|
89
|
+
end
|
33
90
|
end
|
34
91
|
end
|
35
92
|
|
@@ -42,5 +99,11 @@ module RecordFilter
|
|
42
99
|
end
|
43
100
|
nil
|
44
101
|
end
|
102
|
+
|
103
|
+
def loaded_data
|
104
|
+
@loaded_data ||= do_with_scope do
|
105
|
+
@clazz.find(:all)
|
106
|
+
end
|
107
|
+
end
|
45
108
|
end
|
46
109
|
end
|
data/lib/record_filter/join.rb
CHANGED
@@ -2,9 +2,10 @@ module RecordFilter
|
|
2
2
|
class Join
|
3
3
|
attr_reader :left_table, :right_table
|
4
4
|
|
5
|
-
def initialize(left_table, right_table, join_conditions, join_type
|
5
|
+
def initialize(left_table, right_table, join_conditions, join_type=nil)
|
6
6
|
@left_table, @right_table, @join_conditions, @join_type =
|
7
7
|
left_table, right_table, join_conditions, join_type
|
8
|
+
@join_type ||= :inner
|
8
9
|
end
|
9
10
|
|
10
11
|
def to_sql
|
@@ -20,6 +21,10 @@ module RecordFilter
|
|
20
21
|
"#{join_type_string} JOIN #{@right_table.table_name} AS #{@right_table.table_alias} ON #{predicate_sql}"
|
21
22
|
end
|
22
23
|
|
24
|
+
def requires_distinct_select?
|
25
|
+
[:left, :outer, :left_outer].include?(@join_type)
|
26
|
+
end
|
27
|
+
|
23
28
|
protected
|
24
29
|
|
25
30
|
def condition_to_predicate_part(condition)
|
@@ -51,6 +56,8 @@ module RecordFilter
|
|
51
56
|
@join_type_string ||= case(@join_type)
|
52
57
|
when :inner then 'INNER'
|
53
58
|
when :left then 'LEFT'
|
59
|
+
when :left_outer then 'LEFT OUTER'
|
60
|
+
when :outer then 'OUTER'
|
54
61
|
else nil
|
55
62
|
end
|
56
63
|
end
|
data/lib/record_filter/order.rb
CHANGED
data/lib/record_filter/query.rb
CHANGED
@@ -6,10 +6,19 @@ module RecordFilter
|
|
6
6
|
@conjunction = RecordFilter::Conjunctions::Base.create_from(dsl_conjunction, @table)
|
7
7
|
end
|
8
8
|
|
9
|
-
def to_find_params
|
10
|
-
params = {
|
9
|
+
def to_find_params(count_query=false)
|
10
|
+
params = {}
|
11
|
+
conditions = @conjunction.to_conditions
|
12
|
+
params = { :conditions => conditions } if conditions
|
11
13
|
joins = @table.all_joins
|
12
14
|
params[:joins] = joins.map { |join| join.to_sql } * ' ' unless joins.empty?
|
15
|
+
if (joins.any? { |j| j.requires_distinct_select? })
|
16
|
+
if count_query
|
17
|
+
params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.#{@table.model_class.primary_key}"
|
18
|
+
else
|
19
|
+
params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.*"
|
20
|
+
end
|
21
|
+
end
|
13
22
|
orders = @table.orders
|
14
23
|
params[:order] = orders.map { |order| order.to_sql } * ', ' unless orders.empty?
|
15
24
|
group_bys = @table.group_bys
|
@@ -15,7 +15,7 @@ module RecordFilter
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_negative_sql
|
18
|
-
"
|
18
|
+
"NOT (#{to_positive_sql})"
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.class_from_operator(operator)
|
@@ -29,7 +29,7 @@ module RecordFilter
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def to_negative_sql
|
32
|
-
"#{@column_name}
|
32
|
+
"#{@column_name} <> ?"
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
data/lib/record_filter/table.rb
CHANGED
@@ -16,18 +16,25 @@ module RecordFilter
|
|
16
16
|
@model_class.quoted_table_name
|
17
17
|
end
|
18
18
|
|
19
|
-
def join_association(association_name)
|
19
|
+
def join_association(association_name, join_type=nil)
|
20
20
|
@joins_cache[association_name] ||=
|
21
21
|
begin
|
22
22
|
association = @model_class.reflect_on_association(association_name)
|
23
23
|
if association.nil?
|
24
24
|
raise AssociationNotFoundException.new("The association #{association_name} was not found on #{@model_class.name}.")
|
25
25
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
|
27
|
+
if (association.options[:through])
|
28
|
+
through_association = @model_class.reflect_on_association(association.options[:through])
|
29
|
+
through_join = join_association(association.options[:through], join_type)
|
30
|
+
through_join.right_table.join_association(association_name, join_type)
|
31
|
+
else
|
32
|
+
case association.macro
|
33
|
+
when :belongs_to, :has_many, :has_one
|
34
|
+
simple_join(association, join_type)
|
35
|
+
when :has_and_belongs_to_many
|
36
|
+
compound_join(association, join_type)
|
37
|
+
end
|
31
38
|
end
|
32
39
|
end
|
33
40
|
end
|
@@ -62,7 +69,7 @@ module RecordFilter
|
|
62
69
|
|
63
70
|
private
|
64
71
|
|
65
|
-
def simple_join(association)
|
72
|
+
def simple_join(association, join_type)
|
66
73
|
join_predicate =
|
67
74
|
case association.macro
|
68
75
|
when :belongs_to
|
@@ -70,19 +77,23 @@ module RecordFilter
|
|
70
77
|
when :has_many, :has_one
|
71
78
|
[{ :id => association.primary_key_name.to_sym }]
|
72
79
|
end
|
80
|
+
|
81
|
+
if association.options[:as]
|
82
|
+
join_predicate << DSL::Restriction.new(association.options[:as].to_s + '_type').equal_to(association.active_record.base_class.name)
|
83
|
+
end
|
73
84
|
join_table = Table.new(association.klass, alias_for_association(association))
|
74
|
-
@joins << join = Join.new(self, join_table, join_predicate)
|
85
|
+
@joins << join = Join.new(self, join_table, join_predicate, join_type)
|
75
86
|
join
|
76
87
|
end
|
77
88
|
|
78
|
-
def compound_join(association)
|
89
|
+
def compound_join(association, join_type)
|
79
90
|
pivot_join_predicate = [{ :id => association.primary_key_name.to_sym }]
|
80
91
|
table_name = @model_class.connection.quote_table_name(association.options[:join_table])
|
81
92
|
pivot_table = PivotTable.new(table_name, association, "__#{alias_for_association(association)}")
|
82
|
-
pivot_join = Join.new(self, pivot_table, pivot_join_predicate)
|
93
|
+
pivot_join = Join.new(self, pivot_table, pivot_join_predicate, join_type)
|
83
94
|
join_predicate = [{ association.association_foreign_key.to_sym => :id }]
|
84
95
|
join_table = Table.new(association.klass, alias_for_association(association))
|
85
|
-
pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate)
|
96
|
+
pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate, join_type)
|
86
97
|
@joins << pivot_join
|
87
98
|
join
|
88
99
|
end
|
data/spec/exception_spec.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
2
|
|
3
3
|
describe 'raising exceptions' do
|
4
|
-
|
4
|
+
before do
|
5
|
+
TestModel.extended_models.each { |model| model.last_find = {} }
|
6
|
+
end
|
7
|
+
|
5
8
|
describe 'on missing associations' do
|
6
9
|
it 'should get AssociationNotFoundException' do
|
7
10
|
lambda {
|
data/spec/explicit_join_spec.rb
CHANGED
@@ -73,7 +73,34 @@ describe 'explicit joins' do
|
|
73
73
|
end
|
74
74
|
|
75
75
|
it 'should add the correct join' do
|
76
|
-
Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews__Feature ON (reviews__Feature.featurable_type IS NULL) AND (reviews__Feature.featurable_id >= 12) AND (reviews__Feature.priority
|
76
|
+
Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews__Feature ON (reviews__Feature.featurable_type IS NULL) AND (reviews__Feature.featurable_id >= 12) AND (reviews__Feature.priority <> 6))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'using implicit and explicit joins together with conditions' do
|
81
|
+
before do
|
82
|
+
Blog.named_filter :somethings do
|
83
|
+
having(:ads) do
|
84
|
+
with(:content, nil)
|
85
|
+
end
|
86
|
+
join(Post, :left) do
|
87
|
+
on(:id => :blog_id)
|
88
|
+
join(Comment, :inner) do
|
89
|
+
on(:id => :post_id)
|
90
|
+
on(:offensive, true)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
group_by(:id)
|
94
|
+
end
|
95
|
+
Blog.somethings.inspect
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should produce the correct conditions' do
|
99
|
+
Blog.last_find[:conditions].should == [%q((blogs__ads.content IS NULL))]
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should produce the correct join' do
|
103
|
+
Blog.last_find[:joins].should == %q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id LEFT JOIN "posts" AS blogs__Post ON "blogs".id = blogs__Post.blog_id INNER JOIN "comments" AS blogs__Post__Comment ON blogs__Post.id = blogs__Post__Comment.post_id AND (blogs__Post__Comment.offensive = 't'))
|
77
104
|
end
|
78
105
|
end
|
79
106
|
end
|
data/spec/implicit_join_spec.rb
CHANGED
@@ -191,7 +191,7 @@ describe 'implicit joins' do
|
|
191
191
|
end
|
192
192
|
|
193
193
|
it 'should create the correct condition' do
|
194
|
-
Comment.last_find[:conditions].should == [%q("comments".offensive
|
194
|
+
Comment.last_find[:conditions].should == [%q("comments".offensive <> ?), false]
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
@@ -220,4 +220,94 @@ describe 'implicit joins' do
|
|
220
220
|
Comment.last_find[:conditions].should == [%q(("comments".contents IS NOT NULL) AND ("comments".offensive = ?)), true]
|
221
221
|
end
|
222
222
|
end
|
223
|
+
|
224
|
+
describe 'passing the join type to having' do
|
225
|
+
before do
|
226
|
+
Blog.filter do
|
227
|
+
having(:left_outer, :posts) do
|
228
|
+
with(:permalink, 'ack')
|
229
|
+
end
|
230
|
+
end.inspect
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'should create the correct condition' do
|
234
|
+
Blog.last_find[:conditions].should == [%q(blogs__posts.permalink = ?), 'ack']
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'should create the correct join' do
|
238
|
+
Blog.last_find[:joins].should == %q(LEFT OUTER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe 'passing the join type to having with multiple joins' do
|
243
|
+
before do
|
244
|
+
Blog.filter do
|
245
|
+
having(:left_outer, :posts => :comments) do
|
246
|
+
with(:offensive, true)
|
247
|
+
end
|
248
|
+
end.inspect
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'should create the correct condition' do
|
252
|
+
Blog.last_find[:conditions].should == [%q(blogs__posts__comments.offensive = ?), true]
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'should create the correct join' do
|
256
|
+
Blog.last_find[:joins].should == %q(LEFT OUTER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id LEFT OUTER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
describe 'on polymorphic associations' do
|
261
|
+
before do
|
262
|
+
PublicPost.filter do
|
263
|
+
having(:reviews) do
|
264
|
+
with(:stars_count, 3)
|
265
|
+
end
|
266
|
+
end.inspect
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'should create the correct condition' do
|
270
|
+
PublicPost.last_find[:conditions].should == [%q(posts__reviews.stars_count = ?), 3]
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'should create the correct join' do
|
274
|
+
PublicPost.last_find[:joins].should == %q(INNER JOIN "reviews" AS posts__reviews ON "posts".id = posts__reviews.reviewable_id AND (posts__reviews.reviewable_type = 'Post'))
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe 'on has_many_through associations' do
|
279
|
+
before do
|
280
|
+
Blog.filter do
|
281
|
+
having(:comments) do
|
282
|
+
with(:offensive, true)
|
283
|
+
end
|
284
|
+
end.inspect
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'should create the correct condition' do
|
288
|
+
Blog.last_find[:conditions].should == [%q(blogs__posts__comments.offensive = ?), true]
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'should create the correct join' do
|
292
|
+
Blog.last_find[:joins].should == %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
describe 'on has_one_through associations' do
|
297
|
+
before do
|
298
|
+
Post.filter do
|
299
|
+
having(:user) do
|
300
|
+
with(:first_name, 'Joe')
|
301
|
+
end
|
302
|
+
end.inspect
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'should create the correct condition' do
|
306
|
+
Post.last_find[:conditions].should == [%q(posts__author__user.first_name = ?), 'Joe']
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'should create the correct join' do
|
310
|
+
Post.last_find[:joins].should == %q(INNER JOIN "authors" AS posts__author ON "posts".id = posts__author.post_id INNER JOIN "users" AS posts__author__user ON posts__author.user_id = posts__author__user.id)
|
311
|
+
end
|
312
|
+
end
|
223
313
|
end
|
@@ -21,7 +21,7 @@ describe 'filter qualifiers' do
|
|
21
21
|
|
22
22
|
describe 'with multiple calls to limit' do
|
23
23
|
before do
|
24
|
-
|
24
|
+
Post.filter do
|
25
25
|
limit 5
|
26
26
|
with :published, true
|
27
27
|
limit 6
|
@@ -115,7 +115,7 @@ describe 'filter qualifiers' do
|
|
115
115
|
end
|
116
116
|
|
117
117
|
it 'should add the limit to the parameters' do
|
118
|
-
Post.last_find[:order].should == %q(
|
118
|
+
Post.last_find[:order].should == %q(posts__photo.path DESC, "posts".permalink ASC)
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
@@ -184,7 +184,7 @@ describe 'filter qualifiers' do
|
|
184
184
|
group_by(:created_at)
|
185
185
|
group_by(:photo => :format)
|
186
186
|
end.inspect
|
187
|
-
Post.last_find[:group].should == %q("posts".created_at,
|
187
|
+
Post.last_find[:group].should == %q("posts".created_at, posts__photo.format)
|
188
188
|
end
|
189
189
|
end
|
190
190
|
end
|
data/spec/models.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
class Ad < ActiveRecord::Base
|
2
|
+
extend TestModel
|
3
|
+
belongs_to :blog
|
4
|
+
end
|
5
|
+
|
6
|
+
|
7
|
+
class Author < ActiveRecord::Base
|
8
|
+
extend TestModel
|
9
|
+
belongs_to :user
|
10
|
+
belongs_to :post
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
class Blog < ActiveRecord::Base
|
15
|
+
extend TestModel
|
16
|
+
has_many :posts
|
17
|
+
has_many :comments, :through => :posts
|
18
|
+
has_many :ads
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
class Comment < ActiveRecord::Base
|
23
|
+
extend TestModel
|
24
|
+
belongs_to :post
|
25
|
+
belongs_to :user
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class Feature < ActiveRecord::Base
|
30
|
+
extend TestModel
|
31
|
+
belongs_to :featurable, :polymorphic => true
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
class Photo < ActiveRecord::Base
|
36
|
+
belongs_to :post
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
class Post < ActiveRecord::Base
|
41
|
+
extend TestModel
|
42
|
+
belongs_to :blog
|
43
|
+
has_many :comments
|
44
|
+
has_one :photo
|
45
|
+
has_and_belongs_to_many :tags
|
46
|
+
has_many :features, :as => :featurable
|
47
|
+
has_many :reviews, :as => :reviewable
|
48
|
+
has_one :author
|
49
|
+
has_one :user, :through => :author
|
50
|
+
end
|
51
|
+
|
52
|
+
class PublicPost < Post
|
53
|
+
end
|
54
|
+
|
55
|
+
class Review < ActiveRecord::Base
|
56
|
+
extend TestModel
|
57
|
+
belongs_to :reviewable, :polymorphic => true
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
class Tag < ActiveRecord::Base
|
62
|
+
has_and_belongs_to_many :posts
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
class User < ActiveRecord::Base
|
67
|
+
extend TestModel
|
68
|
+
has_one :author
|
69
|
+
has_many :comments
|
70
|
+
end
|
data/spec/named_filter_spec.rb
CHANGED
@@ -50,7 +50,8 @@ describe 'named filters' do
|
|
50
50
|
|
51
51
|
it 'should call the filter passing all of the arguments' do
|
52
52
|
Blog.with_name_and_post_with_permalink('booya', 'ftw').inspect
|
53
|
-
Blog.last_find[:conditions].should ==
|
53
|
+
Blog.last_find[:conditions].should ==
|
54
|
+
[%q(("blogs".name = ?) AND (blogs__posts.permalink = ?)), 'booya', 'ftw']
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
@@ -81,7 +82,8 @@ describe 'named filters' do
|
|
81
82
|
|
82
83
|
it 'should execute the parent class filters correctly' do
|
83
84
|
NiceComment.with_contents('test contents').inspect
|
84
|
-
NiceComment.last_find[:conditions].should ==
|
85
|
+
NiceComment.last_find[:conditions].should ==
|
86
|
+
[%q("comments".contents = ?), 'test contents']
|
85
87
|
end
|
86
88
|
|
87
89
|
it 'should not have the subclass filters in the parent class' do
|
@@ -90,7 +92,8 @@ describe 'named filters' do
|
|
90
92
|
|
91
93
|
it 'should have parent class filters in the subclass' do
|
92
94
|
NiceComment.offensive.with_contents('something').inspect
|
93
|
-
NiceComment.last_find[:conditions].should ==
|
95
|
+
NiceComment.last_find[:conditions].should ==
|
96
|
+
%q(("comments".contents = 'something') AND ("comments".offensive = 't'))
|
94
97
|
end
|
95
98
|
|
96
99
|
it 'should provide access to the named filters' do
|
@@ -126,25 +129,25 @@ describe 'named filters' do
|
|
126
129
|
|
127
130
|
it 'should chain the filters into a single query' do
|
128
131
|
Post.for_blog(1).with_offensive_comments.inspect
|
129
|
-
Post.last_find[:conditions].should ==
|
130
|
-
Post.last_find[:joins].should == %q(INNER JOIN "
|
132
|
+
Post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
|
133
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
|
131
134
|
end
|
132
135
|
|
133
136
|
it 'should remove duplicate joins' do
|
134
137
|
Post.for_blog(1).with_offensive_comments.with_interesting_comments.inspect
|
135
|
-
Post.last_find[:joins].should == %q(INNER JOIN "
|
138
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
|
136
139
|
end
|
137
140
|
|
138
141
|
it 'should allow for filtering a named_filter' do
|
139
142
|
Post.for_blog(1).filter { having(:comments).with :offensive, true }.inspect
|
140
|
-
Post.last_find[:conditions].should ==
|
141
|
-
Post.last_find[:joins].should == %q(INNER JOIN "
|
143
|
+
Post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
|
144
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
|
142
145
|
end
|
143
146
|
|
144
147
|
it 'should allow for applying a named filter to a filter' do
|
145
148
|
Post.filter { having(:comments).with :offensive, false }.for_blog(1).inspect
|
146
|
-
Post.last_find[:conditions].should ==
|
147
|
-
Post.last_find[:joins].should == %q(INNER JOIN "
|
149
|
+
Post.last_find[:conditions].should == %q((posts__blog.id = 1) AND (posts__comments.offensive = 'f'))
|
150
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id), %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)]
|
148
151
|
end
|
149
152
|
|
150
153
|
it 'should not change the inner filter conditions when chaining filters' do
|
@@ -163,11 +166,74 @@ describe 'named filters' do
|
|
163
166
|
|
164
167
|
it 'should not change an original filter when reusing it' do
|
165
168
|
base = Post.for_blog(1)
|
166
|
-
level1 = base.with_offensive_comments
|
169
|
+
level1 = base.with_offensive_comments.inspect
|
167
170
|
level2 = base.with_interesting_comments
|
168
|
-
|
169
|
-
Post.last_find[:
|
170
|
-
|
171
|
+
Post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
|
172
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe 'chaining named filters with regular AR associations' do
|
177
|
+
before do
|
178
|
+
Post.named_filter(:published) do
|
179
|
+
with(:published, true)
|
180
|
+
end
|
181
|
+
@blog = Blog.create
|
182
|
+
@blog.posts.published.inspect
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should combine the conditions from the association with the named filter' do
|
186
|
+
Post.last_find[:conditions].should == "(\"posts\".published = 't') AND (\"posts\".blog_id = #{@blog.id})"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe 'chaining named filters with AR associations that involve joins' do
|
191
|
+
before do
|
192
|
+
Comment.named_filter(:with_user_named) do |name|
|
193
|
+
having(:user).with(:first_name, name)
|
194
|
+
end
|
195
|
+
@blog = Blog.create
|
196
|
+
@blog.comments.with_user_named('Bob').inspect
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should combine the joins from the association with the named filter' do
|
200
|
+
Comment.last_find[:joins].should == [%q(INNER JOIN "users" AS comments__user ON "comments".user_id = comments__user.id), %q(INNER JOIN "posts" ON "comments".post_id = "posts".id)]
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'should combine the conditions from the association with the named filter' do
|
204
|
+
Comment.last_find[:conditions].should == "(comments__user.first_name = 'Bob') AND ((\"posts\".blog_id = #{@blog.id}))"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe 'chaining multiple named filters with an AR association' do
|
209
|
+
before do
|
210
|
+
Comment.named_filter(:offensive) { with(:offensive, true) }
|
211
|
+
Comment.named_filter(:with_fun_in_contents) { with(:contents).like('%fun%') }
|
212
|
+
@post = Post.create
|
213
|
+
@post.comments.offensive.with_fun_in_contents.inspect
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'should combine the conditions correctly' do
|
217
|
+
Comment.last_find[:conditions].should == "(\"comments\".contents LIKE '%fun%') AND ((\"comments\".offensive = 't') AND (\"comments\".post_id = #{@post.id}))"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe 'chaining multiple named filters with different joins' do
|
222
|
+
before do
|
223
|
+
Blog.named_filter(:with_offensive_comments) { having(:comments).with(:offensive, true) }
|
224
|
+
Blog.named_filter(:with_ads_with_content) { |content| having(:ads).with(:content, content) }
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'compile the joins correctly' do
|
228
|
+
Blog.with_offensive_comments.with_ads_with_content('ack').inspect
|
229
|
+
Blog.last_find[:joins].should == [%q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id), %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe 'with named filters that only include orders' do
|
234
|
+
it 'should have an empty conditions hash' do
|
235
|
+
Blog.named_filter(:ordered_by_id) { order(:id, :desc) }
|
236
|
+
Blog.ordered_by_id.proxy_options.should == { :order => %q("blogs".id DESC) }
|
171
237
|
end
|
172
238
|
end
|
173
239
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe 'proxying to the found data' do
|
4
|
+
before do
|
5
|
+
TestModel.extended_models.each { |model| model.last_find = {} }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'calling first and last on a filter result' do
|
9
|
+
before do
|
10
|
+
Blog.all.each { |b| b.destroy }
|
11
|
+
Blog.named_filter(:by_name) do
|
12
|
+
order(:name)
|
13
|
+
end
|
14
|
+
@blog1 = Blog.create(:name => 'a')
|
15
|
+
@blog2 = Blog.create(:name => 'b')
|
16
|
+
@blog3 = Blog.create(:name => 'c')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should return the first element in the result correctly' do
|
20
|
+
Blog.by_name.first.should == @blog1
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should return the last element in the result correctly' do
|
24
|
+
Blog.by_name.last.should == @blog3
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should proxy arguments through' do
|
28
|
+
Blog.by_name.first(:conditions => ['name = ?', 'b']).should == @blog2
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'calling reject! on a filter result' do
|
33
|
+
before do
|
34
|
+
Blog.all.each { |blog| blog.destroy }
|
35
|
+
Blog.named_filter(:by_name) do
|
36
|
+
order(:name)
|
37
|
+
end
|
38
|
+
@blog1 = Blog.create
|
39
|
+
@blog2 = Blog.create
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should remove the rejected items from the list' do
|
43
|
+
items = Blog.by_name
|
44
|
+
items.size.should == 2
|
45
|
+
items.reject! { |i| true } # should reject them all
|
46
|
+
items.size.should == 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/spec/restrictions_spec.rb
CHANGED
@@ -72,7 +72,7 @@ describe 'RecordFilter restrictions' do
|
|
72
72
|
with(:permalink, 'eek')
|
73
73
|
end
|
74
74
|
end.inspect
|
75
|
-
Post.last_find.should == { :conditions => [%q{
|
75
|
+
Post.last_find.should == { :conditions => [%q{NOT (("posts".blog_id = ?) OR ("posts".permalink = ?))}, 1, 'eek'] }
|
76
76
|
end
|
77
77
|
|
78
78
|
it 'should filter by not_all_of' do
|
@@ -82,7 +82,7 @@ describe 'RecordFilter restrictions' do
|
|
82
82
|
with(:permalink, 'eek')
|
83
83
|
end
|
84
84
|
end.inspect
|
85
|
-
Post.last_find.should == { :conditions => [%q{
|
85
|
+
Post.last_find.should == { :conditions => [%q{NOT (("posts".blog_id = ?) AND ("posts".permalink = ?))}, 1, 'eek'] }
|
86
86
|
end
|
87
87
|
|
88
88
|
it 'should filter by disjunction' do
|
@@ -137,6 +137,6 @@ describe 'RecordFilter restrictions' do
|
|
137
137
|
Post.filter do
|
138
138
|
with(:permalink).not.equal_to(filter_class.name)
|
139
139
|
end.inspect
|
140
|
-
Post.last_find.should == { :conditions => [%q("posts".permalink
|
140
|
+
Post.last_find.should == { :conditions => [%q("posts".permalink <> ?), 'Post'] }
|
141
141
|
end
|
142
142
|
end
|
data/spec/select_spec.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe 'with custom selects for cases where DISTINCT is required' do
|
4
|
+
before do
|
5
|
+
TestModel.extended_models.each { |model| model.last_find = {} }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'on a standard filter' do
|
9
|
+
it 'should put nothing in the select' do
|
10
|
+
Post.filter do
|
11
|
+
having(:comments).with(:offensive, true)
|
12
|
+
end.inspect
|
13
|
+
Post.last_find[:select].should be_nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'with join types that require distinct' do
|
18
|
+
it 'should put the distinct clause in the select' do
|
19
|
+
[:left, :outer, :left_outer].each do |join_type|
|
20
|
+
Post.filter do
|
21
|
+
having(join_type, :comments).with(:offensive, true)
|
22
|
+
end.inspect
|
23
|
+
Post.last_find[:select].should == %q(DISTINCT "posts".*)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'with join types that do not require distinct' do
|
29
|
+
it 'should not put the distinct clause in the select' do
|
30
|
+
[:inner].each do |join_type|
|
31
|
+
Post.filter do
|
32
|
+
having(join_type, :comments).with(:offensive, true)
|
33
|
+
end.inspect
|
34
|
+
Post.last_find[:select].should be_nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'on a filter with nested joins that require distinct' do
|
40
|
+
it 'should put the distinct clause in the select' do
|
41
|
+
Blog.filter do
|
42
|
+
having(:posts) do
|
43
|
+
having(:left_outer, :comments).with(:offensive, true)
|
44
|
+
end
|
45
|
+
end.inspect
|
46
|
+
Blog.last_find[:select].should == %q(DISTINCT "blogs".*)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'on a filter that requires distinct with a count call' do
|
51
|
+
it 'should put the distinct clause in the select' do
|
52
|
+
Post.filter do
|
53
|
+
having(:left_outer, :comments).with(:offensive, true)
|
54
|
+
end.count
|
55
|
+
Post.last_find[:select].should == %q(DISTINCT "posts".id)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -13,8 +13,14 @@ module TestModel
|
|
13
13
|
|
14
14
|
attr_accessor :last_find
|
15
15
|
|
16
|
-
def
|
17
|
-
@last_find =
|
16
|
+
def find(*args)
|
17
|
+
@last_find = current_scoped_methods[:find] if current_scoped_methods
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def count(*args)
|
22
|
+
@last_find = current_scoped_methods[:find] if current_scoped_methods
|
23
|
+
super
|
18
24
|
end
|
19
25
|
|
20
26
|
def self.extended(base)
|
@@ -22,7 +28,7 @@ module TestModel
|
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
25
|
-
|
31
|
+
require File.join(File.dirname(__FILE__), 'models')
|
26
32
|
|
27
33
|
ActiveRecord::Base.establish_connection(
|
28
34
|
:adapter => 'sqlite3',
|
data/spec/test.db
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aub-record_filter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mat Brown
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2009-04-
|
13
|
+
date: 2009-04-27 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -52,15 +52,11 @@ files:
|
|
52
52
|
- spec/explicit_join_spec.rb
|
53
53
|
- spec/implicit_join_spec.rb
|
54
54
|
- spec/limits_and_ordering_spec.rb
|
55
|
-
- spec/models
|
56
|
-
- spec/models/comment.rb
|
57
|
-
- spec/models/feature.rb
|
58
|
-
- spec/models/photo.rb
|
59
|
-
- spec/models/post.rb
|
60
|
-
- spec/models/review.rb
|
61
|
-
- spec/models/tag.rb
|
55
|
+
- spec/models.rb
|
62
56
|
- spec/named_filter_spec.rb
|
57
|
+
- spec/proxying_spec.rb
|
63
58
|
- spec/restrictions_spec.rb
|
59
|
+
- spec/select_spec.rb
|
64
60
|
- spec/spec_helper.rb
|
65
61
|
- spec/test.db
|
66
62
|
has_rdoc: true
|
@@ -94,13 +90,9 @@ test_files:
|
|
94
90
|
- spec/explicit_join_spec.rb
|
95
91
|
- spec/implicit_join_spec.rb
|
96
92
|
- spec/limits_and_ordering_spec.rb
|
97
|
-
- spec/models
|
98
|
-
- spec/models/comment.rb
|
99
|
-
- spec/models/feature.rb
|
100
|
-
- spec/models/photo.rb
|
101
|
-
- spec/models/post.rb
|
102
|
-
- spec/models/review.rb
|
103
|
-
- spec/models/tag.rb
|
93
|
+
- spec/models.rb
|
104
94
|
- spec/named_filter_spec.rb
|
95
|
+
- spec/proxying_spec.rb
|
105
96
|
- spec/restrictions_spec.rb
|
97
|
+
- spec/select_spec.rb
|
106
98
|
- spec/spec_helper.rb
|
data/spec/models/blog.rb
DELETED
data/spec/models/comment.rb
DELETED
data/spec/models/feature.rb
DELETED
data/spec/models/photo.rb
DELETED
data/spec/models/post.rb
DELETED
data/spec/models/review.rb
DELETED
data/spec/models/tag.rb
DELETED