square-activerecord 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6140 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +179 -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 +430 -0
- data/lib/active_record/associations.rb +2307 -0
- data/lib/active_record/associations/association_collection.rb +572 -0
- data/lib/active_record/associations/association_proxy.rb +299 -0
- data/lib/active_record/associations/belongs_to_association.rb +91 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
- data/lib/active_record/associations/has_many_association.rb +128 -0
- data/lib/active_record/associations/has_many_through_association.rb +115 -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 +30 -0
- data/lib/active_record/attribute_methods/dirty.rb +95 -0
- data/lib/active_record/attribute_methods/primary_key.rb +56 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +145 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
- data/lib/active_record/attribute_methods/write.rb +43 -0
- data/lib/active_record/autosave_association.rb +369 -0
- data/lib/active_record/base.rb +1904 -0
- data/lib/active_record/callbacks.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -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 +333 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +539 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -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 +56 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +172 -0
- data/lib/active_record/fixtures.rb +1006 -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 +419 -0
- data/lib/active_record/observer.rb +125 -0
- data/lib/active_record/persistence.rb +290 -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 +411 -0
- data/lib/active_record/relation.rb +394 -0
- data/lib/active_record/relation/batches.rb +89 -0
- data/lib/active_record/relation/calculations.rb +295 -0
- data/lib/active_record/relation/finder_methods.rb +363 -0
- data/lib/active_record/relation/predicate_builder.rb +48 -0
- data/lib/active_record/relation/query_methods.rb +303 -0
- data/lib/active_record/relation/spawn_methods.rb +132 -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 +359 -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 +190 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +19 -0
- data/lib/rails/generators/active_record/migration.rb +15 -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 +223 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'active_support/deprecation'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# = Active Record Has And Belongs To Many Association
|
5
|
+
module Associations
|
6
|
+
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
7
|
+
def initialize(owner, reflection)
|
8
|
+
super
|
9
|
+
if columns.size > 2
|
10
|
+
ActiveSupport::Deprecation.warn "Having additional attributes on the join table of a has_and_belongs_to_many association is deprecated and will be removed in Rails 3.1. Please use a has_many :through association instead."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(attributes = {})
|
15
|
+
create_record(attributes) { |record| insert_record(record) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def create!(attributes = {})
|
19
|
+
create_record(attributes) { |record| insert_record(record, true) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def columns
|
23
|
+
@reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset_column_information
|
27
|
+
@reflection.reset_column_information
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_primary_key?
|
31
|
+
@has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table])
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
def construct_find_options!(options)
|
36
|
+
options[:joins] = Arel::SqlLiteral.new @join_sql
|
37
|
+
options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
|
38
|
+
options[:select] ||= (@reflection.options[:select] || Arel::SqlLiteral.new('*'))
|
39
|
+
end
|
40
|
+
|
41
|
+
def count_records
|
42
|
+
load_target.size
|
43
|
+
end
|
44
|
+
|
45
|
+
def insert_record(record, force = true, validate = true)
|
46
|
+
if record.new_record?
|
47
|
+
if force
|
48
|
+
record.save!
|
49
|
+
else
|
50
|
+
return false unless record.save(:validate => validate)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if @reflection.options[:insert_sql]
|
55
|
+
@owner.connection.insert(interpolate_and_sanitize_sql(@reflection.options[:insert_sql], record))
|
56
|
+
else
|
57
|
+
relation = Arel::Table.new(@reflection.options[:join_table])
|
58
|
+
timestamps = record_timestamp_columns(record)
|
59
|
+
timezone = record.send(:current_time_from_proper_timezone) if timestamps.any?
|
60
|
+
|
61
|
+
attributes = Hash[columns.map do |column|
|
62
|
+
name = column.name
|
63
|
+
value = case name.to_s
|
64
|
+
when @reflection.primary_key_name.to_s
|
65
|
+
@owner.id
|
66
|
+
when @reflection.association_foreign_key.to_s
|
67
|
+
record.id
|
68
|
+
when *timestamps
|
69
|
+
timezone
|
70
|
+
else
|
71
|
+
@owner.send(:quote_value, record[name], column) if record.has_attribute?(name)
|
72
|
+
end
|
73
|
+
[relation[name], value] unless value.nil?
|
74
|
+
end]
|
75
|
+
|
76
|
+
relation.insert(attributes)
|
77
|
+
end
|
78
|
+
|
79
|
+
return true
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete_records(records)
|
83
|
+
if sql = @reflection.options[:delete_sql]
|
84
|
+
records.each { |record| @owner.connection.delete(interpolate_and_sanitize_sql(sql, record)) }
|
85
|
+
else
|
86
|
+
relation = Arel::Table.new(@reflection.options[:join_table])
|
87
|
+
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
88
|
+
and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
|
89
|
+
).delete
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def construct_sql
|
94
|
+
if @reflection.options[:finder_sql]
|
95
|
+
@finder_sql = interpolate_and_sanitize_sql(@reflection.options[:finder_sql])
|
96
|
+
else
|
97
|
+
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
|
98
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
99
|
+
end
|
100
|
+
|
101
|
+
@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}"
|
102
|
+
|
103
|
+
construct_counter_sql
|
104
|
+
end
|
105
|
+
|
106
|
+
def construct_scope
|
107
|
+
{ :find => { :conditions => @finder_sql,
|
108
|
+
:joins => @join_sql,
|
109
|
+
:readonly => false,
|
110
|
+
:order => @reflection.options[:order],
|
111
|
+
:include => @reflection.options[:include],
|
112
|
+
:limit => @reflection.options[:limit] } }
|
113
|
+
end
|
114
|
+
|
115
|
+
# Join tables with additional columns on top of the two foreign keys must be considered
|
116
|
+
# ambiguous unless a select clause has been explicitly defined. Otherwise you can get
|
117
|
+
# broken records back, if, for example, the join column also has an id column. This will
|
118
|
+
# then overwrite the id column of the records coming back.
|
119
|
+
def finding_with_ambiguous_select?(select_clause)
|
120
|
+
!select_clause && columns.size != 2
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
def create_record(attributes, &block)
|
125
|
+
# Can't use Base.create because the foreign key may be a protected attribute.
|
126
|
+
ensure_owner_is_not_new
|
127
|
+
if attributes.is_a?(Array)
|
128
|
+
attributes.collect { |attr| create(attr) }
|
129
|
+
else
|
130
|
+
build_record(attributes, &block)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def record_timestamp_columns(record)
|
135
|
+
if record.record_timestamps
|
136
|
+
record.send(:all_timestamp_attributes).map { |x| x.to_s }
|
137
|
+
else
|
138
|
+
[]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
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[:finder_sql] || @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_and_sanitize_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,115 @@
|
|
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_association.create!(construct_join_attributes(record))
|
72
|
+
end
|
73
|
+
|
74
|
+
# TODO - add dependent option support
|
75
|
+
def delete_records(records)
|
76
|
+
klass = @reflection.through_reflection.klass
|
77
|
+
records.each do |associate|
|
78
|
+
klass.delete_all(construct_join_attributes(associate))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def find_target
|
83
|
+
return [] unless target_reflection_has_associated_record?
|
84
|
+
with_scope(construct_scope) { @reflection.klass.find(:all) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def construct_sql
|
88
|
+
case
|
89
|
+
when @reflection.options[:finder_sql]
|
90
|
+
@finder_sql = interpolate_and_sanitize_sql(@reflection.options[:finder_sql])
|
91
|
+
|
92
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
93
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
94
|
+
else
|
95
|
+
@finder_sql = construct_conditions
|
96
|
+
end
|
97
|
+
|
98
|
+
construct_counter_sql
|
99
|
+
end
|
100
|
+
|
101
|
+
def has_cached_counter?
|
102
|
+
@owner.attribute_present?(cached_counter_attribute_name)
|
103
|
+
end
|
104
|
+
|
105
|
+
def cached_counter_attribute_name
|
106
|
+
"#{@reflection.name}_count"
|
107
|
+
end
|
108
|
+
|
109
|
+
# NOTE - not sure that we can actually cope with inverses here
|
110
|
+
def we_can_set_the_inverse_on_this?(record)
|
111
|
+
false
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
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
|