better_ar 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.txt +4 -0
- data/README.markdown +8 -0
- data/better_ar.gemspec +0 -2
- data/lib/better_ar.rb +4 -106
- data/lib/better_ar/association_collection.rb +49 -0
- data/lib/better_ar/calculations.rb +28 -0
- data/lib/better_ar/finder_methods.rb +84 -0
- data/lib/better_ar/version.rb +1 -1
- data/test/helper.rb +7 -1
- data/test/test_finder_methods.rb +71 -1
- metadata +5 -2
data/HISTORY.txt
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## 0.0.7
|
2
|
+
Added support for association collections
|
3
|
+
Moved ActiveRecord::Relation included methods into proper modules
|
4
|
+
|
1
5
|
## 0.0.6
|
2
6
|
Moved methods as higher level module in ActiveRecord::Relation than ActiveRecord::FinderMethods. So the BetterAR modules take precedence.
|
3
7
|
|
data/README.markdown
CHANGED
@@ -23,6 +23,14 @@ is the same as
|
|
23
23
|
|
24
24
|
User.joins(:records).where(:age => 10, :records => {:name => 'test'}).limit(5).offset(10).order(:name)
|
25
25
|
|
26
|
+
It also works on association collections:
|
27
|
+
|
28
|
+
User.first.records.all(:level => 2, :limit! => 5, :offset! => 10, :order! => :name, :reports => {:name => 'test'})
|
29
|
+
|
30
|
+
This would be the same as:
|
31
|
+
|
32
|
+
User.first.records.joins(:reports).where(:level => 2, :reports => {:name => 'test'}).limit(5).offset(10).order(:name)
|
33
|
+
|
26
34
|
While this may seem less concise the advantage is being able to dynamically construct the query with a single hash in code.
|
27
35
|
|
28
36
|
### Legacy
|
data/better_ar.gemspec
CHANGED
data/lib/better_ar.rb
CHANGED
@@ -1,107 +1,5 @@
|
|
1
|
-
module BetterAr
|
1
|
+
module BetterAr; end
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# User.all(:age => 10, :limit! => 2, :offset! => 3, :order! => :name)
|
7
|
-
#
|
8
|
-
# is the same as:
|
9
|
-
# User.where(:age => 10).limit(2).offset(3).order(:name)
|
10
|
-
#
|
11
|
-
# if the key :conditions is present it will fall back to legacy
|
12
|
-
#
|
13
|
-
# any key with the '!' at the end will be assumed to be a sql operator. The key should match either an {ActiveRecord::Relation} instance method or an ARel predicate.
|
14
|
-
#
|
15
|
-
# Implicit joins are supported.
|
16
|
-
# example:
|
17
|
-
# User.all(:records => {:name => 'test'})
|
18
|
-
#
|
19
|
-
# is the same as:
|
20
|
-
# User.joins(:records).where(:records => {:name => 'test'})
|
21
|
-
#
|
22
|
-
# @param [Hash]
|
23
|
-
# Optional
|
24
|
-
# @return [ActiveRecord::Relation]
|
25
|
-
def all(opts = {})
|
26
|
-
if opts.empty?
|
27
|
-
super()
|
28
|
-
elsif opts.key?(:conditions)
|
29
|
-
super(opts)
|
30
|
-
else
|
31
|
-
relation = clone
|
32
|
-
|
33
|
-
unless opts.key?(:conditions)
|
34
|
-
unless opts.empty?
|
35
|
-
opts.keys.select { |key| key.to_s =~ /!$/ }.each do |predicate|
|
36
|
-
if value = opts.delete(predicate)
|
37
|
-
relation = relation.send(predicate.to_s.sub('!',''), value)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
reflect_on_all_associations.map(&:name).each do |name|
|
42
|
-
if opts.key?(name)
|
43
|
-
relation = relation.joins(name)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
relation.where(opts)
|
48
|
-
end
|
49
|
-
|
50
|
-
if opts.empty?
|
51
|
-
relation
|
52
|
-
else
|
53
|
-
relation.where(opts)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# Forces a limit of 1 on the collection
|
60
|
-
#
|
61
|
-
# example:
|
62
|
-
# User.first(:age => 10, :name => 'Brian')
|
63
|
-
#
|
64
|
-
# is the same as:
|
65
|
-
# User.where(:age => 10, :name => 'Brian').limit(1).first
|
66
|
-
#
|
67
|
-
# if the key :conditions is present it will fall back to legacy
|
68
|
-
#
|
69
|
-
# @param [Hash]
|
70
|
-
# Optional follows same convention as {#all}
|
71
|
-
# @return [ActiveRecord::Base]
|
72
|
-
def first(opts = {})
|
73
|
-
if opts.empty?
|
74
|
-
super()
|
75
|
-
elsif opts.key?(:conditions)
|
76
|
-
super(opts)
|
77
|
-
else
|
78
|
-
all(opts.merge(:limit! => 1)).first
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Does a count on the query
|
83
|
-
#
|
84
|
-
# example:
|
85
|
-
# User.count(:age => 20)
|
86
|
-
#
|
87
|
-
# is the same as:
|
88
|
-
# User.where(:age => 20).count
|
89
|
-
#
|
90
|
-
# if the key :conditions is present it will fall back to legacy
|
91
|
-
#
|
92
|
-
# @param [Hash]
|
93
|
-
# Optional follows same convention as {#all}
|
94
|
-
# @return [Integer]
|
95
|
-
def count(opts = {})
|
96
|
-
if opts.empty?
|
97
|
-
super()
|
98
|
-
elsif opts.key?(:conditions)
|
99
|
-
super(opts)
|
100
|
-
else
|
101
|
-
all(opts).count
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
ActiveRecord::Relation.send(:include, BetterAr)
|
3
|
+
require 'better_ar/finder_methods'
|
4
|
+
require 'better_ar/calculations'
|
5
|
+
require 'better_ar/association_collection'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module BetterAr
|
2
|
+
module AssociationCollection
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
alias_method_chain :first, :better_ar
|
7
|
+
alias_method_chain :count, :better_ar
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Allows for the same interface as {BetterAr::Relation#first} on association collections
|
12
|
+
#
|
13
|
+
# example:
|
14
|
+
# User.first.records.first(:level => 2, :order! => :name)
|
15
|
+
#
|
16
|
+
# @param [Hash]
|
17
|
+
# Optional
|
18
|
+
# @returns [ActiveRecord::Base]
|
19
|
+
def first_with_better_ar(opts = {})
|
20
|
+
if opts.empty?
|
21
|
+
first_without_better_ar
|
22
|
+
elsif opts.key?(:conditions)
|
23
|
+
first_without_better_ar(opts)
|
24
|
+
else
|
25
|
+
scoped.first(opts)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Allows for the same interface as {BetterAr::Relation#count} on association collections
|
30
|
+
#
|
31
|
+
# example:
|
32
|
+
# User.first.records.count(:level => 2)
|
33
|
+
#
|
34
|
+
# @param [Hash]
|
35
|
+
# Optional
|
36
|
+
# @returns [Integer]
|
37
|
+
def count_with_better_ar(opts = {})
|
38
|
+
if opts.empty?
|
39
|
+
count_without_better_ar
|
40
|
+
elsif opts.key?(:conditions)
|
41
|
+
count_without_better_ar(opts)
|
42
|
+
else
|
43
|
+
scoped.count(opts)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
ActiveRecord::Associations::AssociationCollection.send(:include, BetterAr::AssociationCollection)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BetterAr
|
2
|
+
module Calculations
|
3
|
+
# Does a count on the query
|
4
|
+
#
|
5
|
+
# example:
|
6
|
+
# User.count(:age => 20)
|
7
|
+
#
|
8
|
+
# is the same as:
|
9
|
+
# User.where(:age => 20).count
|
10
|
+
#
|
11
|
+
# if the key :conditions is present it will fall back to legacy
|
12
|
+
#
|
13
|
+
# @param [Hash]
|
14
|
+
# Optional follows same convention as {#all}
|
15
|
+
# @return [Integer]
|
16
|
+
def count(opts = {}, extra = {})
|
17
|
+
if opts.nil? || opts.empty?
|
18
|
+
super(nil, extra)
|
19
|
+
elsif opts.key?(:conditions)
|
20
|
+
super(opts, extra)
|
21
|
+
else
|
22
|
+
all(opts).count
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
ActiveRecord::Relation.send(:include, BetterAr::Calculations)
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module BetterAr
|
2
|
+
module FinderMethods
|
3
|
+
# Breaks down the hash to do a {ActiveRecord::Relation} query
|
4
|
+
#
|
5
|
+
# example:
|
6
|
+
# User.all(:age => 10, :limit! => 2, :offset! => 3, :order! => :name)
|
7
|
+
#
|
8
|
+
# is the same as:
|
9
|
+
# User.where(:age => 10).limit(2).offset(3).order(:name)
|
10
|
+
#
|
11
|
+
# if the key :conditions is present it will fall back to legacy
|
12
|
+
#
|
13
|
+
# any key with the '!' at the end will be assumed to be a sql operator. The key should match either an {ActiveRecord::Relation} instance method or an ARel predicate.
|
14
|
+
#
|
15
|
+
# Implicit joins are supported.
|
16
|
+
# example:
|
17
|
+
# User.all(:records => {:name => 'test'})
|
18
|
+
#
|
19
|
+
# is the same as:
|
20
|
+
# User.joins(:records).where(:records => {:name => 'test'})
|
21
|
+
#
|
22
|
+
# @param [Hash]
|
23
|
+
# Optional
|
24
|
+
# @return [ActiveRecord::Relation]
|
25
|
+
def all(opts = {})
|
26
|
+
if opts.empty?
|
27
|
+
super()
|
28
|
+
elsif opts.key?(:conditions)
|
29
|
+
super(opts)
|
30
|
+
else
|
31
|
+
relation = clone
|
32
|
+
|
33
|
+
unless opts.key?(:conditions)
|
34
|
+
unless opts.empty?
|
35
|
+
opts.keys.select { |key| key.to_s =~ /!$/ }.each do |predicate|
|
36
|
+
if value = opts.delete(predicate)
|
37
|
+
relation = relation.send(predicate.to_s.sub('!',''), value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
reflect_on_all_associations.map(&:name).each do |name|
|
42
|
+
if opts.key?(name)
|
43
|
+
relation = relation.joins(name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
relation.where(opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
if opts.empty?
|
51
|
+
relation
|
52
|
+
else
|
53
|
+
relation.where(opts)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Forces a limit of 1 on the collection
|
60
|
+
#
|
61
|
+
# example:
|
62
|
+
# User.first(:age => 10, :name => 'Brian')
|
63
|
+
#
|
64
|
+
# is the same as:
|
65
|
+
# User.where(:age => 10, :name => 'Brian').limit(1).first
|
66
|
+
#
|
67
|
+
# if the key :conditions is present it will fall back to legacy
|
68
|
+
#
|
69
|
+
# @param [Hash]
|
70
|
+
# Optional follows same convention as {#all}
|
71
|
+
# @return [ActiveRecord::Base]
|
72
|
+
def first(opts = {})
|
73
|
+
if opts.empty?
|
74
|
+
super()
|
75
|
+
elsif opts.key?(:conditions)
|
76
|
+
super(opts)
|
77
|
+
else
|
78
|
+
all(opts.merge(:limit! => 1)).first
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
ActiveRecord::Relation.send(:include, BetterAr::FinderMethods)
|
data/lib/better_ar/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'ruby-debug'
|
3
3
|
require 'minitest/autorun'
|
4
|
-
# require 'mocha'
|
5
4
|
require 'active_support/core_ext'
|
6
5
|
require 'active_record'
|
7
6
|
require 'better_ar'
|
@@ -19,8 +18,10 @@ ActiveRecord::Base.establish_connection(
|
|
19
18
|
|
20
19
|
users_table = %{CREATE TABLE users (id INTEGER PRIMARY KEY, age INTEGER, name TEXT);}
|
21
20
|
records_table = %{CREATE TABLE records (id INTEGER PRIMARY KEY, user_id INTEGER, name TEXT);}
|
21
|
+
reports_table = %{CREATE TABLE reports (id INTEGER PRIMARY KEY, record_id INTEGER, name TEXT);}
|
22
22
|
ActiveRecord::Base.connection.execute(users_table)
|
23
23
|
ActiveRecord::Base.connection.execute(records_table)
|
24
|
+
ActiveRecord::Base.connection.execute(reports_table)
|
24
25
|
|
25
26
|
class User < ActiveRecord::Base
|
26
27
|
has_many :records
|
@@ -28,5 +29,10 @@ end
|
|
28
29
|
|
29
30
|
class Record < ActiveRecord::Base
|
30
31
|
belongs_to :user
|
32
|
+
has_many :reports
|
33
|
+
end
|
34
|
+
|
35
|
+
class Report < ActiveRecord::Base
|
36
|
+
belongs_to :record
|
31
37
|
end
|
32
38
|
|
data/test/test_finder_methods.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
|
-
describe '
|
3
|
+
describe 'ActiveRecord::Relation Finder Methods' do
|
4
4
|
after do
|
5
5
|
User.destroy_all
|
6
6
|
end
|
@@ -72,3 +72,73 @@ describe 'Enhanced Finder Methods' do
|
|
72
72
|
|
73
73
|
end
|
74
74
|
|
75
|
+
describe 'ActiveRecord::Associations::AssociationCollection Finder Methods' do
|
76
|
+
before do
|
77
|
+
@user = User.create
|
78
|
+
@record_1 = @user.records.create(:name => 'one')
|
79
|
+
@record_2 = @user.records.create(:name => 'two')
|
80
|
+
end
|
81
|
+
|
82
|
+
after do
|
83
|
+
User.destroy_all
|
84
|
+
Record.destroy_all
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '.all' do
|
88
|
+
it 'extracts the non-where scopes and applies' do
|
89
|
+
test_sql = @user.records.all(:limit! => 1, :offset! => 2, :order! => :name, :name => 'one').to_sql
|
90
|
+
expected_sql = @user.records.where(:name => 'one').limit(1).offset(2).order(:name).to_sql
|
91
|
+
test_sql.must_be_like expected_sql
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'finds implicit joins by reflection' do
|
95
|
+
test_sql = @user.records.all(:reports => { :name => 'test' }).to_sql
|
96
|
+
expected_sql = @user.records.joins(:reports).where(:reports => { :name => 'test' }).to_sql
|
97
|
+
test_sql.must_be_like expected_sql
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '.first' do
|
102
|
+
it 'calls #first on .all' do
|
103
|
+
@user.records.first(:name => 'two').must_equal @record_2
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '.count' do
|
108
|
+
it 'calls #count on .all' do
|
109
|
+
@user.records.count(:name => 'one').must_equal 1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'hash contains :conditions' do
|
114
|
+
describe 'with conditions' do
|
115
|
+
it 'falls back for .all' do
|
116
|
+
@user.records.all(:conditions => "name = 'one'").must_equal [@record_1]
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'falls back for .first' do
|
120
|
+
@user.records.create(:name => 'one')
|
121
|
+
@user.records.first(:conditions => "name = 'one'").must_equal @record_1
|
122
|
+
end
|
123
|
+
|
124
|
+
# Note a valid test... need to rewrite.
|
125
|
+
# it 'falls back for .count' do
|
126
|
+
# @user.records.count(:conditions => "name = 'one'").must_equal 1
|
127
|
+
# end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'without conditions' do
|
131
|
+
it 'falls back for .all' do
|
132
|
+
@user.records.all.must_equal [@record_1, @record_2]
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'falls back for .first' do
|
136
|
+
@user.records.first.must_equal @record_1
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'falls back for .count' do
|
140
|
+
@user.records.count.must_equal 2
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 7
|
9
|
+
version: 0.0.7
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Brian Cardarella
|
@@ -51,6 +51,9 @@ files:
|
|
51
51
|
- Rakefile
|
52
52
|
- better_ar.gemspec
|
53
53
|
- lib/better_ar.rb
|
54
|
+
- lib/better_ar/association_collection.rb
|
55
|
+
- lib/better_ar/calculations.rb
|
56
|
+
- lib/better_ar/finder_methods.rb
|
54
57
|
- lib/better_ar/version.rb
|
55
58
|
- test/helper.rb
|
56
59
|
- test/test_finder_methods.rb
|