0

I have the following models:

class Audit < ActiveRecord::Base
  has_one :country, :through => :another_model
  has_and_belongs_to_many :questions
end

class Question < ActiveRecord::Base
  has_and_belongs_to_many :audits
  has_many :details # one detail for each country [england, wales, scotland etc]

  has_one :detail, :conditions?
  # i want it to have one detail dynamically depending on the audit
end

class Detail < ActiveRecord::Base
  belongs_to :country
  belongs_to :question
end

The problem is regarding the question model. Say for example I have Audit A with Questions 1, 2 and 3 and Audit A is based in the country England. When viewing Audit A I want to display the 3 Questions; no problem. @audit.questions.each do |q| etc

Now let's explain the question details. Let's say I have Detail X, Y and Z. Detail X, Y and Z are all associated with Question 1. However, Detail X is based in the country England, Detail Y in Wales and Detail Z in Scotland. At the moment I have all details pulled out of the database and then in the view/model I get the current audit's country and subsequently get the detail based off that.

Is there a way to say something along the lines of:

class Question < ActiveRecord::Base
  has_and_belongs_to_many :audits
  has_one :detail, :conditions -> {country_id = current_audit_being_viewed.country.id}
end

I'm also aware I could just be thinking about the relations in the wrong way so please feel free to just tell me a better way all together to do what I'm trying to achieve.

bensmithbwd
  • 343
  • 3
  • 12
  • 1
    I've learned from experience to almost never use has_and_belongs_to_many. I've experienced a lot of issues with this when I was first getting started in rails. I'd recommend considering dealing directly with a join model and join table. – pixelearth Jun 29 '14 at 08:01
  • It seems a bit overkill to have something in between an audit and it's questions? What model would you suggest? As much as I appreciate the comment and advice from a more experienced user, surely just adding a :through association wouldn't actually do anything to solve my problem unless I'm missing something obvious? – bensmithbwd Jun 29 '14 at 17:05

1 Answers1

0

just to clarify things. In your post you said:

class Question < ActiveRecord::Base
  has_and_belongs_to_many :audits
  has_one :detail    
end

and

class Detail < ActiveRecord::Base
  belongs_to :country
  belongs_to :question
end

You also mentioned "Now let's explain the question details. Let's say I have Detail X, Y and Z. Detail X, Y and Z are all associated with Question 1". Which says that one Question can have many Details.

But based on the model declarations you made above, Question only 'has_one' Detail. Please verify. Thanks!

For the problem you have, you can try using model 'scope':

class Detail < ActiveRecord::Base
  belongs_to :country
  belongs_to :question
  scope :with_country_id, -> (country_id) { where( country_id: country_id ) }
end

So assuming the Question has_many Detail(s), then you can: question.details.with_country_id( current_audit_being_viewed.country_id )

Also, try using has_many :through. Could make things a lot easier to design (and prettier). ;)

Hope that helps! :)

Cheers!

koyz
  • 154
  • 10
  • I have updated my question to make it a bit clearer as a question does indeed have many details (it has one for each country [eng, wales, scotland etc]) - I want to only show one detail dynamically depending on the current audit being viewed. I'll investigate the scope idea and let you know how that goes. Thanks. – bensmithbwd Jun 29 '14 at 16:59
  • The scope has worked but it produces a ton of select queries even though the details are eager loaded in the controller. I also couldn't quite get your syntax to work the `->` in particular caused errors: `syntax error, unexpected tLPAREN_ARG, expecting keyword_do_LAMBDA or tLAMBEG`. I eventually went with: `scope :with_country_id, lambda {|country_id = nil| where(:country_id => country_id) }` which I presume could be causing the select queries? The way I got round the multiple select queries was just to get all details has_many style and pick out the correct country in the view. – bensmithbwd Jun 29 '14 at 17:45
  • I also don't understand how a :through association can solve my problems - I would gladly use one with no issues at all but I must be missing something obvious as to how it would help? – bensmithbwd Jun 29 '14 at 17:47
  • scope :with_country_id, -> { .... } is the same as using lambda. not sure why it didnt work for you. Can you paste the content of the controller that loads the details? thanks. – koyz Jun 29 '14 at 17:56
  • It looks like it was just because I put the scope below the belongs_to declaration - it worked fine once I moved it above. Using that method I still get a ton of queries for each question loaded on the page though. I decided to live with the has_many details and use a method on the question as follows: `def get_detail_from_country(country_id)` which simply has the one line: `details.select{|d| d.country_id == country_id}[0]`. Then in the view I can do: `question.get_detail_from_country(@audit.country.id).ATTRIBUTE`. That allows me to eager load the details in the audit controller. – bensmithbwd Jun 29 '14 at 19:12
  • 1
    cool. Can you post the previous content of the controller which generated a ton of queries? that got me curious. `details.select{|d| d.country_id == country_id}[0]` will just do a SELECT "details".* FROM "details" then converts the result to an array and do an Array#select and returns the objects where the specified condition returns true. Try using `details.where(country_id: country_id).first` for a more accurate select query. the 'with_country_id' scope placed in the Detail model mentioned earlier can be used as well: `details.with_country_id( country_id )`. – koyz Jun 29 '14 at 19:52
  • Thanks, `details.where(country_id: country_id).first` looks a million times better than how I had it! Essentially the code that caused the mass queries was the scope: `scope :with_country_id, -> (country_id) { where( country_id: country_id ) }` (literally as you have it in your answer). I have in my controller an eager load like so: `Audit.includes(:audit_questions => :audit_question_details).find(params[:id])` and for some reason the scope just ignores it I presume? – bensmithbwd Jun 30 '14 at 02:31