Rails 4: Create parent only if nested model is saved - ruby-on-rails

Well, not a good title but here is the problem.
[Question updated]
I have two models, Word and Definition. When the user looks up a word the definitions are enlisted and there should be a form below the definitions so that the user can contribute by adding up another definition. So far no problem. But if the search returns no result, I will ask the user to create Word along with its first definition.
I do not know how to deal with the form and logic of the problem. It is more than a nested form. Because something like form_for [#word, #word.definitions.build] do |form| would not work since there is no #word object to which the/a new definition can be referred.
Addendum:
I seem to find a way here. It just works but not so clean to me. If you think there is a better solution please share it anyway.

My approach would be to implement a form object (RailsCast). I'd use a transaction so I'm not left with any orphan database records.
In the submit method of your form object:
def submit
if dictionary_item.present?
# just save the entry
else
# start a transaction so both operations will either succeed or fail
ActiveRecord::Base.transaction do
# save the new dictionary_item
# save the entry
end
end
# return true if the objects are valid and persisted, false otherwise
end
Make sure to call save! or create! inside the transaction. The bangs are important, because an error has to be raised for the transaction to trigger a rollback.

Related

How to save an active record object when its association has been deleted?

I have a model named Company along with many associations, and it's an association having many more. (with dependent destroy)
com = Company.find_by(name: 'ABC')
I then destroy the entry by
Company.find_by(name: 'ABC').destroy
Now 'com' has the record, and when I perform com.save the object does not get stored along with its association.
Note
I am trying to debug as to where my destroy query is taking time (it has hundreds of associations), so I don't want to lose the data, I want to save it in the console after debugging.
The best approach would be to, one way or another, wrap the action in a database transaction. You can then rollback the transaction to restore all the data to its original state.
To write this explicitly, you could do something like:
ActiveRecord::Base.transaction do
Company.find_by(name: 'ABC').destroy
# Any debugging can be done here, or above
raise ActiveRecord::Rollback
end
In fact, rails actually provides a debugging tool for precisely this use case: You can make any changes to the database and have everything rolled back (inside a transaction, like above) by running:
rails console --sandbox

Rails - detach model from database session

I came across a scenario where in other languages I would detach the model object from the transaction then I can alter it all I want without worry of an automatic-update to the record.
Question
Does rails not support attaching\detaching a model object?
What is the alternative, just duplicate the object?
EDIT
Scenario
We are reading models out of the database and we want to make changes to them that will not be persisted to the database at the end of the transaction. In Hibernate\JPA etc you detach the model (Entity) and no changes will be persisted.
Now you may ask why not use Model.dup? The answer is that we still need the id of the model but as soon as you assign the id, rails believes this instance is now the model and updates the record at the end of the transaction.
Thanks
You can totally change Rails model instance attributes without persisting the changes.
There are a couple of methods to change model attributes, some of which do automatically persist the changes to the DB and others just change the attribute values of the in-memory instance.
You may want to try using #assign_attributes or #<attribute>= from the above list.
Only after explicitly calling #save afterwards, the changes will be saved to the database.
As stated in a previous answer, you can update instance attributes without persisting them (by avoiding self.save for example). But if you want to be sure, consider the following that uses a validation to check for a flag attribute being nil (or blank):
attr_accessor :prevent_save
validates :prevent_save, absence: true
def prevent_save!
self.prevent_save = true
end
def do_something_safely
prevent_save!
self.other_attr = 'abc'
end
def accidentally_save
# if prevent_save! has been previously called,
# validations will fail, and save! will raise an exception
self.save!
end

Rails - how to save existing record and apply update to a copy?

I have a webpage that tracks budgets containing a LOT of variables, stored in 40+ columns. Over time, adjustments are made to these budgets, but I need to be able to track changes over time and year to year. I tried adding a private method to my model that should create a duplicate of the existing record triggered by a :before_update callback. However, it's not working. The update changes the existing record, and the original is not preserved at all.
Model:
class Budget < ActiveRecord::Base
before_update :copy_budget
private
def copy_budget
#budget = Budget.find(params[:id])
#budget.dup
#budget.save
end
end
I'm still learning rails, (this is in Rails 4) and I think this would have been the best way to do this. If not, is there a better way to set the form to ALWAYS post a new record instead of routing to update if a record already exists?
Currently the form_for line looks like this:
<%= form_for(#budget) do |f| %>
Everything works as it should, with the exception of the duplication not happening. What am I missing? Is it possible the .dup function is also duplicating the :id? This is assigned by auto-increment in the MySQL db I an using, so if .dup is copying EVERYTHING, is there a way to copy all of the data except the :id into a new record?
Thanks in advance for any suggestions.
the dup method returns the new object without an id, it doesn't update it in place. Since your copy_budget method is already an instance method on Budget, you also would not need to (and you wouldn't even be able to, since params aren't accessible in models) look up the budget by id and instead could just use the current instance (self). So the following changed would fix the copy_budget method for you, but you are still copying an already modified object, just before it gets saved to the database
def copy_budget
copy_of_budget = self.dup
copy_of_budget.save
end
it would work the way you're expecting it to work. However, you aren't linking the copy in anyway to the current version of the Budget (no way to tell Budget id = 1 is an older version of Budget id = 2). I'd recommend taking a look at a gem such as PaperTrail (I'm sure there are lots of others if that one doesn't suit your needs) which has already thought through a lot of the problems and features with keeping a history of record changed.

can find_or_create_by_name update?

when using find_or_create_by_name in rails, if the table is found and you pass in parameters for other attributes, will it update the table? for example:
College.find_or_create_by_name(name: 'University of Pittsburgh', calendar: 'semester')
Say the University of Pittsburgh table has already been created, but the calendar attribute is nil. Will this code update the calendar attribute to make it 'semester'?
Some context... I'm making a website with a bunch of pages for different colleges. Part of the website involves listing a bunch of data for the college. Right now I'm writing my seed file, but I anticipate having to change it. I'd like to have hundreds of schools on the website, and for each school, there are going to be hundreds of different pieces of data. I was thinking that having the seed file and using find_or_create_by_name would be a good way to do this, but if you have a better way, please let me know.
That code won't update the record if it already exists. I would suggest:
#college = College.find_or_initialize_by_name("Robot House!!")
#college.attributes = {
reputation: "partyhouse",
occupants: "robots"
}
#college.save!
You could wrap this in a method if you needed to.
find_or_create_by_ does exactly what it says: it either creates a new item (create saves to database) or finds the existing one, meaning reads it from the database.
It returns false on validation errors when creating an object
So to save changes you use the normal update methods:
if #college= College.find_or_create_by_name(given_attributes) &&
#college.update_attributes(given_attributes)
else
# handle validation errors
end
It won't hit the database twice, because update_attributes does'n apply any changes to newly created objects (but possible changes to existing ones)
To write it more explicit:
#college= College.find_or_create_by_name(given_attributes)
if #college.present?
if #college.update_attributes(given_attributes)
# do your success stuff
else
# handle update validation errors
end
else
# handle find_or_create errors
end

Which rails ActiveRecord callback to sync with service (stripe) when creating a new record and still properly use errors?

I have a User and a StripeCustomer model. Every User embeds one and accepts_nested_attributes_for StripeCustomer.
When creating a new user, I always create a corresponding StripeCustomer and if you provide either a CC or a coupon code, I create a subscription.
In my StripeCustomer:
attr_accessible :coupon_id, :stripe_card_token
What I'd like to do is, if the coupon is invalid, do:
errors.add :coupon_id, "bad coupon id"
So that normal rails controller patters like:
if #stripe_customer.save
....
else
....
end
will just work. And be able to use normal rails field_with_errors stuff for handling a bad coupon.
So the question is, at which active record callback should I call Stripe::Customer.create and save the stripe_customer_token?
I had it on before_create, because I want it done only if you are really going to persist the record. But this does strange things with valid? and worse, if you are going to create it via a User, the save of User and StripeCustomer actually succeeds even if you do errors.add in the before_create callback! I think the issue is that the save will only fail if you add errors and return false at before_validation.
That last part I'm not sure if it is a mongoid issue or not.
I could move it to before_validation :on => :create but then it would create a new Stripe::Customer even if I just called valid? which I don't want.
Anyway, I'm generically curious about what the best practices are with any model that is backed by or linked to a record on a remote service and how to handle errors.
Ok here is what I did, I split the calls to stripe into 2 callbacks, one at before_validation and one before_create (or before_update).
In the before_validation, I do whatever I can to check the uncontrolled inputs (directly from user) are valid. In the stripe case that just means the coupon code so I check with stripe that it is valid and add errors to :coupon_code as needed.
Actually creating/updating customers with stripe, I wait to do until before_create/before_update (I use two instead of just doing before_save because I handle these two cases differently). If there is an error then, I just don't handle the exception instead of trying to add to errors after validation which (a) doesn't really make any sense and (b) sort of works but fails to prevent saves on nested models (in mongoid anyway, which is very bad and strange).
This way I know by the time I get to persisting, that all the attributes are sound. Something could of course still fail but I've minimized my risk substantially. Now I can also do things like call valid? without worrying about creating records with stripe I didn't want.
In retrospect this seems pretty obvious.
I'm not sure I totally understand the scenario. you wrote:
Every User embeds one and accepts_nested_attributes_for StripeUser
Did you mean StripeCustomer?
So you have a User that has a Customer that holds the coupon info?
If so, I think it should be enough to accept nested attributed for the customer in the user, put the validation in the customer code and that's it.
See here
Let me know if I got your question wrong...

Resources