Class: ActiveRecord::Associations::HasManyThroughAssociation

Inherits:
AssociationProxy show all
Defined in:
lib/active_record/associations/has_many_through_association.rb

Overview

:nodoc:

Instance Attribute Summary

Attributes inherited from AssociationProxy

#reflection

Instance Method Summary collapse

Methods inherited from AssociationProxy

#===, #aliased_table_name, #inspect, #loaded, #loaded?, #proxy_owner, #proxy_reflection, #proxy_respond_to?, #proxy_target, #reload, #respond_to?, #target, #target=

Constructor Details

#initialize(owner, reflection) ⇒ HasManyThroughAssociation

Returns a new instance of HasManyThroughAssociation.



4
5
6
7
8
9
# File 'lib/active_record/associations/has_many_through_association.rb', line 4

def initialize(owner, reflection)
  super
  reflection.check_validity!
  @finder_sql = construct_conditions
  construct_sql
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object (protected)



131
132
133
134
135
136
137
# File 'lib/active_record/associations/has_many_through_association.rb', line 131

def method_missing(method, *args, &block)
  if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
    super
  else
    @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) }
  end
end

Instance Method Details

#<<(*records) ⇒ Object

Adds records to the association. The source record and its associates must have ids in order to create records associating them, so this will raise ActiveRecord::HasManyThroughCantAssociateNewRecords if either is a new record. Calls create! so you can rescue errors.

The :before_add and :after_add callbacks are not yet supported.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/active_record/associations/has_many_through_association.rb', line 49

def <<(*records)
  return if records.empty?
  through = @reflection.through_reflection
  raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) if @owner.new_record?

  klass = through.klass
  klass.transaction do
    flatten_deeper(records).each do |associate|
      raise_on_type_mismatch(associate)
      raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?

      @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(associate)) { klass.create! }
      @target << associate if loaded?
    end
  end

  self
end

#build(attrs = nil) ⇒ Object Also known as: new



94
95
96
# File 'lib/active_record/associations/has_many_through_association.rb', line 94

def build(attrs = nil)
  raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, @reflection.through_reflection)
end

#count(*args) ⇒ Object



120
121
122
123
124
125
126
127
128
# File 'lib/active_record/associations/has_many_through_association.rb', line 120

def count(*args)
  column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
  if @reflection.options[:uniq]
    # This is needed because 'SELECT count(DISTINCT *)..' is not valid sql statement.
    column_name = "#{@reflection.klass.table_name}.#{@reflection.klass.primary_key}" if column_name == :all
    options.merge!(:distinct => true) 
  end
  @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) } 
end

#create!(attrs = nil) ⇒ Object



99
100
101
102
103
104
# File 'lib/active_record/associations/has_many_through_association.rb', line 99

def create!(attrs = nil)
  @reflection.klass.transaction do
    self << (object = @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! })
    object
  end
end

#delete(*records) ⇒ Object

Removes records from this association. Does not destroy records.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/active_record/associations/has_many_through_association.rb', line 71

def delete(*records)
  records = flatten_deeper(records)
  records.each { |associate| raise_on_type_mismatch(associate) }

  through = @reflection.through_reflection
  raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) if @owner.new_record?

  load_target

  klass = through.klass
  klass.transaction do
    flatten_deeper(records).each do |associate|
      raise_on_type_mismatch(associate)
      raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?

      @owner.send(through.name).proxy_target.delete(klass.delete_all(construct_join_attributes(associate)))
      @target.delete(associate)
    end
  end

  self
end

#find(*args) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/active_record/associations/has_many_through_association.rb', line 11

def find(*args)
  options = args.extract_options!

  conditions = "#{@finder_sql}"
  if sanitized_conditions = sanitize_sql(options[:conditions])
    conditions << " AND (#{sanitized_conditions})"
  end
  options[:conditions] = conditions

  if options[:order] && @reflection.options[:order]
    options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
  elsif @reflection.options[:order]
    options[:order] = @reflection.options[:order]
  end

  options[:select]  = construct_select(options[:select])
  options[:from]  ||= construct_from
  options[:joins]   = construct_joins(options[:joins])
  options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?

  merge_options_from_reflection!(options)

  # Pass through args exactly as we received them.
  args << options
  @reflection.klass.find(*args)
end

#resetObject



38
39
40
41
# File 'lib/active_record/associations/has_many_through_association.rb', line 38

def reset
  @target = []
  @loaded = false
end

#sizeObject

Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn’t been loaded and calling collection.size if it has. If it’s more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards, it’ll take one less SELECT query if you use length.



109
110
111
112
113
# File 'lib/active_record/associations/has_many_through_association.rb', line 109

def size
  return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
  return @target.size if loaded?
  return count
end

#sum(*args, &block) ⇒ Object

Calculate sum using SQL, not Enumerable



116
117
118
# File 'lib/active_record/associations/has_many_through_association.rb', line 116

def sum(*args, &block)
  calculate(:sum, *args, &block)
end