Rails 3.1 - Inheriting values from a join table? - ruby-on-rails

I have the following models: Releases, Tracks & a has_many join called ReleasesTrack.
I also have Products that (semi) successfully inherit a releases tracks with Track & Release Ids being copied to a ProductsTrack has_many_through join.
The problem is i'm not getting the correct position value.
I currently have this in ProductsTrack model, it appears to work, but i'm not getting the value I want.
before_save do
self.position = self.track.position
end
Instead of the position value in the track table, I want the position from it's has_many_through join table releases_tracks. I've tried the following a variations thereof, but no joy:
before_save do
self.position = self.track.releases_track.position
end
I did think having a position field in both Tracks & ReleasesTracks could be causing the issue and there is a reason I have this in both, but I've tested with a temp field and it's not that.
I think the crux of the issue is structring self.track.releases_track.position correctly.
OR
I'm missing something in an association?
Any ideas?
EDIT: MODELS ADDED (Note, ProductsTrack is actually the badly named Producttracklisting)
class Release < ActiveRecord::Base
has_many :products, :dependent => :destroy
has_many :releases_tracks, :dependent => :destroy, :after_add => :position_track
has_many :tracks, :through => :releases_tracks, :order => "releases_tracks.position"
accepts_nested_attributes_for :tracks, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => :true
accepts_nested_attributes_for :releases_tracks
def position_track(track)
releases_tracks.each { |t| t.position = t.track.position }
end
def track_attributes=(track_attributes)
track_attributes.each do |attributes|
tracks.build(attributes)
artists_tracks.build(attributes)
end
end
end
class Track < ActiveRecord::Base
has_many :releases_tracks, :dependent => :destroy
has_many :releases, :through => :releases_tracks
has_many :producttracklistings, :dependent => :destroy
has_many :products, :through => :producttracklistings
end
class ReleasesTrack < ActiveRecord::Base
belongs_to :release
belongs_to :track
end
class Producttracklisting < ActiveRecord::Base
belongs_to :product
belongs_to :track
before_save do
self.position = self.track.position
end
end
class Product < ActiveRecord::Base
belongs_to :release
has_many :releases_tracks, :through => :release, :source => :tracks
has_many :producttracklistings, :dependent => :destroy
has_many :tracks, :through => :producttracklistings
accepts_nested_attributes_for :tracks, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => :true
accepts_nested_attributes_for :producttracklistings
#Below is where a product inherits tracks from the parent release
before_save do
self.track_ids = self.releases_track_ids
end
end

> I have the following models; Releases, Tracks & a has_many_through join called ReleasesTrack
So you have
class Release < ActiveRecord::Base
has_many :release_tracks
has_many :tracks, :through => :release_tracks
end
class Track < ActiveRecord::Base
has_many :release_tracks
has_many :releases, :through => :release_tracks
end
class ReleaseTrack < ActiveRecord::Base
belongs_to :release
belongs_to :track
end
> I also have Products that (semi) successfully inherit a releases tracks with Track & Release Ids being copied to a ProductsTrack has_many_through join.
class Products < ReleaseTrack
#uses table release_tracks
#belongs_to :release #from inheritance
#belongs_to :track #from inheritance
end
> I currently have this in ProductsTrack model, it appears to work, but i'm not getting the value I want.
What ProductsTrack model? Did you mean before that you have a ProductsTrack that inherits from a release tracks?
class ProductsTrack < ReleaseTrack
#uses table release_tracks
#belongs_to :release #from inheritance
#belongs_to :track #from inheritance
before_save do
self.position = self.track.position
end
end
> Instead of the position value in the track table, I want the position from it's has_many_through join table releases_tracks.
So far, the way I've coded it base on what you said, self.position is the position from the has_many_through join table release_tracks.

Your data model probably needs to be cleaned up. It's probably way more complicated than you need, but, given the model, let's sort things out.
There is no inheritance. All the record models inherit from ActiveRecord::Base.
You basically have releases, tracks, and products with a many-to-many relationship between releases and tracks, and another many-to-many relationship between tracks and products.
Also, products belongs_to releases and products has_many :releases_tracks through it's release.
Your question is
> I currently have this in ProductsTrack model, it appears to work, but i'm not getting the value I want.
> Instead of the position value in the track table, I want the position from it's has_many_through join table releases_tracks.
You do not list a ProductsTrack model, so I assume this is the Producttracklisting class. You want to set the position field in the producttracklistings table to "the position from it's has_many_through join table releases_tracks".
So the answer is that you cannot do this, because there is not one release_tracks record for the given producttracklistings record. The producttracklistings points to a (belongs_to) a tracks record, but the releases_tracks records point to the tracks record (not the other way around), so there can be any number of release_tracks records from 0 to many, which your question refers to.

Related

rails 2.2 - polymorphic has_many without using has_polymorphs

I'm trying to set up some new classes and associations in a Rails 2.2.2 app (please don't ask me to upgrade it).
The idea is that there is a Conversation class. It can have many participants, which are a mix of User and Pupil records, and it can have many owners, which can be a mix of a variety of things, including User and Pupil records. This is what I have so far:
#the :owner association can point to *anything*, including a Pupil or User record
class ConversationOwnership < ActiveRecord::Base
belongs_to :conversation
belongs_to :owner, :polymorphic => true
end
#the :participant association will point to either a Pupil or User record
class ConversationParticipant < ActiveRecord::Base
belongs_to :conversation
belongs_to :participant, :polymorphic => true
end
class Conversation < ActiveRecord::Base
has_many :conversation_ownerships
has_many :owners, :through => :conversation_ownerships
has_many :conversation_participants
has_many :participants, :through => :conversation_participants
end
This currently isn't working:
Conversation.first.participants
=> ActiveRecord::HasManyThroughAssociationPolymorphicError: Cannot have a has_many :through association 'Conversation#participants' on the polymorphic object 'Participant#participant'.
Now, I know that the has_many_polymorphs plugin was designed to solve this very problem, but the problem with using that is that it automatically makes associations for each of the listed classes, and because User and Pupil are in both participants and owners, they would clash:
OWNER_CLASSES = [:ilps, :lessons, :digilearning_modules, :resources, :pupil_groups, :pupils, :users]
PARTICIPANT_CLASSES = [:pupils, :users, :contacts, :parent_carers]
has_many_polymorphs :participants, :from => PARTICIPANT_CLASSES, :through => :conversation_participants, :order => "conversation_participants.created_at"
has_many_polymorphs :owners, :from => OWNER_CLASSES, :through => :conversation_ownerships, :order => "conversation_ownerships.owner_type, conversation_ownerships.created_at"
With this, the first hmp makes a .pupils association which effectively means "participants that are Pupils", and the second hmp also makes a .pupils association which effectively means "owners that are Pupils".
I don't want these extra associations that has_many_polymorphs brings, which is why i thought I would roll my own associations. But, I can't get past that Cannot have a has_many :through association 'Conversation#participants' on the polymorphic object 'Participant#participant'. error.
This feels like it should be possible - is it?

Adding existing records to has_many :through relationship

I'm building a dictionary application where I have some concepts linked to some words using a join model. I have a form to edit each concept, where I can edit its words, add new words to it or associate existing words with it.
I'm using accepts_nested_attributes_for and update_attributes, but this only works with the already associated words or the newly created ones. For the existing words that weren't previously associated, I get errors like this ActiveRecord::RecordNotFound (Couldn't find Word with ID=4 for Concept with ID=1000) when ActiveRecord runs a select on the join model.
Here are my models:
class Word < ActiveRecord::Base
attr_accessible :notes, :text, :grammar_tag_list
has_many :semantic_relations, :dependent => :destroy
has_many :concepts, :through => :semantic_relations
end
class Concept < ActiveRecord::Base
attr_accessible :meaning_tag_list, :words_attributes
has_many :semantic_relations, :dependent => :destroy
has_many :words, :through=> :semantic_relations, :order => 'knowledge_id ASC'
accepts_nested_attributes_for :words, :reject_if => lambda { |w| w[:text].blank? }
end
class SemanticRelation < ActiveRecord::Base
belongs_to :word, :inverse_of => :semantic_relations
belongs_to :concept, :inverse_of => :semantic_relations
end
I have managed it to work using the following method in my Concepts controller (calling it before update_attributes), but it seems a quite dirty approach. Isn't there any proper way to achieve this?
def check_words
current_ids = #concept.word_ids
new_ids = params[:concept][:words_attributes].map do |w|
id = w[1][:id].to_i
unless id == 0
unless current_ids.include? id
id
end
end
end
#concept.word_ids = current_ids + new_ids unless new_ids.blank?
end

Rails many-to-many :through association: associated object is not updated after destroying the association object

As I am new to Rails, there is may be a trivial solution. But I could not even find this exact issue somewhere. Other posts deal with destroy vs. delete (I tried both with the same result) or just do not mention how the associated object behaves.
My problem: I want to create a many-to-many association via :through. When I delete an association (i.e. the relation object, not the related objects) I expect that this association is removed (updated) in all model instances of the associated objects. But this does not fully happen.
My example:
Meeting < ActiveRecord::Base
has_many :participations
has_many :users, :through => :participations
User < ActiveRecord::Base
has_many :participations
has_many :meetings, :through => :participations
Participation < ActiveRecord::Base
belongs_to :meeting, :foreign_key => :meeting_id
belongs_to :user, :foreign_key => :user_id
When I create a new association, the associated objects are updated accordingly:
u = User.find(...)
m = Meeting.find(...)
m.users<< u
The same when creating the association this way:
m.participations.create(:user_id => u.id) # this requires to make the user_id attribute accessible
When I now look at the associated user model instance, it got updated as expected:
u.meetings >> contains the newly created association to the meeting m
When I destroy (not delete!) this association, the associated object is not updated as I expect it:
m.users.find_by_user_id(u.id).destroy
m.users >> []
u.meetings >> still contains the destroyed association to meeting m
I would have expected that u.meetings is updated and empty ([]). Adding validations didn't help to solve this:
Meeting < ActiveRecord::Base
validates_associated :contacts
or
Participation < ActiveRecord::Base
validates_presence_of :contact, :interview
What am I doing wrong or what am I missing here?
I am using Rails 3.2.8
Thanks to everyone who is willing to help me.
You should be doing :dependent => :destroy.
Meeting < ActiveRecord::Base
has_many :participations, :dependent => :destroy
has_many :users, :through => :participations
User < ActiveRecord::Base
has_many :participations, :dependent => :destroy
has_many :meetings, :through => :participations
Participation < ActiveRecord::Base
belongs_to :meeting, :foreign_key => :meeting_id
belongs_to :user, :foreign_key => :user_id
This will make sure to destroy the participation if either of the associated user or meeting is destroyed.
You should update your model with the following relationship option:
dependent: destroy
Which will call destroy on the associated objects.
Reference: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Deleting+from+associations
I guess the problem could be like this. In your example,
m.users.find_by_user_id(u.id).destroy
m.users >> []
u.meetings >> still contains the destroyed association to meeting m
u and u.meetings were already loaded before m.users.find_by_user_id(u.id).destroy. Then u.meetings output the cached data.
You can try u.meetings(true) or u.reload; u.meetings to see if there are any difference.

How to save data with has_many :through

I have many-to-many relationship between Game and Account models like below:
class Account < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :games, :through => :account_games
end
class Game < ActiveRecord::Base
has_many :account_games, :dependent => :destroy
has_many :accounts, :through => :account_games
end
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
end
Now I know let's say I want to create a record something like:
#account = Account.new(params[:user])
#account.games << Game.first
#account.save
But how am I supposed to update some of the attributes in AccountGame while I'm doing that? Lets say that AccountGame has some field called score, how would I update this attribute? Can you tell me about the best way to do that? To add any field in the through table while I'm saving the object.
#account = Account.new(params[:user])
#accountgame = #account.account_games.build(:game => Game.first, :score => 100)
#accountgame.save
Though I'd strongly recommend that if you start adding columns to your join-model that you call it something different eg "subscription" or "membership" or something similar. Once you add columns it stops being a join model and starts just being a regular model.
This should work:
class AccountGame < ActiveRecord::Base
belongs_to :account
belongs_to :game
attr_accessible :account_id, :game_id #<======= Notice this line
end

How can I make a cascade deletion in a one_to_many relationship in Rails ActiveRecord?

I have a model in rails with one_to_many relationship. When I delete the father, I'd like to delete all childrens. How should I do it? I want to delete all orders and its items when I delete a user
My models are:
class User < ActiveRecord::Base
has_many :orders, :foreign_key => "id_user"
end
class Order < ActiveRecord::Base
has_many :order_items, :foreign_key => "id_pedido"
belongs_to :user, :foreign_key => "id_usuer"
end
class OrderItem < ActiveRecord::Base
belongs_to :order, :foreign_key => "id_pedido"
end
jdl's answer is correct - you need to add :dependent => :destroy to both relationships - i.e. in your User class, add it to has_many :orders, and in your Order class, add it to has_many :order_items.
You might also want to change the MySQL behaviour wrt foreign keys, perhaps setting them to ON DELETE CASCADE.
What you're looking for is the :dependent => :destroy option on has_many.
has_many docs

Resources