activerecord 2.3.3 → 2.3.4
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 +8 -1
- data/Rakefile +32 -15
- data/examples/performance.rb +162 -0
- data/lib/active_record/associations.rb +37 -5
- data/lib/active_record/associations/association_collection.rb +1 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +16 -0
- data/lib/active_record/associations/has_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_through_association.rb +13 -3
- data/lib/active_record/associations/has_one_through_association.rb +8 -2
- data/lib/active_record/autosave_association.rb +4 -3
- data/lib/active_record/base.rb +18 -10
- data/lib/active_record/calculations.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/abstract_adapter.rb +7 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +17 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -13
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +12 -0
- data/lib/active_record/dirty.rb +1 -1
- data/lib/active_record/fixtures.rb +9 -7
- data/lib/active_record/i18n_interpolation_deprecation.rb +1 -1
- data/lib/active_record/locale/en.yml +4 -0
- data/lib/active_record/named_scope.rb +1 -6
- data/lib/active_record/reflection.rb +1 -1
- data/lib/active_record/schema_dumper.rb +1 -2
- data/lib/active_record/serializers/json_serializer.rb +5 -3
- data/lib/active_record/serializers/xml_serializer.rb +6 -2
- data/lib/active_record/validations.rb +148 -79
- data/lib/active_record/version.rb +1 -1
- data/test/cases/adapter_test.rb +12 -0
- data/test/cases/associations/belongs_to_associations_test.rb +0 -18
- data/test/cases/associations/eager_load_nested_include_test.rb +5 -5
- data/test/cases/associations/habtm_join_table_test.rb +56 -0
- data/test/cases/associations/has_many_associations_test.rb +56 -2
- data/test/cases/associations/has_many_through_associations_test.rb +46 -1
- data/test/cases/associations/has_one_through_associations_test.rb +10 -0
- data/test/cases/associations/join_model_test.rb +4 -4
- data/test/cases/base_test.rb +49 -4
- data/test/cases/calculations_test.rb +6 -0
- data/test/cases/column_definition_test.rb +34 -0
- data/test/cases/dirty_test.rb +10 -0
- data/test/cases/finder_test.rb +15 -50
- data/test/cases/fixtures_test.rb +1 -1
- data/test/cases/i18n_test.rb +5 -0
- data/test/cases/method_scoping_test.rb +1 -1
- data/test/cases/migration_test.rb +39 -11
- data/test/cases/modules_test.rb +42 -0
- data/test/cases/named_scope_test.rb +6 -4
- data/test/cases/pk_test.rb +18 -0
- data/test/cases/reflection_test.rb +2 -2
- data/test/cases/schema_dumper_test.rb +19 -1
- data/test/cases/validations_i18n_test.rb +656 -624
- data/test/cases/validations_test.rb +12 -2
- data/test/cases/xml_serialization_test.rb +20 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/posts.yml +3 -0
- data/test/models/author.rb +1 -0
- data/test/models/comment.rb +5 -1
- data/test/models/company.rb +2 -0
- data/test/models/company_in_module.rb +1 -1
- data/test/models/contract.rb +5 -0
- data/test/models/organization.rb +2 -0
- data/test/models/topic.rb +0 -2
- data/test/schema/postgresql_specific_schema.rb +13 -2
- data/test/schema/schema.rb +4 -0
- metadata +12 -54
- data/test/debug.log +0 -415
data/CHANGELOG
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
*2.3.
|
1
|
+
*2.3.4 (September 4, 2009)*
|
2
|
+
|
3
|
+
* PostgreSQL: XML datatype support. #1874 [Leonardo Borges]
|
4
|
+
|
5
|
+
* SQLite: deprecate the 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
|
6
|
+
|
7
|
+
|
8
|
+
*2.3.3 (July 12, 2009)*
|
2
9
|
|
3
10
|
* Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha]
|
4
11
|
# employees.company_name references companies.name
|
data/Rakefile
CHANGED
@@ -24,14 +24,30 @@ PKG_FILES = FileList[
|
|
24
24
|
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
|
25
25
|
].exclude(/\bCVS\b|~$/)
|
26
26
|
|
27
|
+
def run_without_aborting(*tasks)
|
28
|
+
errors = []
|
29
|
+
|
30
|
+
tasks.each do |task|
|
31
|
+
begin
|
32
|
+
Rake::Task[task].invoke
|
33
|
+
rescue Exception
|
34
|
+
errors << task
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
abort "Errors running #{errors.join(', ')}" if errors.any?
|
39
|
+
end
|
27
40
|
|
28
41
|
desc 'Run mysql, sqlite, and postgresql tests by default'
|
29
42
|
task :default => :test
|
30
43
|
|
31
44
|
desc 'Run mysql, sqlite, and postgresql tests'
|
32
|
-
task :test
|
33
|
-
|
34
|
-
|
45
|
+
task :test do
|
46
|
+
tasks = defined?(JRUBY_VERSION) ?
|
47
|
+
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
|
48
|
+
%w(test_mysql test_sqlite3 test_postgresql)
|
49
|
+
run_without_aborting(*tasks)
|
50
|
+
end
|
35
51
|
|
36
52
|
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
|
37
53
|
Rake::TestTask.new("test_#{adapter}") { |t|
|
@@ -53,8 +69,8 @@ end
|
|
53
69
|
namespace :mysql do
|
54
70
|
desc 'Build the MySQL test databases'
|
55
71
|
task :build_databases do
|
56
|
-
%x(
|
57
|
-
%x(
|
72
|
+
%x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
|
73
|
+
%x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
|
58
74
|
end
|
59
75
|
|
60
76
|
desc 'Drop the MySQL test databases'
|
@@ -75,8 +91,8 @@ task :rebuild_mysql_databases => 'mysql:rebuild_databases'
|
|
75
91
|
namespace :postgresql do
|
76
92
|
desc 'Build the PostgreSQL test databases'
|
77
93
|
task :build_databases do
|
78
|
-
%x( createdb activerecord_unittest )
|
79
|
-
%x( createdb activerecord_unittest2 )
|
94
|
+
%x( createdb -E UTF8 activerecord_unittest )
|
95
|
+
%x( createdb -E UTF8 activerecord_unittest2 )
|
80
96
|
end
|
81
97
|
|
82
98
|
desc 'Drop the PostgreSQL test databases'
|
@@ -176,7 +192,7 @@ spec = Gem::Specification.new do |s|
|
|
176
192
|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
177
193
|
end
|
178
194
|
|
179
|
-
s.add_dependency('activesupport', '= 2.3.
|
195
|
+
s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD)
|
180
196
|
|
181
197
|
s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
|
182
198
|
s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
|
@@ -243,11 +259,12 @@ end
|
|
243
259
|
|
244
260
|
desc "Publish the release files to RubyForge."
|
245
261
|
task :release => [ :package ] do
|
246
|
-
|
262
|
+
require 'rubyforge'
|
263
|
+
require 'rake/contrib/rubyforgepublisher'
|
247
264
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
end
|
265
|
+
packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
|
266
|
+
|
267
|
+
rubyforge = RubyForge.new
|
268
|
+
rubyforge.login
|
269
|
+
rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
|
270
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
#!/usr/bin/env ruby -KU
|
2
|
+
|
3
|
+
TIMES = (ENV['N'] || 10000).to_i
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
gem 'addressable', '~>2.0'
|
7
|
+
gem 'faker', '~>0.3.1'
|
8
|
+
gem 'rbench', '~>0.2.3'
|
9
|
+
require 'addressable/uri'
|
10
|
+
require 'faker'
|
11
|
+
require 'rbench'
|
12
|
+
|
13
|
+
__DIR__ = File.dirname(__FILE__)
|
14
|
+
$:.unshift "#{__DIR__}/../lib"
|
15
|
+
require 'active_record'
|
16
|
+
|
17
|
+
conn = { :adapter => 'mysql',
|
18
|
+
:database => 'activerecord_unittest',
|
19
|
+
:username => 'rails', :password => '',
|
20
|
+
:encoding => 'utf8' }
|
21
|
+
|
22
|
+
conn[:socket] = Pathname.glob(%w[
|
23
|
+
/opt/local/var/run/mysql5/mysqld.sock
|
24
|
+
/tmp/mysqld.sock
|
25
|
+
/tmp/mysql.sock
|
26
|
+
/var/mysql/mysql.sock
|
27
|
+
/var/run/mysqld/mysqld.sock
|
28
|
+
]).find { |path| path.socket? }
|
29
|
+
|
30
|
+
ActiveRecord::Base.establish_connection(conn)
|
31
|
+
|
32
|
+
class User < ActiveRecord::Base
|
33
|
+
connection.create_table :users, :force => true do |t|
|
34
|
+
t.string :name, :email
|
35
|
+
t.timestamps
|
36
|
+
end
|
37
|
+
|
38
|
+
has_many :exhibits
|
39
|
+
end
|
40
|
+
|
41
|
+
class Exhibit < ActiveRecord::Base
|
42
|
+
connection.create_table :exhibits, :force => true do |t|
|
43
|
+
t.belongs_to :user
|
44
|
+
t.string :name
|
45
|
+
t.text :notes
|
46
|
+
t.timestamps
|
47
|
+
end
|
48
|
+
|
49
|
+
belongs_to :user
|
50
|
+
|
51
|
+
def look; attributes end
|
52
|
+
def feel; look; user.name end
|
53
|
+
|
54
|
+
def self.look(exhibits) exhibits.each { |e| e.look } end
|
55
|
+
def self.feel(exhibits) exhibits.each { |e| e.feel } end
|
56
|
+
end
|
57
|
+
|
58
|
+
sqlfile = "#{__DIR__}/performance.sql"
|
59
|
+
|
60
|
+
if File.exists?(sqlfile)
|
61
|
+
mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
|
62
|
+
`#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
|
63
|
+
else
|
64
|
+
puts 'Generating data...'
|
65
|
+
|
66
|
+
# pre-compute the insert statements and fake data compilation,
|
67
|
+
# so the benchmarks below show the actual runtime for the execute
|
68
|
+
# method, minus the setup steps
|
69
|
+
|
70
|
+
# Using the same paragraph for all exhibits because it is very slow
|
71
|
+
# to generate unique paragraphs for all exhibits.
|
72
|
+
notes = Faker::Lorem.paragraphs.join($/)
|
73
|
+
today = Date.today
|
74
|
+
|
75
|
+
puts 'Inserting 10,000 users and exhibits...'
|
76
|
+
10_000.times do
|
77
|
+
user = User.create(
|
78
|
+
:created_at => today,
|
79
|
+
:name => Faker::Name.name,
|
80
|
+
:email => Faker::Internet.email
|
81
|
+
)
|
82
|
+
|
83
|
+
Exhibit.create(
|
84
|
+
:created_at => today,
|
85
|
+
:name => Faker::Company.name,
|
86
|
+
:user => user,
|
87
|
+
:notes => notes
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
mysqldump_bin = %w[mysqldump mysqldump5].select { |bin| `which #{bin}`.length > 0 }
|
92
|
+
`#{mysqldump_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} exhibits users > #{sqlfile}`
|
93
|
+
end
|
94
|
+
|
95
|
+
RBench.run(TIMES) do
|
96
|
+
column :times
|
97
|
+
column :ar
|
98
|
+
|
99
|
+
report 'Model#id', (TIMES * 100).ceil do
|
100
|
+
ar_obj = Exhibit.find(1)
|
101
|
+
|
102
|
+
ar { ar_obj.id }
|
103
|
+
end
|
104
|
+
|
105
|
+
report 'Model.new (instantiation)' do
|
106
|
+
ar { Exhibit.new }
|
107
|
+
end
|
108
|
+
|
109
|
+
report 'Model.new (setting attributes)' do
|
110
|
+
attrs = { :name => 'sam' }
|
111
|
+
ar { Exhibit.new(attrs) }
|
112
|
+
end
|
113
|
+
|
114
|
+
report 'Model.first' do
|
115
|
+
ar { Exhibit.first.look }
|
116
|
+
end
|
117
|
+
|
118
|
+
report 'Model.all limit(100)', (TIMES / 10).ceil do
|
119
|
+
ar { Exhibit.look Exhibit.all(:limit => 100) }
|
120
|
+
end
|
121
|
+
|
122
|
+
report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do
|
123
|
+
ar { Exhibit.feel Exhibit.all(:limit => 100, :include => :user) }
|
124
|
+
end
|
125
|
+
|
126
|
+
report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
|
127
|
+
ar { Exhibit.look Exhibit.all(:limit => 10000) }
|
128
|
+
end
|
129
|
+
|
130
|
+
exhibit = {
|
131
|
+
:name => Faker::Company.name,
|
132
|
+
:notes => Faker::Lorem.paragraphs.join($/),
|
133
|
+
:created_at => Date.today
|
134
|
+
}
|
135
|
+
|
136
|
+
report 'Model.create' do
|
137
|
+
ar { Exhibit.create(exhibit) }
|
138
|
+
end
|
139
|
+
|
140
|
+
report 'Resource#attributes=' do
|
141
|
+
attrs_first = { :name => 'sam' }
|
142
|
+
attrs_second = { :name => 'tom' }
|
143
|
+
ar { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
|
144
|
+
end
|
145
|
+
|
146
|
+
report 'Resource#update' do
|
147
|
+
ar { Exhibit.first.update_attributes(:name => 'bob') }
|
148
|
+
end
|
149
|
+
|
150
|
+
report 'Resource#destroy' do
|
151
|
+
ar { Exhibit.first.destroy }
|
152
|
+
end
|
153
|
+
|
154
|
+
report 'Model.transaction' do
|
155
|
+
ar { Exhibit.transaction { Exhibit.new } }
|
156
|
+
end
|
157
|
+
|
158
|
+
summary 'Total'
|
159
|
+
end
|
160
|
+
|
161
|
+
ActiveRecord::Migration.drop_table "exhibits"
|
162
|
+
ActiveRecord::Migration.drop_table "users"
|
@@ -34,11 +34,13 @@ module ActiveRecord
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
class
|
37
|
+
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
|
38
38
|
def initialize(owner, reflection)
|
39
39
|
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
|
40
40
|
end
|
41
41
|
end
|
42
|
+
HasManyThroughCantAssociateThroughHasManyReflection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection', 'ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection')
|
43
|
+
|
42
44
|
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
|
43
45
|
def initialize(owner, reflection)
|
44
46
|
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
|
@@ -410,6 +412,32 @@ module ActiveRecord
|
|
410
412
|
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
|
411
413
|
# @firm.invoices # selects all invoices by going through the Client join model.
|
412
414
|
#
|
415
|
+
# Similarly you can go through a +has_one+ association on the join model:
|
416
|
+
#
|
417
|
+
# class Group < ActiveRecord::Base
|
418
|
+
# has_many :users
|
419
|
+
# has_many :avatars, :through => :users
|
420
|
+
# end
|
421
|
+
#
|
422
|
+
# class User < ActiveRecord::Base
|
423
|
+
# belongs_to :group
|
424
|
+
# has_one :avatar
|
425
|
+
# end
|
426
|
+
#
|
427
|
+
# class Avatar < ActiveRecord::Base
|
428
|
+
# belongs_to :user
|
429
|
+
# end
|
430
|
+
#
|
431
|
+
# @group = Group.first
|
432
|
+
# @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
|
433
|
+
# @group.avatars # selects all avatars by going through the User join model.
|
434
|
+
#
|
435
|
+
# An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
|
436
|
+
# *read-only*. For example, the following would not work following the previous example:
|
437
|
+
#
|
438
|
+
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
|
439
|
+
# @group.avatars.delete(@group.avatars.last) # so would this
|
440
|
+
#
|
413
441
|
# === Polymorphic Associations
|
414
442
|
#
|
415
443
|
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
|
@@ -759,7 +787,7 @@ module ActiveRecord
|
|
759
787
|
# [:through]
|
760
788
|
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
|
761
789
|
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
|
762
|
-
# or <tt>has_many</tt> association on the join model.
|
790
|
+
# <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
|
763
791
|
# [:source]
|
764
792
|
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
|
765
793
|
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
|
@@ -1241,7 +1269,11 @@ module ActiveRecord
|
|
1241
1269
|
|
1242
1270
|
if association_proxy_class == HasOneThroughAssociation
|
1243
1271
|
association.create_through_record(new_value)
|
1244
|
-
|
1272
|
+
if new_record?
|
1273
|
+
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
1274
|
+
else
|
1275
|
+
self.send(reflection.name, new_value)
|
1276
|
+
end
|
1245
1277
|
else
|
1246
1278
|
association.replace(new_value)
|
1247
1279
|
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
@@ -1293,7 +1325,7 @@ module ActiveRecord
|
|
1293
1325
|
|
1294
1326
|
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
1295
1327
|
ids = (new_value || []).reject { |nid| nid.blank? }
|
1296
|
-
send("#{reflection.name}=", reflection.
|
1328
|
+
send("#{reflection.name}=", reflection.klass.find(ids))
|
1297
1329
|
end
|
1298
1330
|
end
|
1299
1331
|
end
|
@@ -1838,7 +1870,7 @@ module ActiveRecord
|
|
1838
1870
|
descendant
|
1839
1871
|
end.flatten.compact
|
1840
1872
|
|
1841
|
-
remove_duplicate_results!(reflection.
|
1873
|
+
remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
|
1842
1874
|
end
|
1843
1875
|
end
|
1844
1876
|
end
|
@@ -208,6 +208,7 @@ module ActiveRecord
|
|
208
208
|
# Note that this method will _always_ remove records from the database
|
209
209
|
# ignoring the +:dependent+ option.
|
210
210
|
def destroy(*records)
|
211
|
+
records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
|
211
212
|
remove_records(records) do |records, old_records|
|
212
213
|
old_records.each { |record| record.destroy }
|
213
214
|
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
4
|
+
def initialize(owner, reflection)
|
5
|
+
super
|
6
|
+
@primary_key_list = {}
|
7
|
+
end
|
8
|
+
|
4
9
|
def create(attributes = {})
|
5
10
|
create_record(attributes) { |record| insert_record(record) }
|
6
11
|
end
|
@@ -17,6 +22,12 @@ module ActiveRecord
|
|
17
22
|
@reflection.reset_column_information
|
18
23
|
end
|
19
24
|
|
25
|
+
def has_primary_key?
|
26
|
+
return @has_primary_key unless @has_primary_key.nil?
|
27
|
+
@has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
|
28
|
+
ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
|
29
|
+
end
|
30
|
+
|
20
31
|
protected
|
21
32
|
def construct_find_options!(options)
|
22
33
|
options[:joins] = @join_sql
|
@@ -29,6 +40,11 @@ module ActiveRecord
|
|
29
40
|
end
|
30
41
|
|
31
42
|
def insert_record(record, force = true, validate = true)
|
43
|
+
if has_primary_key?
|
44
|
+
raise ActiveRecord::ConfigurationError,
|
45
|
+
"Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
|
46
|
+
end
|
47
|
+
|
32
48
|
if record.new_record?
|
33
49
|
if force
|
34
50
|
record.save!
|
@@ -74,6 +74,7 @@ module ActiveRecord
|
|
74
74
|
"#{@reflection.primary_key_name} = NULL",
|
75
75
|
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
76
76
|
)
|
77
|
+
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
@@ -17,7 +17,17 @@ module ActiveRecord
|
|
17
17
|
|
18
18
|
def create(attrs = nil)
|
19
19
|
transaction do
|
20
|
-
|
20
|
+
object = if attrs
|
21
|
+
@reflection.klass.send(:with_scope, :create => attrs) {
|
22
|
+
@reflection.create_association
|
23
|
+
}
|
24
|
+
else
|
25
|
+
@reflection.create_association
|
26
|
+
end
|
27
|
+
raise_on_type_mismatch(object)
|
28
|
+
add_record_to_target_with_callbacks(object) do |r|
|
29
|
+
insert_record(object, false)
|
30
|
+
end
|
21
31
|
object
|
22
32
|
end
|
23
33
|
end
|
@@ -44,7 +54,7 @@ module ActiveRecord
|
|
44
54
|
options[:select] = construct_select(options[:select])
|
45
55
|
options[:from] ||= construct_from
|
46
56
|
options[:joins] = construct_joins(options[:joins])
|
47
|
-
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
|
57
|
+
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
|
48
58
|
end
|
49
59
|
|
50
60
|
def insert_record(record, force = true, validate = true)
|
@@ -96,7 +106,7 @@ module ActiveRecord
|
|
96
106
|
# Construct attributes for :through pointing to owner and associate.
|
97
107
|
def construct_join_attributes(associate)
|
98
108
|
# TODO: revist this to allow it for deletion, supposing dependent option is supported
|
99
|
-
raise ActiveRecord::
|
109
|
+
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
|
100
110
|
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
101
111
|
if @reflection.options[:source_type]
|
102
112
|
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
|
@@ -9,8 +9,14 @@ module ActiveRecord
|
|
9
9
|
|
10
10
|
if current_object
|
11
11
|
new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
|
12
|
-
|
13
|
-
@owner.
|
12
|
+
elsif new_value
|
13
|
+
if @owner.new_record?
|
14
|
+
self.target = new_value
|
15
|
+
through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
|
16
|
+
through_association.build(construct_join_attributes(new_value))
|
17
|
+
else
|
18
|
+
@owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
|
19
|
+
end
|
14
20
|
end
|
15
21
|
end
|
16
22
|
|