how to save object without associated records - ruby-on-rails

I have many associations in my model. Every record associated with parent object is saved every time parent is saved. It is behavious i want, just not every time. Is there a special method to save JUST the 'mother' object?

The ability to autosave associated models can be turned on or off on a per-association basis, like so:
class Post < ActiveRecord::Base
has_one :author, autosave: false
has_many :things, autosave: false
end
Read all about it here: https://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html

Here is sample not sure this is what you are looking for??
class Survey < ActiveRecord::Base
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
class Question < ActiveRecord::Base
belongs_to :survey
has_many :answers, :dependent => :destroy
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
Here Survey has many Questions, So I assume from your question that when survey is build up Question should not be saved?? Lets now have a look to controller of Survey.
surveys_controller.rb
def new
#survey = Survey.new
#survey.build # here I have commented below lines which create the object for questions also
# 3.times do
# question = #survey.questions.build
# 4.times { question.answers.build }
# end
end
def create
#survey = Survey.new(params[:survey])
if #survey.save
flash[:notice] = "Successfully created survey."
redirect_to #survey
else
render :action => 'new'
end
end

As far as I know, by default a belongs_to association won't get saved if you save the parent class (model).
To enable this feature you need autosave.
Eg.
class Post < ActiveRecord::Base
has_one :author, autosave: true
end
However, looking further I found out this default behavior varies for different associations. Have a look at this answer: When will ActiveRecord save associations?

Maybe accepts_nested_attributes_for in your model. I have the same problem. I set it as save: false, but it doesn't make sense. I remove the accepts_nested_attributes_for, and then it makes sense. Hope it will help you.

Unfortunately there's no such functionality.
In case of has_many association, the child is saved on saving its parent if child is #new_record?.
I'd avoid saving it by not permitting params, if only it's modified in controller.

Related

Rails has many through association setting multiple attributes

So I have a has_many through association where between two tables posts and users:
class Post < ApplicationRecord
has_many :assignments
has_many :users, :through => :assignments
end
class User < ApplicationRecord
has_many :assignments
has_many :posts, :through => :assignments
end
class Assignment < ApplicationRecord
belongs_to :request
belongs_to :user
end
Now in my association table (assignment) there are additional attributes for creator:boolean and editor:boolean.
My question is what's the best way to set these secondary attributes from within the controller?
Having looked around I've got a current solution:
posts_controller.rb:
class PostsController < ApplicationController
def create
params.permit!
#post = Post.new(post_params)
if #post.save
Assignment.handle_post(#post.id, params[:creator], params[:editors])
redirect_to posts_path, notice: "The post #{#post.title} has been created."
else
render "new"
end
end
assignment.rb:
class Assignment < ApplicationRecord
belongs_to :request
belongs_to :user
def self.handle_post(post_id, creator, assignment)
Assignment.where(:post_id => post_id).delete_all
Assignment.create!(:post_id => post_id, :user_id => creator, :creator => true, :editor => false)
if editors.present?
editors.each do |e|
Assignment.create!(:post_id => post_id, :user_id => e, :creator => false, :editor => true)
end
end
end
end
So what is essentially happening is I'm getting the user_ids from the form via params (creator returns 1 id, editors returns an array), and AFTER creating the post I'm deleting all columns associated with the post and recreating them off the new attributes.
The issue I have here is I can't run post validations on these associations (e.g. check a creator is present).
My two questions are as follows:
Is this the correct way to handle secondary attributes?
Is there a way to set the association up and then save it all at once so validations can be performed?
This is a more Rails way to do this:
Use nested attributes
post.rb
class Post < ApplicationRecord
# Associations
has_many :assignments, inverse_of: :post
has_many :users, through: :assignments
accepts_nested_attributes_for :assignments
# Your logic
end
assignment.rb
class Assignment < ApplicationRecord
after_create :set_editors
belongs_to :request
belongs_to :user
belongs_to :post, inverse_of: :assignments
# I would create attribute accessors to handle the values passed to the model
attr_accessor :editors
# Your validations go here
validates :user_id, presence: true
# Your logic
private
def set_editors
# you can perform deeper vaidation here for the editors attribute
if editors.present?
editors.each do |e|
Assignment.create!(post_id: post_id, user_id: e, creator: false, editor: true)
end
end
end
end
And finally, add this to your PostsController
params.require(:post).permit(..., assignments_attributes: [...])
This allows you to create Assignments from the create Post action, will run validations on Post and Assignment and run callbacks for you.
I hope this helps!

Rails 4.2: updating relationships, via a relationship…

I have a the following models:
class Part
belongs_to :note, inverse_of: :part, dependent: :destroy
class Note
has_many :attachments, as: :attachable, dependent: :destroy
has_one :part, inverse_of: :note
end
class Attachment
belongs_to :attachable, polymorphic: true, touch: true
end
When I add attachments to a note, I do it via the part
def update
#part = current_user.parts.find params[:id]
#part.note.update note_params
…
end
What I find strange is the following:
def update
#part = current_user.parts.find params[:id]
#part.note.update note_params
#part.note.attachments.any? # => false
#part.note.attachments.any? # => true
end
Why does the first call return false? Since I need this in my view, I'm left with calling #part.note.reload, but I can't understand what is going on.
Thanks
Associations are cached for performance reasons. Use association(true) to bypass the cache and force Rails to refetch the current state after you have done something to change it:
#part.note(true).attachments.any?
See Association Basic: Controlling Caching.

rails counter cache while destroy

For example, I have three model user, question and answer, and the relationship between them are:
class User < ActiveRecord::Base
has_many :answers
has_many :questions
end
class Question < ActiveRecord::Base
has_many :answers, :dependent => :destroy
belongs_to :user, :counter_cache => true
end
class Answer < ActiveRecord::Base
belongs_to :user, :counter_cache => true
belongs_to :question, :counter_cache => true
end
Then when i want to destroy a question(with 1000 answers), these will happen:
The answers will destroy one by one and will update the counter in the user model, even the counter in the question i want to destroy, and which will take a long time to do the counter update.
my question is how to make it faster?
I got my own solution like this:
step 1:
remove the dependent destroy, which will call for counter update before destroy itself.
step 2:
add my own before_destroy, like this
before_destroy :delete_dependents
and use delete_all function to delete without call any before_destroy, then call reset_counters function to reset the counter in User model.
full codes of class question:
class Question < ActiveRecord::Base
has_many :answers
has_many :answer_users, :through => :answers, :source => :user, :uniq => true
belongs_to :user, :counter_cache => true
before_destroy :delete_dependents
private
def delete_dependents
answer_user_ids = self.answer_user_ids # eager loading for store the answer users id
Answer.delete_all(:question_id => self.id)
answer_user_ids.each do |u_id|
User.reset_counters u_id, :answers
end
end
end
PS: If there are too many counters need to be reset, you might need a background job to work it out.

Rails :dependent => destroy with conditions

Say I have dogs, leashes, and owners... If I destroy a leash I want to destroy the dog too.. but not if the dog has an owner..
You don't want to use :dependent => :destroy here, but rather the before_destroy callback like so:
#leash.rb
before_destroy :destroy_dog
def destroy_dog
dog.destroy unless dog.owner
end
class Book < ApplicationRecord
belongs_to :author, -> { where active: true },
dependent: :destroy
end
works for has_many too, in which it destroys the objects according to the where condition

RoR: Creating a relationship between a purchased product and a user

Hello and thanks for any help in advance.
I want to create a relationship between a user and a product(pdf) upon an order being saved.
class PDF
has_many :line_items
end
class LineItem
belongs_to :pdf
belongs_to :cart
end
class Cart
has_many :line_items
has_one :order
end
class Order
belongs_to :cart
end
Upon the user purchasing a line_item, I would like to create relationships through a join model between user and pdf (pdf_relationships).
I'm trying to find each PDF(found by foreign_key line_item.pdf_id) in a given cart and create pdf_relationships between a user and each pdf in the cart. I'll be making the user's id the owner id and making the pdf's id the owned_id.
My order controller looks like this:
def create
#order = current_cart.build_order(params[:order])
#order.ip_address = request.remote_ip
if #order.save
if #order.purchase
render :action => "success"
else
render :action => "failure"
end
else
render :action => 'new'
end
end
and what I'm having trouble with is this:
class Order
belongs_to :cart
before_save :create_owner
***def create_owner
self.cart.line_items.each do |item|
pdf.find_by_item_pdf_id(:pdf_id)
current_user.pdf_relationships.build(:owned_id => pdf.id)
end
end***
end
here is my user model:
class User
has_many :line_items
has_many :pdf_relationships, foreign_key: :owner_id, :dependent => :destroy
has_many :pdfs, foreign_key: :user_id, dependent: :destroy
has_many :pdf_ownings, :through => :pdf_relationships, :source => :owned
def owning?(owned)
pdf_relationships.find_by_pdf_owned_id(owned)
end
def own!(owned)
pdf_relationships.create!(:owned_id => owned.id)
end
def unown!(owned)
pdf_relationships.find_by_pdf_owned_id(owned).destroy
end
I hope this is clear enough. I've been trying to figure this out for sometime now and definitely trying to get past being just a novice. Suggestive readings are definitely welcome too!
It looks like you have the right idea.
Since LineItem has many PDFs, you can just use the pdf_id from the LineItem without bothering to fetch the record from the database. You can also append those IDs to the existing set of user-PDF associations as if you were just pushing items onto an array.
However, your model won't have access to the current session (since that's handled by the controller), so you'll have to pass the current user to the Order some other way, perhaps as part of the purchase method.
class Order
belongs_to :cart
def purchase(user)
# ... existing logic ...
user.pdf_relationship_ids << cart.line_items.map(&:pdf_id)
end
end
You will also have to declare the association between User and PDF and create the migrations, but it sounds like you're already planning to do that.
Update: I think you can greatly simplify your User model by taking advantage of has_many :through. Here's what I envision:
class User < ActiveRecord::Base
has_many :orders
has_many :line_items, :through => :orders
has_many :pdfs, :through => :line_items
end
class Order < ActiveRecord::Base
belongs_to :user
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
has_one :pdf
end
class Pdf < ActiveRecord::Base
belongs_to :line_item
end
Then you don't have to explicitly specify that a user owns a PDF. It's implicit in the user -> order -> line item -> PDF relationship.
This may not solve your problem as-is. You wouldn't be able to "disown" a PDF without deleting the original line item, which is probably not what you want. But I think you should try to aim for something like this. Take advantage of Rails's built-in associations as much as you can.

Resources