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.

Files changed (70) hide show
  1. data/CHANGELOG +8 -1
  2. data/Rakefile +32 -15
  3. data/examples/performance.rb +162 -0
  4. data/lib/active_record/associations.rb +37 -5
  5. data/lib/active_record/associations/association_collection.rb +1 -0
  6. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +16 -0
  7. data/lib/active_record/associations/has_many_association.rb +1 -0
  8. data/lib/active_record/associations/has_many_through_association.rb +13 -3
  9. data/lib/active_record/associations/has_one_through_association.rb +8 -2
  10. data/lib/active_record/autosave_association.rb +4 -3
  11. data/lib/active_record/base.rb +18 -10
  12. data/lib/active_record/calculations.rb +2 -0
  13. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -2
  14. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +2 -2
  15. data/lib/active_record/connection_adapters/abstract_adapter.rb +7 -0
  16. data/lib/active_record/connection_adapters/mysql_adapter.rb +17 -8
  17. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -13
  18. data/lib/active_record/connection_adapters/sqlite_adapter.rb +12 -0
  19. data/lib/active_record/dirty.rb +1 -1
  20. data/lib/active_record/fixtures.rb +9 -7
  21. data/lib/active_record/i18n_interpolation_deprecation.rb +1 -1
  22. data/lib/active_record/locale/en.yml +4 -0
  23. data/lib/active_record/named_scope.rb +1 -6
  24. data/lib/active_record/reflection.rb +1 -1
  25. data/lib/active_record/schema_dumper.rb +1 -2
  26. data/lib/active_record/serializers/json_serializer.rb +5 -3
  27. data/lib/active_record/serializers/xml_serializer.rb +6 -2
  28. data/lib/active_record/validations.rb +148 -79
  29. data/lib/active_record/version.rb +1 -1
  30. data/test/cases/adapter_test.rb +12 -0
  31. data/test/cases/associations/belongs_to_associations_test.rb +0 -18
  32. data/test/cases/associations/eager_load_nested_include_test.rb +5 -5
  33. data/test/cases/associations/habtm_join_table_test.rb +56 -0
  34. data/test/cases/associations/has_many_associations_test.rb +56 -2
  35. data/test/cases/associations/has_many_through_associations_test.rb +46 -1
  36. data/test/cases/associations/has_one_through_associations_test.rb +10 -0
  37. data/test/cases/associations/join_model_test.rb +4 -4
  38. data/test/cases/base_test.rb +49 -4
  39. data/test/cases/calculations_test.rb +6 -0
  40. data/test/cases/column_definition_test.rb +34 -0
  41. data/test/cases/dirty_test.rb +10 -0
  42. data/test/cases/finder_test.rb +15 -50
  43. data/test/cases/fixtures_test.rb +1 -1
  44. data/test/cases/i18n_test.rb +5 -0
  45. data/test/cases/method_scoping_test.rb +1 -1
  46. data/test/cases/migration_test.rb +39 -11
  47. data/test/cases/modules_test.rb +42 -0
  48. data/test/cases/named_scope_test.rb +6 -4
  49. data/test/cases/pk_test.rb +18 -0
  50. data/test/cases/reflection_test.rb +2 -2
  51. data/test/cases/schema_dumper_test.rb +19 -1
  52. data/test/cases/validations_i18n_test.rb +656 -624
  53. data/test/cases/validations_test.rb +12 -2
  54. data/test/cases/xml_serialization_test.rb +20 -0
  55. data/test/fixtures/fixture_database.sqlite +0 -0
  56. data/test/fixtures/fixture_database.sqlite3 +0 -0
  57. data/test/fixtures/fixture_database_2.sqlite +0 -0
  58. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  59. data/test/fixtures/posts.yml +3 -0
  60. data/test/models/author.rb +1 -0
  61. data/test/models/comment.rb +5 -1
  62. data/test/models/company.rb +2 -0
  63. data/test/models/company_in_module.rb +1 -1
  64. data/test/models/contract.rb +5 -0
  65. data/test/models/organization.rb +2 -0
  66. data/test/models/topic.rb +0 -2
  67. data/test/schema/postgresql_specific_schema.rb +13 -2
  68. data/test/schema/schema.rb +4 -0
  69. metadata +12 -54
  70. data/test/debug.log +0 -415
data/CHANGELOG CHANGED
@@ -1,4 +1,11 @@
1
- *2.3.3 (July 20, 2009)*
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 => defined?(JRUBY_VERSION) ?
33
- %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
34
- %w(test_mysql test_sqlite3 test_postgresql)
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( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest )
57
- %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 )
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.3' + PKG_BUILD)
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
- `rubyforge login`
262
+ require 'rubyforge'
263
+ require 'rake/contrib/rubyforgepublisher'
247
264
 
248
- for ext in %w( gem tgz zip )
249
- release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
250
- puts release_command
251
- system(release_command)
252
- end
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 HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
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
- self.send(reflection.name, new_value)
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.class_name.constantize.find(ids))
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.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
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
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
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::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
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
- else
13
- @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value
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