Let's say I have something like:
#order = Order.new(status: :pending)
#item = #order.items.build(title: "Shirt")
When I try to call #item.order, I get an error not found, I guess it's because it's still unsaved to the DB, but how can make it point to the built object without saving any of the objects?
#item.order # Not Found - Tries to fetch from the DB
To make this work you have to tell the order association of your item model that it is the inverse of your items association or you order model and vice versa:
Like this:
class Item < ActiveRecord::Base
belongs_to :order, inverse_of: :items
end
class Order < ActiveRecord::Base
has_many :items, inverse_of: :order
end
That way your associations will be set up correctly even before saving to the database.
EDIT: If you don't want this you can always assign the association explicitly:
#item = #order.items.build(title: "Shirt")
#item.order = #order
or in one line:
#item = #order.items.build(title: "Shirt", order: #order)
I tested this in Rails 4.0.1 and it looks like using inverse_of is no longer needed as the associations are inferred properly. It was addressed in this commit: https://github.com/rails/rails/pull/9522
It is worth noting that there is still no solution for has_many through: relationships at this time.
That's because the #order haven't got an ID yet. So when calling #order.items.build the item has an nil in order_id field and thus cannot found the order you want.
I suggest you use the nested attributes feature to save some associated records together.
Related
I have two models, Profile and Skill, where a profile has_many skills.
I am using Rails as an API and passing an array of skill objects.
I'm doing #profile.skills = #skills in the controller, where #skills is my data from the frontend.
Whenever I delete or add a new skill, the above works as expected - also, #profile.skills.replace(#skills) works just the same.
The associated object gets deleted or created in the database as well. All as expected.
However, if I only change one or more attributes on an already existing skill, the changes are not saved to the database.
If I log #profile.skills after the above line of code, it seems like the changes expected are present.
But it does not get saved to the database and on the next request the changes are obviously not present.
What am I doing wrong?
Have you tried to pass like this:
class Profile < ApplicationRecord
has_many :skills
accepts_nested_attributes_for :skills
end
or
class Profile < ApplicationRecord
has_many :skills, autosave: true
end
after that, edit the same attribute of the same record and save
#profile.skills.same.same_attr = new_val
#profile.save
accepts_nested_attributes_for - it also adds autosave to asociations, and a lot of other things, you can see more in the documentation and source code
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
a little bit about how autosave works for associations
https://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
I am trying to save an array of multiple ids (item_variation_ids) to a model called items_stock from item variations model. In a column called item_variation_ids in item_stock, it is saving the ids like [1,2,3] for twice. I want the item_variation_ids to be saved once only with 1,2,3 in a single column.
My item_variation model
#app/models/item_variation
class ItemVariation < ApplicationRecord
belongs_to :item
validates_associated :item
after_save :add_to_item_stock
def add_to_item_stock
ItemStock.create(item_variation_ids: ItemVariation.ids, items_id: items_id)
end
end
My item model
#app/models/item
class Item < ApplicationRecord
has_many :item_variations, foreign_key: :items_id
has_many :item_stocks, foreign_key: :items_id
accepts_nested_attributes_for :item_stocks
end
My item_stock model
#app/models/item_stock
class ItemStock < ApplicationRecord
belongs_to :item
end
But how do you know which ItemVariation ids should go on that ItemStock? and you are creating one ItemStock each time any variation gets saved. I don't even think you need to set that ids array since the ItemStock already belongs to an Item which has many variations (#item_stock.item.variations and you are done).
Also now you are talking about a stock_qty attribute you never mentioned before, you are never setting it on the callback and you didn't show your database schema. where does that amout come from? is an attribute on the variation that you want to sum to the current item_stock?
I also don't understand why an item has many item stocks for the code you are showing.
I'll do a wild guess and suggest you do something like:
ItemStock
belongs_to :item
belongs_to :item_variation
end
ItemVariation
after_save :add_to_item_stock
def add_to_item_stock
item_stock = self.item.item_stock.where(item_variation_id: self.id).first_or_initialize
item_stock.stock_qty = self.stock_qty
item_stock.save
end
end
but as I said, it's a wiiiiild guess. I'd recommend you to first try to understand what you are doing, because it seems like you just copied to code from that question you linked and you are no actually understanding it.
Give a two models, with a has_one association:
class ShopInfo
belongs_to :shop
end
class Shop
has_one :shop_info
end
s = Shop.create
ss1 = s.create_shop_info
In some other place of my code I do
ss2 = s.create_shop_info
After this, ss1.shop_id is set to nil, so ss1 is now an orphan record.
Is there any way to remove previous records instead of set them to nil?
By default, the has_one association executes a nullify. Adding the dependent: :destroy solved the problem.
class Shop
has_one :shop_info, dependent: :destroy
end
Just if someone wants more info, the ActiveRecord code for has_one replacement record is this:
https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/associations/has_one_association.rb#L24-L51
BUT if you add a dependent option in the association, executes the delete method as well:
https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/associations/has_one_association.rb#L7-L22
Fran, if you look at the has_one documentation, I think you want to use the association= method:
association=(associate)
Assigns the associate object, extracts the
primary key, sets it as the foreign key, and saves the associate
object. To avoid database inconsistencies, permanently deletes an
existing associated object when assigning a new one, even if the new
one isn't saved to database.
Which means your code might look like...
ss2.shop_info = ShopInfo.new(...)
Assuming
class Kid < ActiveRecord::Base
has_one :friend
end
class Friend< ActiveRecord::Base
belongs_to :kid
end
How can I change this to
class Kid < ActiveRecord::Base
has_many :friends
end
class Friend< ActiveRecord::Base
belongs_to :kid
end
Will appreciate your insight...
Collection
The bottom line is that if you change your association to a has_many :x relationship, it creates a collection of the associative data; rather than a single object as with the single association
The difference here has no bearing on its implementation, but a lot of implications for how you use the association throughout your application. I'll explain both
Fix
Firstly, you are correct in that you can just change your has_one :friend to has_many :friends. You need to be careful to understand why this works:
ActiveRecord associations work by associating something called foreign_keys within your datatables. These are column references to the "primary key" (ID) of your parent class, allowing Rails / ActiveRecord to associate them
As long as you maintain the foreign_keys for all your Friend objects, you'll get the system working no problem.
--
Data
To expand on this idea, you must remember that as you create a has_many association, Rails / ActiveRecord is going to be pulling many records each time you reference the association.
This means that if you call #kind.friends, you will no longer receive a single object back. You'll receive all the objects from the datatable - which means you'll have to call a .each loop to manipulate / display them:
#kid = Kid.find 1
#kid.friends.each do |friend|
friend.name
end
If after doing this changes you have problem calling the save method on the order.save telling you that it already exists, and it not allowing you to actually have many order records for one customer you might need to call orders.save(:validate=> false)
You have answered the question. Just change it in model as you've shown.
class Task < ActiveRecord::Base
has_many :notes, :as => :notable, :dependent => :destroy
has_many :work_times, :dependent => :destroy
end
class WorkTime < ActiveRecord::Base
belongs_to :task
end
class NotesController < ApplicationController
end
end
####
Any help please??
Since the relationship is a has_many you will need to work with a particular time, not the aggregate:
work_time = #task.work_times.last
work_time.start_time = Time.now
work_time.save!
In this case the last WorkTime record is selected and manipulated. Maybe you want to use the first, or select it with a condition:
work_time = #task.work_times.where(:active => true).first
There's a lot of ways to select the correct record to manipulate, but your question is somewhat vague.
If you're looking to create a new entry instead of modifying one, you might want to do this:
#task.work_times.create(:start_time => Time.now)
This is just exercising the ActiveRecord model relationship.
You would simply get the object and just change its value like :
user = User.first
user.username = 'changed_name'
user.save # and save it if you want
But, this is actually code that belongs to a model and should be wrapped by a model method.
As alluded to by a few of the other answers, you need an object of type WorkTime to pass the value to.
From the code you've posted it doesn't look like you've got such an instance. You can either find one (WorkTime.find.. ) or create a new one (WorkTime.new..)
It looks like you have an instance of a note (#note), though I'm not sure where that came from.. you might be able to fetch appropriate WorkTime objects using:
#note.task.work_times
or the first of these with:
#note.task.work_times.first