before_create still saves - ruby-on-rails

Before everything i would like to thank you for your help
I have a model like this:
attr_protected nil
belongs_to :product
belongs_to :user
before_create :add_ammount
def carted_product_price(ammount, price)
ammount * price
end
def add_ammount
carted_product = CartedProduct.where(:product_id => self.product_id, :user_id => self.user_id)
if carted_product
carted_product.first.ammount += self.ammount
carted_product.first.update_attributes(:ammount => carted_product.first.ammount)
else
self.save
end
end
it saves buying orders in a table called Carted_Products connected to Users and Products in the belogings
the problem is that when the Before create executes i want it to update the record in the table adding the ammount passed by the controller if the record already exists and if not, create one, as far as iv done, it updates the ammount but STILL CREATES A NEW one with the passed params in the order, i want it only to update, not to do both actions when the record is found
thnx for your patience
EDIT:
Tried returning false after the update attributes, it cancels the filter, and dont create or update attributes

Return false in the before_create filter to prevent the object form being saved. add_amount is not responsible for saving the object, and shouldn't call save by itself.

You cannot do this in before_create filter. You need to fetch existing CartedProduct in controller where you're calling create.

Related

Increase a counter in parent when a child is added in has_many association

I have a schema where User has many Student with a user_id field.
In the User table, I am saving a counter next_student_number with default value as 1, and there is a roll_number column in Student.
In the Student class, I have before_create :generate_roll_number callback which sets the student's roll number to next_student_number and increments the value in User class.
Some thing like this :-
def generate_roll_number
self.roll_number = user.next_roll_number
user.increment! :next_roll_number
end
I feel there will be an issue when two records are trying to save at the same time here. Either they'll have a clash, or some roll numbers will be skipped.
What is the best way to implement this?
I think this should work fine:
Controller
def create
Student.transaction do
Student.create(user_id: current_user, ...)
end
end
Student Model
before_create :generate_roll_number
def generate_roll_number
user.increment! :next_roll_number
# Fires query like
# UPDATE users SET next_roll_number=2, WHERE id=xxx
self.roll_number = user.next_roll_number
end
Now, if any error happens while Student record is saved, the transaction will also rollback the incremented next_roll_number value in User table

Use new attribute values to validate before_update

In my project, I have a model called PaymentCondition and another called PaymentPortion.
PaymentCondition has_many payment_portions and PaymentPortion belongs_to payment_condition.
When I create a new PaymentCondition, I have this method that creates n payment_portions. Being n the value of a :amount attribute from PaymentCondition.
If I create a new PaymentCondition with amount: 2, for instance, I'd have 2 payment_portions.
So far, so good.
My problem:
I'm using a nested form to this view, so I can edit everything at once.
PaymentCondition has a attribute called catchments.
PaymentPortion has a attribute called catchment.
Before submiting this form, I'd like to check if the sum of PaymentPortion.catchment is equal to PaymentCondition.catchments. If not, I must raise an error.
As for now, I can't get the new values of PaymentCondition before saving it...
I'm doing this inside payment_conditions_controller:
before_update :check_catchments
def check_catchments
errors.add(:catchments, "Values must check") unless catchments_check? || new_record?
end
def catchments_check?
catchment == portion_catchments
end
def portion_catchments
payment_portions.sum(:catchments)
end
Using sum(), I get only the values that are on the database. What I need are the values that are being send...
Is there a way to do so?
Thanks in advance.
May be working directly in the controller is an option? You can use methods like 'build' or 'first_or_initialize' to get\create AR objects. And then just validate them in the controller and save the data if necessary.

Rails preview update associations without saving to database

I want to preview what the model will look like when saved without currently saving to the database.
I am using #event.attributes = because that assigns but does not save attributes for #event to the database.
However, when I also try to assign the audiences association, Rails inserts new records into the audiences_events join table. Not cool. Is there a way to preview what these new associations will look like without inserting into the join table?
Model
class Event < ActiveRecord::Base
has_and_belongs_to_many :audiences # And vice versa for the Audience model.
end
Controller
class EventsController < ApplicationController
def preview
#event = Event.find(params[:id])
#event.attributes = event_params
end
private
def event_params
params[:event].permit(:name, :start_time, :audiences => [:id, :name]
end
end
Possible Solutions?
Possible solutions that I thought of, but don't know how to do:
Using some sort of method that assigns associations, but does not persist them.
disabling all database writes for this one action (I dont know how to do that).
Rolling back all database changes at the end of this action
Any help with these would be great!
UPDATE:
After the reading the great answers below, I ended up writing this service class that assigns the non-nested attributes to the Event model, then calls collection.build on each of the nested params. I made a little gist. Happy to receive comments/suggestions.
https://gist.github.com/jameskerr/69cedb2f30c95342f64a
In these docs you have:
When are Objects Saved?
When you assign an object to a has_and_belongs_to_many association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.
If you want to assign an object to a has_and_belongs_to_many association without saving the object, use the collection.build method.
Here is a good answer for Rails 3 that goes over some of the same issues
Rails 3 has_and_belongs_to_many association: how to assign related objects without saving them to the database
Transactions
Creating transactions is pretty straight forward:
Event.transaction do
#event.audiences.create!
#event.audiences.first.destroy!
end
Or
#event.transaction do
#event.audiences.create!
#event.audiences.first.destroy!
end
Notice the use of the "bang" methods create! and destroy!, unlike create which returns false create! will raise an exception if it fails and cause the transaction to rollback.
You can also manually trigger a rollback anywhere in the a transaction by raising ActiveRecord::Rollback.
Build
build instantiates a new related object without saving.
event = Event.new(name: 'Party').audiences.build(name: 'Party People')
event.save # saves both event and audiences
I know that this is a pretty old question, but I found a solution that works perfectly for me and hope it could save time to someone else:
class A
has_many :bs, class_name 'B'
end
class B
belongs_to :a, class_name: 'A'
end
a.bs.target.clear
new_bs.each {|new_b| a.bs.build new_b.attributes.except('created_at', 'updated_at', 'id') }
you will avoid autosave that Rails does when you do a.bs = new_bs

Rails: set default record if none assigned or if relation removed

I've got a model called Brand, on which several things rely including in this example a model called User. If a Brand is deleted then a lot of things will fail. What's the best way to set a default Brand for all its relationships in the event that a Brand is deleted?
I thought writing stuff like this might work:
class User < ActiveRecord::Base
after_save :assign_to_default_brand, :if => :not_branded?
def not_branded?
!self.brand_id?
end
def assign_to_default_brand
self.brand_id = Brand.first
end
end
But it doesn't seem to behave the way I want it to. Is there a best-practice established here? Cheers.
UPDATED
I've thrown a default boolean onto Brand and written this but again it seems to have no effect. Am I missing something?
class Brand < ActiveRecord::Base
after_save :assign_users_to_default
def assign_users_to_default
self.users.all.each { |user| user.brand_id = Brand.where(:default => true).first.id if user.not_branded? }
end
end
It should be a before_save instead of after_save That way the value will be persisted to the database when the instance is saved.
For deletion on a brand you could use after_destroy
class Brand
after_destroy :switch_assigned_users
def switch_assigned_users
User.where(:brand_id => id).update_all(:brand_id => Brand.first)
end
end
This finds all users that assigned to that brand and switches them to the first one.

How do I change an ActiveRecord from marked to be saved to make sure it does not get saved, from within the model itself?

How do I change an AcriveRecord from marked to be saved to make sure it does not get saved, from within the model itself?
Considering I can have a method run by a hook in activerecord, such as: before_save
for (hypothetical) example:
before_save :ignore_new_delete_exisiting_if_blank(self.attribute)
def ignore_new_delete_exisiting_if_blank(attribute)
self.do_not_save_me! if attribute.blank?
#what is that magic "do_not_save_me" method?
#Is there such thing, or something to achieve the same thing?
end
Update
My particular use case requires that no errors be thrown and other models to continue to be saved, even if this one will not. I should explain:
I am using model inheritance, and I am having an issue with figuring out how to let save the parent model, but if the child model instances are blank, (no values exist in certain attributes) they should not be persisted; however, the parent should still be persisted. This scenario does not let me make use of validations on the child model as that would block the parent from being persisted as well...
Your method should just return false to make it does not save.
Or you set the errors, which will allow to be more descriptive.
For example:
def ignore_new_delete_exisiting_if_blank_attribute
if attribute.blank?
errors.add(:base, "Not allowed to save if attribute is blank.")
end
end
Note that you cannot send parameters to a before_save. If you just want to make sure a record is not saved when an attribute is not present, you should use
validates_presence_of :attribute
[UPDATE]
When saving a parent model with children, you have to do something like accepts_nested_attributes_for, and in that call, you can specify which attributes must be given or when a child-record is ignored.
For example
accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
will not save a post if the title is blank.
Hope this helps.
The "magic" is that when you return false from the method, the record won't be saved.
In your case:
def ignore_new_delete_exisiting_if_blank(attribute)
attribute.present?
end

Resources