What's the easiest way to find the 'middle' association in has_many :through association?
What I mean is, I have 3 models:
class VoiceActor < ApplicationRecord
has_many :characters
has_many :shows, through: :characters
end
class Character < ApplicationRecord
has_and_belongs_to_many :shows
belongs_to :voice_actor, optional: true
end
class Show < ApplicationRecord
has_and_belongs_to_many :characters
has_many :voice_actors, through: :characters
def character(voice_actor_id)
self.characters.find_by(voice_actor_id: voice_actor_id)
end
end
What I want to is, on a view page for a voice actor to display all shows they appear in. and next to each of those shows, show what character is played by them
I did get it to work, but I'm unsure if my solution is ok? Seems bit heavy on the view still. What I currently do is I pass #voice_actor to the view and #shows (which is just #voice_actor.shows) and use the character method defined in my show model as seen above
and then do
Appears in:
<ul>
<% #shows.each do |show| %>
<li>
<%= link_to show.name, show %> as <%= link_to show.character(#voice_actor.id).name, show.character(#voice_actor.id) %>
</li>
<% end %>
</ul>
would you say that's an okay solution or is there a more elegant way about it? I'm especially worried that I might be doing some bad practice regarding logic in the view
I would model the domain differently and avoid has_and_belongs_to_many. Instead create an actual model that represents the job an actor does in a show:
class Casting < ApplicationRecord
belongs_to :character
belongs_to :voice_actor
belongs_to :show
end
class VoiceActor < ApplicationRecord
has_many :castings
has_many :shows, through: :castings
has_many :characters, through: :castings
end
class Character < ApplicationRecord
has_many :castings
has_many :shows, though: :castings
has_many :voice_actors, though: :castings
end
class Show < ApplicationRecord
has_many :castings
has_many :characters, through: :castings
has_many :voice_actors, through: :castings
end
This provides the flexibilty so that a character (say for example Batman) can be played by different actors and occur in different shows.
Then when you want to list the roles that an actor has played you would do it looping through this model:
Appears in:
<ul>
<% #actor.castings.each do |casting| %>
<li>
<%= link_to(casting.show.name, show) %> as <%= link_to(casting.character.name, casting.character) %>
</li>
<% end %>
</ul>
Unlike your solution which causes a N+1 query this can be eager loaded:
#actor = Actor.eager_load(castings: [:show, :character])
.find(params[:id])
Related
I'm having trouble figuring out how to access the "grandchild" of an object in rails. Here's my example:
I have a product model that can have options (such as "size" or "color") and each option can have choices (such as "small, medium, large" or "red, green, blue").
class Product < ApplicationRecord
has_many :options
end
class Option < ApplicationRecord
belongs_to :product
has_many :choices
end
class Choice < ApplicationRecord
belongs_to :option
end
I'd like to access the "choices" from the Product controller, but I can't figure out how to chain them together.
For instance, Product.first.options returns all of the associated options that belong to the Product, but I'd like to do something like Product.first.options.choices.
That returns NoMethodError (undefined method 'choices')
Is this even possible with active record or do I need to create another association that connects Product and Choice.
#engineersmnky came through with the comment that solved this.
Here's what I added to get this working.
has_many :choices, through: :options was added to the Product model:
class Product < ApplicationRecord
has_many :options
has_many :choices, through: :options
end
And then here was the simple_form code that gave me the user experience I was looking for in the product#edit view:
<% #product.options.each do |option| %>
<div class='form-row'>
<div class='col'>
<%= f.association :choices,
collection: option.choices,
as: :radio_buttons,
label: option.name %>
</div><!-- /.col -->
</div><!-- /.row -->
<% end %>
When I developed my first sandbox application, I wanted to get some records for relational table.
User.rb:
class User < ActiveRecord::Base
#has many followed articles
has_many :follow_articles
And FollowArticle model:
class FollowArticle < ActiveRecord::Base
belongs_to :user
belongs_to :article
end
And Article model:
class Item < ActiveRecord::Base
has_many :follow_articles
end
I want to get all followed articles of a user so in my controller I have:
#articles = current_user.follow_articles
which gave me:
ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_FollowArticle:x3014X2
In my view I can iterate over these articles:
<% #articles.each do |article| %>
<%= article.article.name %>
<% end %>
which works perfectly.
Can I do this in this way to get an Articles array instead of a FollowArticles array, something like:
#items = current_user.follow_articles
to return articles instead of followArticles?
Use has_many :through.
From the Guide:
A has_many :through association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this:
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
So, fully connecting the dots...
#User.rb
class User < ActiveRecord::Base
has_many :follow_articles
has_many :followed_articles, through: :follow_articles
end
I have five models: Course, Lesson, Question, Answer and User.
What I'm trying to do is determine if the User has Answers for all of the Questions in a Lesson (so I can put "Done" next to the lesson in the view if this is the case).
My models:
class Course < ActiveRecord::Base
has_many :lessons, dependent: :destroy
has_many :questions, :through => :lessons
has_many :users, through: :purchases
end
class Lesson < ActiveRecord::Base
belongs_to :course
has_many :questions, dependent: :destroy
has_many :answers, through: :questions
end
class Question < ActiveRecord::Base
belongs_to :lesson
belongs_to :course
has_many :answers, dependent: :destroy
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :user
end
class User < ActiveRecord::Base
has_many :answers
has_one :author
has_many :courses, through: :purchases
end
What I tried to do was to check if a Lesson's Questions were in the Questions the User Answered, but the includes? line doesn't seem to be working the way I want.
in my controller, I have:
#lessons = #course.lessons
#answers = current_user.answers
#questions = Question.where(:id => #answers.map(&:question_id))
in my view, I have:
<% #lessons.each do |lesson| %>
<% lesson_questions = lesson.questions %>
<%= user_questions = #questions.where("lesson_id = ?", lesson.id)%>
<% if user_questions.include?(lesson_questions)%>
Done!
<% end %>
<% end %>
I'm not sure if this is the cause, but I noticed the lesson_questions are #<Question::ActiveRecord_Associations_CollectionProxy:0x9c49698>
While the user_questions are: #<Question::ActiveRecord_Relation:0x9c48330>
I'm wondering, (a) how I accomplish my objective of finding the Lessons with all of the Questions answered, and (b) if there's a more efficient way to do this. Thanks!
Problem
You can't check if an array includes another array just like this:
user_questions.include?(lesson_questions)
You need to check if each element from lesson_questions is included in the user_questions.
Try these instead:
Solution: 1
lesson_questions.all? { |lq| user_questions.include?(lq) }
This should return true if all the lesson_questions are included in the user_questions.
Solution: 2
(lesson_questions - user_questions).empty?
I have three tables: illnesses, symptoms and a third table to map the relationship between the first two, called symptom_illness.
This third table has symptom_id, illness_id and its own id
I need a way to show, for example, all symptoms of a given "Common Cold" illness. In this example, "Common Cold" has an id of 1 and its symptoms have ids of 1 through 5.
This means that symptom_illness has 5 entries, where:
symptom_illness.illness_id = 1, symptom_id = 1
symptom_illness.illness_id = 1, symptom_id = 2
symptom_illness.illness_id = 1, symptom_id = 3
And so on. I need a way to display, in a single page, all the symptoms that have the same illness_id but I can't seem to find a way how to.
EDIT 1: My classes are related as such:
Symptom:
has_many :symptom_illness
has_many :illnesses, through: :symptom_illness
And similar for Illness.
Illness_symptom has belongs_to :symptom and belongs_to :illness
You have three models
class Symptom
has_many :symptom_illnesses
has_many :illnesses, through: :symptom_illnesses
end
class SymptomIllness
belongs_to :illness
belongs_to :symptom
end
class Illness
has_many :symptom_illnesses
has_many :symptoms, through: :symptom_illnesses
end
You can easily access al illness symptoms with something like that:
Illness.find(1).symptoms.each do |symptom|
# do something with this symptom
end
What do you want to show in the page?
If the symptom has a name attribute you can initialize a
#symptoms = Illness.find(1).symptoms
array in controlller and in the page you do something like
<% #symptomps.each do |symptom| %>
<% = symptom.name %>
<% end %>
You should use a has_many through: relationship.
class Illness < ActiveRecord::Base
has_many :symptom_illness
has_many :symptom, through: :symptom_illness
end
class SymptomIllness < ActiveRecord::Base
belongs_to :symptom
belongs_to :illness
end
class Symptom < ActiveRecord::Base
has_many :symptom_illness
has_many :illness, through: :symptom_illness
end
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Class Symptom < ActiveRecord::Base
belongs_to :symptom_illness
has_many :ilnesses, through: symptom_illness
end
class SymptomIllness < ActiveRecord::Base
belongs_to :symptom
belongs_to :illness
scope :ilness, ->(*i) {
where(ilness_id: i.flatten.compact.uniq
}
end
class Illness < ActiveRecord::Base
belongs_to :symptom_illness
has_many :symptoms, through: :symptom_illness
end
Ilness.find(1).symptoms
I am having trouble figuring how to add drag and drop to a has_many through association?
I have a Model for Boards and each Board has many Lists.
Each List has many Cards and each Card has many Lists through a join model called ListCard
I am trying to add drag and drop to cards on each list. I have the front end working with jQuery UI i just don't know how to save the position through ajax to the position integer column in my ListCard Model.
I have looked at these below but i can't figure out how to setup the controller for a has_many through association?
Railscast for setting up drag and drop with acts_as_list
Using acts_as_list with has_many :through in rails
https://github.com/swanandp/acts_as_list/issues/95
https://github.com/swanandp/acts_as_list/issues/86
Models
class List < ActiveRecord::Base
belongs_to :user
belongs_to :board
has_many :list_cards, dependent: :destroy
has_many :cards, through: :cards
accepts_nested_attributes_for :list_cards
end
class ListCard < ActiveRecord::Base
belongs_to :list
belongs_to :card
acts_as_list :scope => :list_card
end
class Card < ActiveRecord::Base
belongs_to :user
has_many :list_cards
has_many :lists, through: :list_cards
accepts_nested_attributes_for :list_cards
end
Routes
resources :boards do
resources :lists do
collection { post: sort }
end
end
resources :cards
Lists Controller
def sort
# not sure what to put here
render nothing: true # this is a POST action, updates sent via AJAX
end
Board Show Page (for example www.example.com/board/1)
<% #lists.each do |list| %>
<ul id="cardwrap">
<%= list.title %>
<% list.cards.each do |card| %>
<%= content_tag_for :li, card do %>
<%= card.title %>
<% end %>
</ul>
<% end %>
jQuery UI Sortable Coffescript
jQuery ->
$('cardwrap').sortable
axis: 'y'
update ->