activerecord 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +6023 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +162 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +124 -0
- data/lib/active_record/aggregations.rb +277 -0
- data/lib/active_record/association_preload.rb +403 -0
- data/lib/active_record/associations.rb +2254 -0
- data/lib/active_record/associations/association_collection.rb +562 -0
- data/lib/active_record/associations/association_proxy.rb +295 -0
- data/lib/active_record/associations/belongs_to_association.rb +91 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
- data/lib/active_record/associations/has_many_association.rb +128 -0
- data/lib/active_record/associations/has_many_through_association.rb +116 -0
- data/lib/active_record/associations/has_one_association.rb +143 -0
- data/lib/active_record/associations/has_one_through_association.rb +40 -0
- data/lib/active_record/associations/through_association_scope.rb +154 -0
- data/lib/active_record/attribute_methods.rb +60 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
- data/lib/active_record/attribute_methods/dirty.rb +95 -0
- data/lib/active_record/attribute_methods/primary_key.rb +50 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +116 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
- data/lib/active_record/attribute_methods/write.rb +37 -0
- data/lib/active_record/autosave_association.rb +369 -0
- data/lib/active_record/base.rb +1867 -0
- data/lib/active_record/callbacks.rb +288 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
- data/lib/active_record/counter_cache.rb +115 -0
- data/lib/active_record/dynamic_finder_match.rb +53 -0
- data/lib/active_record/dynamic_scope_match.rb +32 -0
- data/lib/active_record/errors.rb +172 -0
- data/lib/active_record/fixtures.rb +1008 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +172 -0
- data/lib/active_record/locking/pessimistic.rb +55 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +617 -0
- data/lib/active_record/named_scope.rb +138 -0
- data/lib/active_record/nested_attributes.rb +417 -0
- data/lib/active_record/observer.rb +140 -0
- data/lib/active_record/persistence.rb +291 -0
- data/lib/active_record/query_cache.rb +36 -0
- data/lib/active_record/railtie.rb +91 -0
- data/lib/active_record/railties/controller_runtime.rb +38 -0
- data/lib/active_record/railties/databases.rake +512 -0
- data/lib/active_record/reflection.rb +403 -0
- data/lib/active_record/relation.rb +393 -0
- data/lib/active_record/relation/batches.rb +89 -0
- data/lib/active_record/relation/calculations.rb +286 -0
- data/lib/active_record/relation/finder_methods.rb +355 -0
- data/lib/active_record/relation/predicate_builder.rb +41 -0
- data/lib/active_record/relation/query_methods.rb +261 -0
- data/lib/active_record/relation/spawn_methods.rb +112 -0
- data/lib/active_record/schema.rb +59 -0
- data/lib/active_record/schema_dumper.rb +195 -0
- data/lib/active_record/serialization.rb +60 -0
- data/lib/active_record/serializers/xml_serializer.rb +244 -0
- data/lib/active_record/session_store.rb +340 -0
- data/lib/active_record/test_case.rb +67 -0
- data/lib/active_record/timestamp.rb +88 -0
- data/lib/active_record/transactions.rb +356 -0
- data/lib/active_record/validations.rb +84 -0
- data/lib/active_record/validations/associated.rb +48 -0
- data/lib/active_record/validations/uniqueness.rb +185 -0
- data/lib/active_record/version.rb +9 -0
- data/lib/rails/generators/active_record.rb +27 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
- metadata +224 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record Has And Belongs To Many Association
|
3
|
+
module Associations
|
4
|
+
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
5
|
+
def create(attributes = {})
|
6
|
+
create_record(attributes) { |record| insert_record(record) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def create!(attributes = {})
|
10
|
+
create_record(attributes) { |record| insert_record(record, true) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def columns
|
14
|
+
@reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset_column_information
|
18
|
+
@reflection.reset_column_information
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_primary_key?
|
22
|
+
@has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table])
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def construct_find_options!(options)
|
27
|
+
options[:joins] = Arel::SqlLiteral.new @join_sql
|
28
|
+
options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
|
29
|
+
options[:select] ||= (@reflection.options[:select] || Arel::SqlLiteral.new('*'))
|
30
|
+
end
|
31
|
+
|
32
|
+
def count_records
|
33
|
+
load_target.size
|
34
|
+
end
|
35
|
+
|
36
|
+
def insert_record(record, force = true, validate = true)
|
37
|
+
if record.new_record?
|
38
|
+
if force
|
39
|
+
record.save!
|
40
|
+
else
|
41
|
+
return false unless record.save(:validate => validate)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if @reflection.options[:insert_sql]
|
46
|
+
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
|
47
|
+
else
|
48
|
+
relation = Arel::Table.new(@reflection.options[:join_table])
|
49
|
+
timestamps = record_timestamp_columns(record)
|
50
|
+
timezone = record.send(:current_time_from_proper_timezone) if timestamps.any?
|
51
|
+
|
52
|
+
attributes = columns.inject({}) do |attrs, column|
|
53
|
+
name = column.name
|
54
|
+
case name.to_s
|
55
|
+
when @reflection.primary_key_name.to_s
|
56
|
+
attrs[relation[name]] = @owner.id
|
57
|
+
when @reflection.association_foreign_key.to_s
|
58
|
+
attrs[relation[name]] = record.id
|
59
|
+
when *timestamps
|
60
|
+
attrs[relation[name]] = timezone
|
61
|
+
else
|
62
|
+
if record.has_attribute?(name)
|
63
|
+
value = @owner.send(:quote_value, record[name], column)
|
64
|
+
attrs[relation[name]] = value unless value.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
attrs
|
68
|
+
end
|
69
|
+
|
70
|
+
relation.insert(attributes)
|
71
|
+
end
|
72
|
+
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete_records(records)
|
77
|
+
if sql = @reflection.options[:delete_sql]
|
78
|
+
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
|
79
|
+
else
|
80
|
+
relation = Arel::Table.new(@reflection.options[:join_table])
|
81
|
+
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
82
|
+
and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }))
|
83
|
+
).delete
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def construct_sql
|
88
|
+
if @reflection.options[:finder_sql]
|
89
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
90
|
+
else
|
91
|
+
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
|
92
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
93
|
+
end
|
94
|
+
|
95
|
+
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
96
|
+
|
97
|
+
construct_counter_sql
|
98
|
+
end
|
99
|
+
|
100
|
+
def construct_scope
|
101
|
+
{ :find => { :conditions => @finder_sql,
|
102
|
+
:joins => @join_sql,
|
103
|
+
:readonly => false,
|
104
|
+
:order => @reflection.options[:order],
|
105
|
+
:include => @reflection.options[:include],
|
106
|
+
:limit => @reflection.options[:limit] } }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Join tables with additional columns on top of the two foreign keys must be considered
|
110
|
+
# ambiguous unless a select clause has been explicitly defined. Otherwise you can get
|
111
|
+
# broken records back, if, for example, the join column also has an id column. This will
|
112
|
+
# then overwrite the id column of the records coming back.
|
113
|
+
def finding_with_ambiguous_select?(select_clause)
|
114
|
+
!select_clause && columns.size != 2
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def create_record(attributes, &block)
|
119
|
+
# Can't use Base.create because the foreign key may be a protected attribute.
|
120
|
+
ensure_owner_is_not_new
|
121
|
+
if attributes.is_a?(Array)
|
122
|
+
attributes.collect { |attr| create(attr) }
|
123
|
+
else
|
124
|
+
build_record(attributes, &block)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def record_timestamp_columns(record)
|
129
|
+
if record.record_timestamps
|
130
|
+
record.send(:all_timestamp_attributes).map { |x| x.to_s }
|
131
|
+
else
|
132
|
+
[]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record Has Many Association
|
3
|
+
module Associations
|
4
|
+
# This is the proxy that handles a has many association.
|
5
|
+
#
|
6
|
+
# If the association has a <tt>:through</tt> option further specialization
|
7
|
+
# is provided by its child HasManyThroughAssociation.
|
8
|
+
class HasManyAssociation < AssociationCollection #:nodoc:
|
9
|
+
def initialize(owner, reflection)
|
10
|
+
@finder_sql = nil
|
11
|
+
super
|
12
|
+
end
|
13
|
+
protected
|
14
|
+
def owner_quoted_id
|
15
|
+
if @reflection.options[:primary_key]
|
16
|
+
quote_value(@owner.send(@reflection.options[:primary_key]))
|
17
|
+
else
|
18
|
+
@owner.quoted_id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the number of records in this collection.
|
23
|
+
#
|
24
|
+
# If the association has a counter cache it gets that value. Otherwise
|
25
|
+
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
|
26
|
+
# there's one. Some configuration options like :group make it impossible
|
27
|
+
# to do an SQL count, in those cases the array count will be used.
|
28
|
+
#
|
29
|
+
# That does not depend on whether the collection has already been loaded
|
30
|
+
# or not. The +size+ method is the one that takes the loaded flag into
|
31
|
+
# account and delegates to +count_records+ if needed.
|
32
|
+
#
|
33
|
+
# If the collection is empty the target is set to an empty array and
|
34
|
+
# the loaded flag is set to true as well.
|
35
|
+
def count_records
|
36
|
+
count = if has_cached_counter?
|
37
|
+
@owner.send(:read_attribute, cached_counter_attribute_name)
|
38
|
+
elsif @reflection.options[:counter_sql]
|
39
|
+
@reflection.klass.count_by_sql(@counter_sql)
|
40
|
+
else
|
41
|
+
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
|
42
|
+
end
|
43
|
+
|
44
|
+
# If there's nothing in the database and @target has no new records
|
45
|
+
# we are certain the current target is an empty array. This is a
|
46
|
+
# documented side-effect of the method that may avoid an extra SELECT.
|
47
|
+
@target ||= [] and loaded if count == 0
|
48
|
+
|
49
|
+
if @reflection.options[:limit]
|
50
|
+
count = [ @reflection.options[:limit], count ].min
|
51
|
+
end
|
52
|
+
|
53
|
+
return count
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_cached_counter?
|
57
|
+
@owner.attribute_present?(cached_counter_attribute_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def cached_counter_attribute_name
|
61
|
+
"#{@reflection.name}_count"
|
62
|
+
end
|
63
|
+
|
64
|
+
def insert_record(record, force = false, validate = true)
|
65
|
+
set_belongs_to_association_for(record)
|
66
|
+
force ? record.save! : record.save(:validate => validate)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Deletes the records according to the <tt>:dependent</tt> option.
|
70
|
+
def delete_records(records)
|
71
|
+
case @reflection.options[:dependent]
|
72
|
+
when :destroy
|
73
|
+
records.each { |r| r.destroy }
|
74
|
+
when :delete_all
|
75
|
+
@reflection.klass.delete(records.map { |record| record.id })
|
76
|
+
else
|
77
|
+
relation = Arel::Table.new(@reflection.table_name)
|
78
|
+
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
79
|
+
and(relation[@reflection.klass.primary_key].in(records.map { |r| r.id }))
|
80
|
+
).update(relation[@reflection.primary_key_name] => nil)
|
81
|
+
|
82
|
+
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def target_obsolete?
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
def construct_sql
|
91
|
+
case
|
92
|
+
when @reflection.options[:finder_sql]
|
93
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
94
|
+
|
95
|
+
when @reflection.options[:as]
|
96
|
+
@finder_sql =
|
97
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
98
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
99
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
100
|
+
|
101
|
+
else
|
102
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
103
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
104
|
+
end
|
105
|
+
|
106
|
+
construct_counter_sql
|
107
|
+
end
|
108
|
+
|
109
|
+
def construct_scope
|
110
|
+
create_scoping = {}
|
111
|
+
set_belongs_to_association_for(create_scoping)
|
112
|
+
{
|
113
|
+
:find => { :conditions => @finder_sql,
|
114
|
+
:readonly => false,
|
115
|
+
:order => @reflection.options[:order],
|
116
|
+
:limit => @reflection.options[:limit],
|
117
|
+
:include => @reflection.options[:include]},
|
118
|
+
:create => create_scoping
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
def we_can_set_the_inverse_on_this?(record)
|
123
|
+
inverse = @reflection.inverse_of
|
124
|
+
return !inverse.nil?
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require "active_record/associations/through_association_scope"
|
2
|
+
require 'active_support/core_ext/object/blank'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
# = Active Record Has Many Through Association
|
6
|
+
module Associations
|
7
|
+
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
|
8
|
+
include ThroughAssociationScope
|
9
|
+
|
10
|
+
alias_method :new, :build
|
11
|
+
|
12
|
+
def create!(attrs = nil)
|
13
|
+
create_record(attrs, true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create(attrs = nil)
|
17
|
+
create_record(attrs, false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy(*records)
|
21
|
+
transaction do
|
22
|
+
delete_records(flatten_deeper(records))
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
|
28
|
+
# loaded and calling collection.size if it has. If it's more likely than not that the collection does
|
29
|
+
# have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
|
30
|
+
# SELECT query if you use #length.
|
31
|
+
def size
|
32
|
+
return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
|
33
|
+
return @target.size if loaded?
|
34
|
+
return count
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def create_record(attrs, force = true)
|
39
|
+
ensure_owner_is_not_new
|
40
|
+
|
41
|
+
transaction do
|
42
|
+
object = @reflection.klass.new(attrs)
|
43
|
+
add_record_to_target_with_callbacks(object) {|r| insert_record(object, force) }
|
44
|
+
object
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def target_reflection_has_associated_record?
|
49
|
+
if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
|
50
|
+
false
|
51
|
+
else
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def construct_find_options!(options)
|
57
|
+
options[:joins] = construct_joins(options[:joins])
|
58
|
+
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
|
59
|
+
end
|
60
|
+
|
61
|
+
def insert_record(record, force = true, validate = true)
|
62
|
+
if record.new_record?
|
63
|
+
if force
|
64
|
+
record.save!
|
65
|
+
else
|
66
|
+
return false unless record.save(:validate => validate)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
through_association = @owner.send(@reflection.through_reflection.name)
|
71
|
+
through_record = through_association.create!(construct_join_attributes(record))
|
72
|
+
through_association.proxy_target << through_record
|
73
|
+
end
|
74
|
+
|
75
|
+
# TODO - add dependent option support
|
76
|
+
def delete_records(records)
|
77
|
+
klass = @reflection.through_reflection.klass
|
78
|
+
records.each do |associate|
|
79
|
+
klass.delete_all(construct_join_attributes(associate))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_target
|
84
|
+
return [] unless target_reflection_has_associated_record?
|
85
|
+
with_scope(construct_scope) { @reflection.klass.find(:all) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def construct_sql
|
89
|
+
case
|
90
|
+
when @reflection.options[:finder_sql]
|
91
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
92
|
+
|
93
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
94
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
95
|
+
else
|
96
|
+
@finder_sql = construct_conditions
|
97
|
+
end
|
98
|
+
|
99
|
+
construct_counter_sql
|
100
|
+
end
|
101
|
+
|
102
|
+
def has_cached_counter?
|
103
|
+
@owner.attribute_present?(cached_counter_attribute_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def cached_counter_attribute_name
|
107
|
+
"#{@reflection.name}_count"
|
108
|
+
end
|
109
|
+
|
110
|
+
# NOTE - not sure that we can actually cope with inverses here
|
111
|
+
def we_can_set_the_inverse_on_this?(record)
|
112
|
+
false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record Belongs To Has One Association
|
3
|
+
module Associations
|
4
|
+
class HasOneAssociation < AssociationProxy #:nodoc:
|
5
|
+
def initialize(owner, reflection)
|
6
|
+
super
|
7
|
+
construct_sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def create(attrs = {}, replace_existing = true)
|
11
|
+
new_record(replace_existing) do |reflection|
|
12
|
+
attrs = merge_with_conditions(attrs)
|
13
|
+
reflection.create_association(attrs)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def create!(attrs = {}, replace_existing = true)
|
18
|
+
new_record(replace_existing) do |reflection|
|
19
|
+
attrs = merge_with_conditions(attrs)
|
20
|
+
reflection.create_association!(attrs)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def build(attrs = {}, replace_existing = true)
|
25
|
+
new_record(replace_existing) do |reflection|
|
26
|
+
attrs = merge_with_conditions(attrs)
|
27
|
+
reflection.build_association(attrs)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def replace(obj, dont_save = false)
|
32
|
+
load_target
|
33
|
+
|
34
|
+
unless @target.nil? || @target == obj
|
35
|
+
if dependent? && !dont_save
|
36
|
+
case @reflection.options[:dependent]
|
37
|
+
when :delete
|
38
|
+
@target.delete unless @target.new_record?
|
39
|
+
@owner.clear_association_cache
|
40
|
+
when :destroy
|
41
|
+
@target.destroy unless @target.new_record?
|
42
|
+
@owner.clear_association_cache
|
43
|
+
when :nullify
|
44
|
+
@target[@reflection.primary_key_name] = nil
|
45
|
+
@target.save unless @owner.new_record? || @target.new_record?
|
46
|
+
end
|
47
|
+
else
|
48
|
+
@target[@reflection.primary_key_name] = nil
|
49
|
+
@target.save unless @owner.new_record? || @target.new_record?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if obj.nil?
|
54
|
+
@target = nil
|
55
|
+
else
|
56
|
+
raise_on_type_mismatch(obj)
|
57
|
+
set_belongs_to_association_for(obj)
|
58
|
+
@target = (AssociationProxy === obj ? obj.target : obj)
|
59
|
+
end
|
60
|
+
|
61
|
+
set_inverse_instance(obj, @owner)
|
62
|
+
@loaded = true
|
63
|
+
|
64
|
+
unless @owner.new_record? or obj.nil? or dont_save
|
65
|
+
return (obj.save ? self : false)
|
66
|
+
else
|
67
|
+
return (obj.nil? ? nil : self)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
def owner_quoted_id
|
73
|
+
if @reflection.options[:primary_key]
|
74
|
+
@owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
|
75
|
+
else
|
76
|
+
@owner.quoted_id
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def find_target
|
82
|
+
options = @reflection.options.dup
|
83
|
+
(options.keys - [:select, :order, :include, :readonly]).each do |key|
|
84
|
+
options.delete key
|
85
|
+
end
|
86
|
+
options[:conditions] = @finder_sql
|
87
|
+
|
88
|
+
the_target = @reflection.klass.find(:first, options)
|
89
|
+
set_inverse_instance(the_target, @owner)
|
90
|
+
the_target
|
91
|
+
end
|
92
|
+
|
93
|
+
def construct_sql
|
94
|
+
case
|
95
|
+
when @reflection.options[:as]
|
96
|
+
@finder_sql =
|
97
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
98
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
99
|
+
else
|
100
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
101
|
+
end
|
102
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
103
|
+
end
|
104
|
+
|
105
|
+
def construct_scope
|
106
|
+
create_scoping = {}
|
107
|
+
set_belongs_to_association_for(create_scoping)
|
108
|
+
{ :create => create_scoping }
|
109
|
+
end
|
110
|
+
|
111
|
+
def new_record(replace_existing)
|
112
|
+
# Make sure we load the target first, if we plan on replacing the existing
|
113
|
+
# instance. Otherwise, if the target has not previously been loaded
|
114
|
+
# elsewhere, the instance we create will get orphaned.
|
115
|
+
load_target if replace_existing
|
116
|
+
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
|
117
|
+
yield @reflection
|
118
|
+
end
|
119
|
+
|
120
|
+
if replace_existing
|
121
|
+
replace(record, true)
|
122
|
+
else
|
123
|
+
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
124
|
+
self.target = record
|
125
|
+
set_inverse_instance(record, @owner)
|
126
|
+
end
|
127
|
+
|
128
|
+
record
|
129
|
+
end
|
130
|
+
|
131
|
+
def we_can_set_the_inverse_on_this?(record)
|
132
|
+
inverse = @reflection.inverse_of
|
133
|
+
return !inverse.nil?
|
134
|
+
end
|
135
|
+
|
136
|
+
def merge_with_conditions(attrs={})
|
137
|
+
attrs ||= {}
|
138
|
+
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
139
|
+
attrs
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|