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 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
- ### Advanced usage:
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
- ### Known problems
50
-
51
- 1. Dase doesn't support :through option on associations
52
- 2. You can't put includes_count_of calls into a scope declaration, like this:
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
- class Author
56
- scope :with_counters, lambda {
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
- if options[:as].present?
17
- options[:association] = arg
18
- relation.dase_values[options[:as].to_sym] = options
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 |association, options|
29
- association = options.delete(:association) if options[:association]
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
@@ -10,9 +10,18 @@ module Dase
10
10
  end
11
11
 
12
12
  # Not implemented yet
13
- #class HasManyThrough < ::ActiveRecord::Associations::Preloader::HasManyThrough
14
- # include Dase::PreloaderMethods
15
- #end
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
- raise NotImplementedError, "The support for HasManyThrough associations is not implemented yet"
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
- fk = "#{scoped.quoted_table_name}.#{reflection.foreign_key}"
16
- counters_hash = records_for(ids).count(group: fk)
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(counter_name, value)
36
+ owner.set_dase_counter(@dase_counter_name, value)
20
37
  end
21
38
  end
22
39
 
data/lib/dase/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dase
2
- VERSION = "3.2.2"
2
+ VERSION = "3.2.4"
3
3
  end
@@ -5,6 +5,7 @@ class Author < ActiveRecord::Base
5
5
  has_many :old_books, :class_name => "Book", :conditions => {:year => 1990}
6
6
 
7
7
  has_many :quotes, :through => :books
8
+ has_many :scores, :through => :quotes
8
9
 
9
10
  scope :with_count_of_books, lambda { includes_count_of(:books) }
10
11
 
@@ -4,4 +4,6 @@ class Book < ActiveRecord::Base
4
4
  belongs_to :author
5
5
  has_many :quotes
6
6
 
7
+ scope :year2012, lambda{ where(:year => 2012) }
8
+
7
9
  end
@@ -0,0 +1,4 @@
1
+ class Like < ActiveRecord::Base
2
+ belongs_to :quote
3
+ has_one :author, :through => :quote
4
+ end
@@ -0,0 +1,10 @@
1
+ good:
2
+ id: 1
3
+ quote_id: 1
4
+ score: 5
5
+
6
+ bad:
7
+ id: 2
8
+ quote_id: 1
9
+ score: 2
10
+
@@ -1,4 +1,5 @@
1
1
  class Quote < ActiveRecord::Base
2
2
  belongs_to :book
3
-
3
+ #has_one :author, :through => :book
4
+ has_many :scores, :class_name => 'Like', :foreign_key => "quote_id"
4
5
  end
@@ -19,4 +19,10 @@ ActiveRecord::Schema.define do
19
19
  t.integer "book_id"
20
20
  end
21
21
 
22
+ create_table "likes", :force => true do |t|
23
+ t.integer "score"
24
+ t.integer "quote_id"
25
+ end
26
+
27
+
22
28
  end
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
- # Not yet implemented
61
- #should "count quotations" do
62
- # traditional_counts = Author.order(:name).map { |a| a.quotes.count }
63
- # dase_counts = Author.order(:name).includes_count_of(:quotes).map { |a| a.quotes_count }
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 = [2, 1, 0] # see quotes.yml
66
- # compare_counts(traditional_counts, dase_counts, true_counts)
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.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-23 00:00:00.000000000 Z
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: &70318116217480 !ruby/object:Gem::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: *70318116217480
24
+ version_requirements: *70360982006260
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activesupport
27
- requirement: &70318116214540 !ruby/object:Gem::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: *70318116214540
35
+ version_requirements: *70360982005420
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: shoulda
38
- requirement: &70318116213300 !ruby/object:Gem::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: *70318116213300
46
+ version_requirements: *70360982004720
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sqlite3
49
- requirement: &70318116211980 !ruby/object:Gem::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: *70318116211980
57
+ version_requirements: *70360982003420
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: debugger
60
- requirement: &70318116211100 !ruby/object:Gem::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: *70318116211100
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