dase 3.2.2 → 3.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +21 -13
- data/lib/dase/mp_active_record_base.rb +10 -1
- data/lib/dase/mp_active_record_relation.rb +7 -10
- data/lib/dase/preloader.rb +14 -4
- data/lib/dase/preloader_methods.rb +21 -4
- data/lib/dase/version.rb +1 -1
- data/test/fixtures/author.rb +1 -0
- data/test/fixtures/book.rb +2 -0
- data/test/fixtures/like.rb +4 -0
- data/test/fixtures/likes.yml +10 -0
- data/test/fixtures/quote.rb +2 -1
- data/test/fixtures/schema.rb +6 -0
- data/test/test_dase.rb +35 -6
- metadata +16 -12
data/README.md
CHANGED
@@ -37,28 +37,36 @@ Since it's a sort of a "hack", make sure you specified the version number for "d
|
|
37
37
|
end
|
38
38
|
```
|
39
39
|
|
40
|
-
###
|
41
|
-
|
42
|
-
You can specify a hash of options which will be passed to the underlying finder
|
40
|
+
### Using :conditions hash
|
41
|
+
Specify a hash of options which will be passed to the underlying finder
|
43
42
|
which retrieves the association. Valid keys are: :conditions, :group, :having, :joins, :include
|
44
43
|
```
|
45
|
-
Author.includes_count_of(:articles, :conditions => {:year => 2012})
|
44
|
+
Author.includes_count_of(:articles, :conditions => {:year => 2012}) # counts only articles in year 2012
|
46
45
|
```
|
47
46
|
|
47
|
+
### Using scope merging
|
48
|
+
```
|
49
|
+
scope = Article.where(:year => 2012)
|
50
|
+
Author.includes_count_of(:articles, :only => scope) # counts only articles in year 2012
|
51
|
+
```
|
48
52
|
|
49
|
-
###
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
+
### Using block syntax
|
54
|
+
```
|
55
|
+
Author.includes_count_of(:articles){ where(:year => 2012) } # in the block, 'self' is a Relation instance
|
56
|
+
Author.includes_count_of(:articles){ |scope| scope.where(:year => 2012) } # 'self' is the same inside and outside the block
|
57
|
+
```
|
53
58
|
|
59
|
+
### Renaming counter column
|
54
60
|
```
|
55
|
-
|
56
|
-
|
57
|
-
includes_count_of(:articles) # this will not work!!!
|
58
|
-
}
|
59
|
-
end
|
61
|
+
sites = WebSite.includes_count_of(:users, :conditions => {:role => 'admin'}, :as => :admins_count)
|
62
|
+
sites.each { |site| puts "Site #{site.url} has #{site.admins_count} admin users" }
|
60
63
|
```
|
61
64
|
|
65
|
+
|
66
|
+
### Known problems
|
67
|
+
|
68
|
+
Dase doesn't support polymorphism.
|
69
|
+
|
62
70
|
## How it works
|
63
71
|
|
64
72
|
Here's a pseudo-code that gives an idea on how it works internally
|
@@ -4,7 +4,7 @@ module Dase
|
|
4
4
|
module ARBaseInstanceMethods
|
5
5
|
def set_dase_counter(name, value)
|
6
6
|
@dase_counters ||= {}
|
7
|
-
@dase_counters[name] = value
|
7
|
+
@dase_counters[name.to_sym] = value
|
8
8
|
end
|
9
9
|
|
10
10
|
def get_dase_counter(name)
|
@@ -23,6 +23,15 @@ module Dase
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
# This method uses traditions counting method
|
27
|
+
# and stores the results in dase-compatible form
|
28
|
+
def refresh_dase_counters!(*associations)
|
29
|
+
associations.flatten.each do |assoc|
|
30
|
+
value = self.send(assoc).count # i.e. book.quotes.count
|
31
|
+
set_dase_counter "#{column}_count", value # i.e. book.quotes_count = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
26
35
|
private
|
27
36
|
|
28
37
|
def self.included(klass)
|
@@ -5,29 +5,26 @@ module Dase
|
|
5
5
|
def includes_count_of(*args)
|
6
6
|
args.reject! { |a| a.blank? }
|
7
7
|
options = args.extract_options!
|
8
|
-
options.assert_valid_keys(:as, :conditions, :group, :having, :limit, :offset, :joins, :include, :from, :lock)
|
8
|
+
options.assert_valid_keys(:proc, :as, :only, :conditions, :group, :having, :limit, :offset, :joins, :include, :from, :lock)
|
9
9
|
return self if args.empty?
|
10
10
|
if options.present? and args.many?
|
11
11
|
raise ArgumentError, "includes_count_of takes either multiple associations OR single association + options"
|
12
12
|
end
|
13
13
|
relation = clone
|
14
14
|
relation.dase_values ||= {}
|
15
|
+
#options[:proc] = block if block
|
15
16
|
args.each do |arg|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
else
|
20
|
-
relation.dase_values[arg] = options
|
21
|
-
end
|
17
|
+
options[:association] = arg.to_sym
|
18
|
+
options[:as] = (options[:as] || "#{arg}_count").to_sym
|
19
|
+
relation.dase_values[options[:as]] = options
|
22
20
|
end
|
23
21
|
relation
|
24
22
|
end
|
25
23
|
|
26
24
|
def attach_dase_counters_to_records
|
27
25
|
if dase_values.present? and !@has_dase_counters
|
28
|
-
dase_values.each do |
|
29
|
-
|
30
|
-
Dase::Preloader.new(@records, association, options).run
|
26
|
+
dase_values.each do |name, options|
|
27
|
+
Dase::Preloader.new(@records, options[:association], options).run
|
31
28
|
end
|
32
29
|
@has_dase_counters = true
|
33
30
|
end
|
data/lib/dase/preloader.rb
CHANGED
@@ -10,9 +10,18 @@ module Dase
|
|
10
10
|
end
|
11
11
|
|
12
12
|
# Not implemented yet
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
class HasManyThrough < ::ActiveRecord::Associations::Preloader::HasManyThrough
|
14
|
+
include Dase::PreloaderMethods
|
15
|
+
|
16
|
+
def prefixed_foreign_key
|
17
|
+
"#{reflection.active_record.table_name}.#{reflection.active_record_primary_key}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def records_for(ids)
|
21
|
+
reflection.active_record.joins(reflection.name).
|
22
|
+
where(prefixed_foreign_key => ids)
|
23
|
+
end
|
24
|
+
end
|
16
25
|
|
17
26
|
# an overloaded version of ActiveRecord::Associations::Preloader's preloader_for
|
18
27
|
# which returns a class of a custom preloader for a given association
|
@@ -20,7 +29,8 @@ module Dase
|
|
20
29
|
case reflection.macro
|
21
30
|
when :has_many
|
22
31
|
if reflection.options[:through]
|
23
|
-
|
32
|
+
HasManyThrough
|
33
|
+
#raise NotImplementedError, "The support for HasManyThrough associations is not implemented yet"
|
24
34
|
else
|
25
35
|
HasMany
|
26
36
|
end
|
@@ -4,19 +4,36 @@ module Dase
|
|
4
4
|
def initialize(klass, owners, reflection, preload_options)
|
5
5
|
# grabbing our options
|
6
6
|
preload_options = preload_options.clone
|
7
|
+
@dase_association = preload_options.delete(:association)
|
7
8
|
@dase_counter_name = preload_options.delete(:as)
|
9
|
+
@dase_scope_to_merge = preload_options.delete(:only)
|
10
|
+
#@dase_proc = preload_options.delete(:proc)
|
8
11
|
super(klass, owners, reflection, preload_options)
|
9
12
|
end
|
10
13
|
|
14
|
+
def prefixed_foreign_key
|
15
|
+
"#{scoped.quoted_table_name}.#{reflection.foreign_key}"
|
16
|
+
end
|
17
|
+
|
11
18
|
def preload
|
12
|
-
counter_name = @dase_counter_name || "#{reflection.name}_count".to_sym
|
13
19
|
pk = model.primary_key.to_sym
|
14
20
|
ids = owners.map(&pk)
|
15
|
-
|
16
|
-
|
21
|
+
scope = records_for(ids)
|
22
|
+
scope = scope.merge(@dase_scope_to_merge) if @dase_scope_to_merge
|
23
|
+
#if @dase_proc # support for includes_count_of(...){ where(...) } syntax
|
24
|
+
# case @dase_proc.arity
|
25
|
+
# when 0
|
26
|
+
# scope = scope.instance_eval &@dase_proc
|
27
|
+
# when 1
|
28
|
+
# scope = @dase_proc.call(scope)
|
29
|
+
# else
|
30
|
+
# raise ArgumentError, "The block passed to includes_count_of takes 0 or 1 arguments"
|
31
|
+
# end
|
32
|
+
#end
|
33
|
+
counters_hash = scope.count(:group => prefixed_foreign_key)
|
17
34
|
owners.each do |owner|
|
18
35
|
value = counters_hash[owner[pk]] || 0 # 0 is "default count", when no records found
|
19
|
-
owner.set_dase_counter(
|
36
|
+
owner.set_dase_counter(@dase_counter_name, value)
|
20
37
|
end
|
21
38
|
end
|
22
39
|
|
data/lib/dase/version.rb
CHANGED
data/test/fixtures/author.rb
CHANGED
data/test/fixtures/book.rb
CHANGED
data/test/fixtures/quote.rb
CHANGED
data/test/fixtures/schema.rb
CHANGED
data/test/test_dase.rb
CHANGED
@@ -57,13 +57,42 @@ class TestBase < Test::Unit::TestCase
|
|
57
57
|
compare_counts(traditional_counts, dase_counts, true_counts)
|
58
58
|
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
should "count books for year 2012 using :only option" do
|
61
|
+
dase_counts = Author.includes_count_of(:books, :only => Book.year2012).order(:name).map { |a| a.books_count }
|
62
|
+
# the order is: Bobby, Joe, Teddy - due to order(:name)
|
63
|
+
true_counts = [0, 1, 0] # see books.yml
|
64
|
+
assert_equal true_counts, dase_counts, "results mismatch"
|
65
|
+
end
|
66
|
+
|
67
|
+
#should "count using block conditions (arity: 0)" do
|
68
|
+
# dase_counts = Author.includes_count_of(:books){where(:year => 2012)}.order(:name).map { |a| a.books_count }
|
64
69
|
# # the order is: Bobby, Joe, Teddy - due to order(:name)
|
65
|
-
# true_counts = [
|
66
|
-
#
|
70
|
+
# true_counts = [0, 1, 0] # see books.yml
|
71
|
+
# assert_equal true_counts, dase_counts, "results mismatch"
|
67
72
|
#end
|
73
|
+
#
|
74
|
+
#should "count using block conditions (arity: 1)" do
|
75
|
+
# @y = 2012
|
76
|
+
# dase_counts = Author.includes_count_of(:books){ |books| books.where(:year => @y)}.order(:name).map { |a| a.books_count }
|
77
|
+
# # the order is: Bobby, Joe, Teddy - due to order(:name)
|
78
|
+
# true_counts = [0, 1, 0] # see books.yml
|
79
|
+
# assert_equal true_counts, dase_counts, "results mismatch"
|
80
|
+
#end
|
81
|
+
|
82
|
+
should "count likes" do
|
83
|
+
dase_counts = Author.order(:name).includes_count_of(:scores).map { |a| a.scores_count }
|
84
|
+
# the order is: Bobby, Joe, Teddy - due to order(:name)
|
85
|
+
true_counts = [0, 2, 0] # see likes.yml
|
86
|
+
assert_equal true_counts, dase_counts, "results mismatch"
|
87
|
+
end
|
88
|
+
|
89
|
+
should "count quotations" do
|
90
|
+
traditional_counts = Author.order(:name).map { |a| a.quotes.count }
|
91
|
+
dase_counts = Author.order(:name).includes_count_of(:quotes).map { |a| a.quotes_count }
|
92
|
+
# the order is: Bobby, Joe, Teddy - due to order(:name)
|
93
|
+
true_counts = [2, 1, 0] # see quotes.yml
|
94
|
+
compare_counts(traditional_counts, dase_counts, true_counts)
|
95
|
+
end
|
96
|
+
|
68
97
|
end
|
69
98
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.2.
|
4
|
+
version: 3.2.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement: &
|
16
|
+
requirement: &70360982006260 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.2.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70360982006260
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activesupport
|
27
|
-
requirement: &
|
27
|
+
requirement: &70360982005420 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 3.2.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70360982005420
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: shoulda
|
38
|
-
requirement: &
|
38
|
+
requirement: &70360982004720 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70360982004720
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sqlite3
|
49
|
-
requirement: &
|
49
|
+
requirement: &70360982003420 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 1.3.3
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70360982003420
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: debugger
|
60
|
-
requirement: &
|
60
|
+
requirement: &70360982001620 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70360982001620
|
69
69
|
description: ! "Dase gem creates includes_count_of method in ActiveRecord::Relation\n
|
70
70
|
\ to count associated records efficiently. See examples at
|
71
71
|
https://github.com/vovayartsev/dase\n "
|
@@ -91,6 +91,8 @@ files:
|
|
91
91
|
- test/fixtures/authors.yml
|
92
92
|
- test/fixtures/book.rb
|
93
93
|
- test/fixtures/books.yml
|
94
|
+
- test/fixtures/like.rb
|
95
|
+
- test/fixtures/likes.yml
|
94
96
|
- test/fixtures/quote.rb
|
95
97
|
- test/fixtures/quotes.yml
|
96
98
|
- test/fixtures/schema.rb
|
@@ -126,6 +128,8 @@ test_files:
|
|
126
128
|
- test/fixtures/authors.yml
|
127
129
|
- test/fixtures/book.rb
|
128
130
|
- test/fixtures/books.yml
|
131
|
+
- test/fixtures/like.rb
|
132
|
+
- test/fixtures/likes.yml
|
129
133
|
- test/fixtures/quote.rb
|
130
134
|
- test/fixtures/quotes.yml
|
131
135
|
- test/fixtures/schema.rb
|