So I have a table structure below:
Plane
plane_id
plane_info
Seating
seating_id
seating_info
PlaneSeating
plane_seating_id
plane_id
seating_id
PlaneSeatingNote
plane_seating_note_id
plane_seating_id
note_id
Note
note_id
note_info
This gives me a third-normal DB, but I need to set the model relations up.
I currently have:
class Plane < ActiveRecord::Base
has_many :plane_seatings, dependent: :destroy
has_many :seatings, through: :plane_seatings
end
class Seatings < ActiveRecord::Base
has_many :plane_seatings, dependent: :destroy
has_many :planes, through: :plane_seatings
end
class PlaneSeating < ActiveRecord::Base
belongs_to :plane
belongs_to :seating
has_many :plane_seating_notes, dependent: :destroy
has_many :notes, through: :plane_seating_notes
end
class PlaneSeatingNote < ActiveRecord::Base
belongs_to :plane_seating
has_one :note
end
class Note < ActiveRecord::Base
end
Now, this will give me the ability to say Plane.all.first.plan_seatings.first.notes and get the notes I believe. However, I'd like to be able to say Plane.all.first.seatings.notes and get the notes associated with that plane given that seating.
My thought is there should be a way to say, in Plane:
has_many :seatings, through: plane_seating, has_many :notes, through: plane_seating
or some other chaining magic to get a seating with some notes that only apply to that plane and seating combo. a :with, if you will. But I can't think of any syntax that would give me that. Anyone know?
The best is to pivot it the other way, it you want to grab the notes for a certain Plane:
Note.joins(plane_seating_note: [:plane_seating]).where(plane_seating_note: {plane_seating: {plane_id: 1})
You could make that a scope if you're using it in multiple places and if you want it on the Plane model:
class Plane < ActiveRecord::Base
has_many :plane_seatings, dependent: :destroy
has_many :seatings, through: :plane_seatings
def notes
#notes ||= Note.for_plane_id id
end
end
class Note < ActiveRecord::Base
has_many :plane_seating_notes
scope :for_plane_id ->(plane_id) { joins(plane_seating_notes: [:plane_seating]).where(plane_seating_notes: {plane_seating: {plane_id: plane_id}) }
end
For a specific seat on a specific plane, you'd typically see something like this in a controller:
#seat = PlaneSeat.find params[:id]
#plane = #seat.plane
#notes = Note.joins(:plane_seating_notes).where(plane_seating_notes: {plane_seating_id: #seat.id})
But since you have a HMT you could just do
#seat = PlaneSeat.find params[:id]
#plane = #seat.plane
#notes = #seat.notes
A couple "Rails-way" notes:
Unless you are using Note elsewhere, you should just skip the plane_seat_notes.
Consider using has_and_belongs_to_many if you aren't appending any extra meta-data in the intermediate table; this makes relationships easier and gives you shallower query helpers
Consider using polymorphic relationships rather than unnecessary join tables
I used a helper method in the Plane model to get what I wanted. This method may be inefficient if you are dealing with large datasets, but for my datasets, it works fine. It packages up each seating subset for each plane with the notes associated with it into a hash.
#Get an easy to read hash of the seatings with their notes
def seatings_with_notes
#seatings_with_notes = []
self.plane_seatings.each do |item|
seating = Seating.where(id: item.product_application_id).first
notes = item.notes
combo = {seating:seating, notes:notes}
#seatings_with_notes.append(combo)
end
return #seatings_with_notes
end
Related
Lets say I have two models: Performance and Band, and to connect the two I have a join table called performers. My ActiveRecord models are setup as follows:
class Band < ApplicationRecord
has_many :performers
has_many :performances, through: :performers, dependent: :destroy
end
class Performance < ApplicationRecord
has_many :performers
has_many :bands, through: :performers, dependent: :destroy
end
class Performer < ApplicationRecord
belongs_to :band
belongs_to :performance
end
Now the tricky part. I have a custom attribute called permissions in the performers table that captures the permission levels (owner, editor, viewer) for a performer, which defines who can make changes to a performance. Which brings me to my question: when a band creates a new performance, how can I set a value on the join table during creation e.g.
def create
performance = Performance.new(performance_params)
# here I add a performance to a band's performances, which creates a new performer record
band.performances << performance
# what I would also like to do (at the same time if possible) is also define the permission level
# during creation something like but:
performer = band.performers.last
performer.permissions = 'owner'
performer.save
render json: serialize(performance), status: 200
end
Is there something in Rails that can let me modify a join tables attribute during creation of an association?
EDIT
For reference, right now I do:
def create
performance = Performance.new(performance_params)
performer = Performer.new
performer.band = Band.find(params[:band_id])
performer.permissions = 'owner'
performance.performers << performer
performance.save!
render json: serialize(performance), status: 200
end
But was wondering if there was something simpler.
You can use either Association Callbacks on has_many or add appropriate callback to Performer model as it's still created as a model even it's joining one.
Something like:
class Performer < ApplicationRecord
belongs_to :band
belongs_to :performance
before_create :set_permissions
private
def set_permissions
self.permissions = 'owner'
end
end
So I have these four classes:
class User < ActiveRecord::Base
has_many :water_rights
end
class WaterRight < ActiveRecord::Base
belongs_to :user
has_many :place_of_use_area_water_rights
has_many :place_of_use_areas, through: :place_of_use_area_water_rights
end
class PlaceOfUseAreaWaterRight < ActiveRecord::Base
belongs_to :place_of_use_area
belongs_to :water_right
end
class PlaceOfUseArea < ActiveRecord::Base
has_many :place_of_use_area_water_rights
has_many :water_rights, through: :place_of_use_area_water_rights
end
and I call User.first.water_rights and get a collection of WaterRights. My question is how do I get a collection of PlaceOfUseAreas associated with those WaterRights without doing something like this:
areas = []
water_rights.each do |wr|
areas << wr.place_of_use_areas
end
areas.flatten.uniq{ |a| a.id }
This works but it makes a new query for every single WaterRight. I'm looking for a way to make one query to get the collection of associated PlaceOfUseAreas.
You just want to get all associated PlaceOfUseAreas objects in single query, right?
If so, Rails have pretty single line solution for it:
PlaceOfUseArea.joins(:water_wights).uniq
Read more about joins method if you want more information.
I have the following structure of models:
class User < ActiveRecord::Base
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :company
end
class Color < ActiveRecord::Base
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :colors
has_many :favorities
end
It means that a company has many colors. Every user can favorite a company (and I can print out then every color that the respective company offers).
But I am trying to display all colors that companies I've favorited offers.
I've tried it something like this:
favorited_colors = current_user.favorites.colors
undefined method `colors' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Favorite:0x007fe037da01f0>
and
favorited_colors = current_user.favorites.companies.colors
undefined method `companies' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Favorite:0x007fe038ce8db0>
Is there any other way how to get a list of all colors from favorited companies than to iterated via each loops through all favorited companies and save all colors into an array?
Thank you
This should work from rails 3.1 on:
class User
has_many :favorites
has_many :companies, :through => :favorites
has_many :favorit_colors, :through => :companies
end
And then
favorited_colors = current_user.favorit_colors
This is not tested, though.
Collections
The problem you have is that you're calling the companies method on a collection object
The issue here is that you can only call methods on single instances of objects. This is demonstrated quite well with the error you're seeing:
undefined method `companies' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Favorite:0x007fe038ce8db0>
The way you should attempt to get this to work is to call the following:
current_user.favorites.each do |favourite|
= favourite.colors
end
I understand what you're looking to achieve, so let me give you some ideas:
Association Extensions
There's a functionality of Rails called ActiveRecord Association Extensions, which could give you the ability to provide what you're seeking:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :favorites do
def colors
# Business logic for colours here
end
end
end
Now this is totally untested. However, I have experience with this deep part of Rails, and I therefore know that you'll have the following data-sets to work with:
proxy_association.owner returns the object that the association is a part of.
proxy_association.reflection returns the reflection object that describes the association.
proxy_association.target returns the associated object for belongs_to or has_one, or the collection of associated objects for
has_many or has_and_belongs_to_many.
From this, you'll be able to construct an array / object based off the colors you wish to show:
has_many :favorites do
def colors #totally untested
colors = {}
favorites = proxy_association.target
favorites.each do |favorite|
favorite.company.colors.each do |color|
colors << color
end
end
return colors
end
end
I want to create a random pack of 15 cards which should be invoked in the cardpacks_controller on create. I have the following models:
Card:
class Card < ActiveRecord::Base
# relations
has_many :cardpacks, through: :cardpackcards
belongs_to :cardset
end
Cardpack:
class Cardpack < ActiveRecord::Base
#relations
has_many :cards, through: :cardpackcards
belongs_to :cardset
# accept attributes
accepts_nested_attributes_for :cards
end
Cardpackcards:
class Cardpackcard < ActiveRecord::Base
#relations
belongs_to :card
belongs_to :cardpack
end
Cardsets:
class Cardset < ActiveRecord::Base
#relations
has_many :cards
has_many :cardsets
end
How can I create 15 Cardpackcards records with random card_id values and with the same cardpack_id (so they belong to the same pack)
I have watched the complex form series tutorial but it gives me no comprehension as how to tackle this problem.
I hope anyone can help me solve this problem and give me more insight in the rails language.
Thanks,
Erik
Depending on the database system you might be able to use an order random clause to find 15 random records. For example, in Postgres:
Model.order("RANDOM()").limit(15)
Given the random models, you can add a before_create method that will setup the associations.
If the Cardpackcard model doesn't do anything but provide a matching between cards and cardpacks, you could use a has_and_belongs_to_many association instead, which would simplify things a bit.
Without it, the controller code might look something like this:
cardset = Cardset.find(params[:cardset_id])
cardpack = Cardpack.create(:cardset => cardset)
15.times do
cardpack.cardpackcards.create(:card => Card.create(:cardset => cardset))
end
I have a has_many :through relationship set up like so
class Situation < ActiveRecord::Base
has_many :notifications
has_many :notiftypes, through: :notifications
end
class Notification < ActiveRecord::Base
belongs_to :situation
belongs_to :notiftype
end
class Notiftype < ActiveRecord::Base
has_many :notifications
has_many :situations, through: :notifications
end
So, a Situation has many Notifications, which can be of many types (Notiftype).
My problem is trying to query for the notiftypes that have not been set for a particular situation.
Want to find records with no associated records in Rails 3
The answers in that question get me close, but only to the point of finding Notiftypes that have not been set AT ALL.
If this were the standard :situation has_many :notiftypes I could just do a Left Outer Join like so
myquery = Notiftype.joins('LEFT OUTER JOIN situations ON situations.notiftype_id = notiftype.id').where('notiftype_id IS NULL')
but I'm really not sure how to do this with the intermediate table between them.
I have been trying consecutive joins but it's not working. I'm not sure how to join the two separated tables.
Can anyone explain the right way to query the db? I am using SQLite, Rails 3.1, Ruby 1.9.2 right now, but likely Postgresql in the future.
Try this:
class Situation < ActiveRecord::Base
# ...
has_many :notiftypes, through: :notifications do
def missing(reload=false)
#missing_notiftypes = nil if reload
#missing_notiftypes ||= proxy_owner.notiftype_ids.empty? ?
Notiftype.all :
Notiftype.where("id NOT IN (?)", proxy_owner.notiftype_ids)
end
end
end
Now to get the missing Notiftype
situation.notiftypes.missing
If you want to further optimize this to use one SQL rather than two you can do the following:
class Situation < ActiveRecord::Base
# ...
has_many :notiftypes, through: :notifications do
def missing(reload=false)
#missing_notiftypes = nil if reload
#missing_notiftypes ||= Notiftype.joins("
LEFT OUTER JOIN (#{proxy_owner.notiftypes.to_sql}) A
ON A.id = notiftypes.id").
where("A.id IS NULL")
end
end
end
You can access the missing Notifytypes as:
situation.notiftypes.missing