4

I have models

class Question < ActiveRecord::Base
  WEIGHTS = %w(medium hard easy)

  belongs_to :test
  has_many :answers, :dependent => :destroy
  has_many :testing_questions
end

class Testing < ActiveRecord::Base
  belongs_to :student, :foreign_key => 'user_id'
  belongs_to :subtest
  has_many :testing_questions, :dependent => :destroy
  has_many :questions, :through => :testing_questions
end

So when I try to bind questions to testing on it's creation:

>> questions = Question.all
...
>> questions.count
=> 3
>> testing = Testing.create(:user_id => 3, :subtest_id => 1, :questions => questions)
  Testing Columns (0.9ms)   SHOW FIELDS FROM `testings`                              
  SQL (0.1ms)   BEGIN                                                                
  SQL (0.1ms)   COMMIT                                                               
  SQL (0.1ms)   BEGIN                                                                
  Testing Create (0.3ms)   INSERT INTO `testings` (`created_at`, `updated_at`, `user_id`, `subtest_id`) VALUES('2010-05-18 00:53:05', '2010-05-18 00:53:05', 3, 1)                                                                                                                                                        
  TestingQuestion Columns (0.9ms)   SHOW FIELDS FROM `testing_questions`                                                                                     
  TestingQuestion Create (0.3ms)   INSERT INTO `testing_questions` (`question_id`, `created_at`, `updated_at`, `testing_id`) VALUES(1, '2010-05-18 00:53:05', '2010-05-18 00:53:05', 31)                                                                                                                                  
  TestingQuestion Create (0.4ms)   INSERT INTO `testing_questions` (`question_id`, `created_at`, `updated_at`, `testing_id`) VALUES(2, '2010-05-18 00:53:05', '2010-05-18 00:53:05', 31)                                                                                                                                  
  TestingQuestion Create (0.3ms)   INSERT INTO `testing_questions` (`question_id`, `created_at`, `updated_at`, `testing_id`) VALUES(3, '2010-05-18 00:53:05', '2010-05-18 00:53:05', 31)                                                                                                                                  
  TestingQuestion Create (0.3ms)   INSERT INTO `testing_questions` (`question_id`, `created_at`, `updated_at`, `testing_id`) VALUES(1, '2010-05-18 00:53:05', '2010-05-18 00:53:05', 31)                                                                                                                                  
  TestingQuestion Create (0.3ms)   INSERT INTO `testing_questions` (`question_id`, `created_at`, `updated_at`, `testing_id`) VALUES(2, '2010-05-18 00:53:05', '2010-05-18 00:53:05', 31)                                                                                                                                  
  TestingQuestion Create (0.3ms)   INSERT INTO `testing_questions` (`question_id`, `created_at`, `updated_at`, `testing_id`) VALUES(3, '2010-05-18 00:53:05', '2010-05-18 00:53:05', 31)                                                                                                                                  
  SQL (90.2ms)   COMMIT                                                                                                                                      
=> #<Testing id: 31, subtest_id: 1, user_id: 3, created_at: "2010-05-18 00:53:05", updated_at: "2010-05-18 00:53:05">

There are 6 SQL queries and 6 records in testing_questions are created. Why?

Antiarchitect
  • 1,852
  • 2
  • 16
  • 19

5 Answers5

2

I have created a very simple example that handles your example:

class Question < ActiveRecord::Base
  has_many :testing_questions
end

class Testing < ActiveRecord::Base
  has_many :testing_questions
  has_many :questions, :through => :testing_questions
end

class TestingQuestion < ActiveRecord::Base
  belongs_to :question
  belongs_to :testing
end

.. and then i can just do the following, and no duplicate records are created:

Loading development environment (Rails 2.3.5)
>> q1 = Question.new
=> #<Question id: nil, title: nil, ask: nil, created_at: nil, updated_at: nil>
>> q1.title = "Dit is de eerste vraag"
=> "Dit is de eerste vraag"
>> q2 = Question.new
=> #<Question id: nil, title: nil, ask: nil, created_at: nil, updated_at: nil>
>> q2.title = "Dit is de tweede vraag"
=> "Dit is de tweede vraag"
>> q1.save
=> true
>> q2.save
=> true
>> tt = Testing.new
=> #<Testing id: nil, name: nil, description: nil, action: nil, created_at: nil, updated_at: nil>
>> tt.questions
=> []
>> tt.name = "Test1"
=> "Test1"
>> tt.questions << q1
=> [#<Question id: 1, title: "Dit is de eerste vraag", ask: nil, created_at:   "2010-05-18 19:40:54", updated_at: "2010-05-18 19:40:54">]
>> tt.questions << q2
=> [#<Question id: 1, title: "Dit is de eerste vraag", ask: nil, created_at: "2010-05-18 19:40:54", updated_at: "2010-05-18 19:40:54">, #<Question id: 2, title: "Dit is de tweede vraag", ask: nil, created_at: "2010-05-18 19:40:59", updated_at: "2010-05-18 19:40:59">]
>> tt.testing_questions
=> []
>> tt.save
=> true
>> tt.testing_questions
=> [#<TestingQuestion id: 1, question_id: 1, testing_id: 1, extra_info: nil, created_at: "2010-05-18 19:41:43", updated_at: "2010-05-18 19:41:43">, #<TestingQuestion id: 2, question_id: 2, testing_id: 1, extra_info: nil, created_at: "2010-05-18 19:41:43", updated_at: "2010-05-18 19:41:43">]
>>

Actually that is completely the same as you have, except for the TestingQuestion (which you didn't show). Does that help?

nathanvda
  • 49,707
  • 13
  • 117
  • 139
  • I see that It's all ok in your example. TestingQuestion model exactly the same as yours... But my way to bind associated records to testings different. I try to bind them on creation of Testing – Antiarchitect May 19 '10 at 06:25
  • Yes but that should not make a difference, it is only at save that the TestingQuestion's get created. I assume in your example you write Testing.create, you still have to call save explicitly. Right? I need to. I am testing against Rails 2.3.5. Also: i see that my TestingQuestion has an id, which should not be needed. – nathanvda May 19 '10 at 07:17
0

Your questions array contains 3 items. When you create a Testing instance and specify to create it with :questions => questions it is going to add them to the questions relationship, but because this relationship is through another one, they must be added to the other one first, resulting in them being inserted twice. Typically, you would have some other model representing the join table, so that you have 3 records inserted into the Questions table and also 3 into the TestingQuestions join table (has_many :through).

Looks to me like its an issue in how you have defined your TestingQuestions model, which you have not shown. For example, is it pointing to the same table as the Questions model?

Matt Connolly
  • 9,757
  • 2
  • 65
  • 61
0

A first issue could be the naming of your join table

testing_questions

Rails expects the join table’s name to be a concatenation of the two table names in alphabetical order

question_testings
tommasop
  • 18,495
  • 2
  • 40
  • 51
  • I know, but there is has_many :testing_questions, :dependent => :destroy and has_many :questions, :through => :testing_questions I think it's enough for determining the intermediate table. – Antiarchitect May 18 '10 at 06:36
  • I think nonetheless you have to use set_table_name to use legacy table naming – tommasop May 18 '10 at 07:55
  • That is not true: the table-name is derived from the modelname. If the modelname is TestingQuestion, the table-name will be testing_questions. Using the `:through` you explicitly specify the name of the model of the join-table. – nathanvda May 18 '10 at 08:14
0

Looking at the table structure it is a has_and_belongs_to_many relations between the tables, there is no need to use has_many and through option here as your join table will not be having a model( no extra columns other than ids are present). No need to create a model for join table.

So, I suggest to change your models like this


class Question < ActiveRecord::Base
WEIGHTS = %w(medium hard easy) belongs_to :test
has_many :answers, :dependent => :destroy
has_and_belongs_to_many :question_testings end

class Testing < ActiveRecord::Base
belongs_to :student, :foreign_key => 'user_id' belongs_to :subtest has_and_belongs_to_many :question_testings end

and please change your table name as question_testings.

Please have a look at this link for more info http://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many

Vamsi
  • 248
  • 1
  • 9
  • I do not want to use obsolete has_and_belongs_to_many, I want to use has_many :through. Changing scheme or something like this can be a problem solution, but the problem is solved already, now I want to know why Rails do so. – Antiarchitect May 18 '10 at 13:14
  • can you tell me which gem or plugin you are using for displaying the sql queries in the console?And could you share your solution as well? – Vamsi May 18 '10 at 13:29
  • I do not remember exactly, but try to add this file to your home directory: http://gist.github.com/405733 – Antiarchitect May 18 '10 at 23:50
  • @antiarchitect: Logs in console can be done by adding this code to the ~/.irbrc file. All projects would have it. (the formatting cannot be controlled in comments, so I am adding semi-colons) if ENV.include?('RAILS_ENV') && !Object.const_defined? ; ('RAILS_DEFAULT_LOGGER') ; require 'logger' ; RAILS_DEFAULT_LOGGER = Logger.new(STDOUT) ; end ; – Ram on Rails May 22 '10 at 01:17
0

Without looking at the Rails source or trying around, I'd suggest to try either removing the "has_many :testing_questions" from the Question class or add a has_many ... :through there. Just now Rails just has a relation to the join table, but not to the "real" target from that side.

The name of the join table shouldn't cause any problems here.

averell
  • 3,762
  • 2
  • 21
  • 28