On creating a new user (in my user model) i want to create a stripe customer as well. The two actions must only be completed if they succeed together (like i don't want a customer without a user and vice versa). For this reason I figured it would be a good idea to wrap them in a transaction. However, I must not be doing it correctly. I do not believe I am properly overwriting the create method. If anyone has a suggestion as a better way to do this or what I am doing wrong it would be much appreciated. Thanks!
def create
User.transaction do
super
create_stripe_customer(self)
end
end
def destroy
User.transaction do
super
delete_stripe_customer(self)
end
end
I've done some research into your question and using after_create seems to be ok as long as an exception is raised if it fails. That will rollback the transaction as well. Just use the default callbacks.
Here is a good answer related to the question.
Related
I am creating a ActiveRecord transaction this way
ActiveRecord::Base.transaction do
MyModel.create!(name: "value")
OtherModel.create!(name: "value")
end
I'd want to set a specific after_commit callback to this transaction. I don't want to attach it to any one of the models because I don't it to be executed every time I create one of these separately.
I cannot simply write some code in the end of the transaction block either, because maybe the real transaction is created outside of this code (I am not setting the require_new option to true)
Is there any way to set callbacks to a specific transaction instance?
thank you very much
Please take a look at these gems:
after_commit_everywhere (disclaimer: I'm the author)
after_transaction_commit
Lets put a bit of context on this question. Given an Ecommerce application in Ruby on Rails. Let's deal with 2 models for example. User and CreditCard.
My User is in the system after a registration no issue there.
CreditCard is a model with the credit card information (yes I know about PCI compliance but that's not the point here)
In the Credit Card model, I include a callback after_validation that will do a validation of the credit card against your bank.
Let me put some simple code here.
models/user.rb
class User < ActiveRecord::Base
enum :status, [:active, :banned]
has_one :credit_card
end
models/credit_card.rb
class CreditCard < ActiveRecord::Base
belongs_to :user
after_validation :validate_at_bank
def validate_at_bank
result = Bank.validate(info) #using active_merchant by exemple
unless result.success
errors.add {credit_card: "Bank doesn't validate"}
user.banned!
end
end
end
controllers/credit_cards_controller.rb
class CreditCardsController < ApplicationController
def create
#credit_card = CreditCard.new(credit_card_params) # from Strong Parameters
if #credit_card.save
render #success
else
render #failure
end
end
end
What causing me issue
It look like Rails opens a transaction in ActiveRecord when I'm doing a new. At this point nothing is send to the database.
When the bank reject the credit card, I want to ban the user. I do this by calling banned! Now I realised this update is going to the same transaction. I can see the update, but once the save doesn't go though, everything is rollback from both models. The credit card is not saved (that is good), the user is not saved (this is not good since I want to ban him)
I try to add a Transaction wrapper, but this only add a database checkpoint. I could create a delayed job for the ban, but this seems to me to be overkill. I could use a after_rollback callback, but I'm not sure this is the right way. I'm a bit surprise, I never caught this scenario before, leading me to believe that my patern is not correct or the point where I make this call is incorrect.
After much review and more digging I came up with 3 ways to handle this situation. Depending on the needs you have, one of them should be good for you.
Separate thread and new database connection
Calling the validate function before the save explictly
Send the task to be perform to a Delayed Job
Separate thread
The following answer shows how to do this operation.
https://stackoverflow.com/a/20743433/552443
This will work, but again not that nice and simple.
Call valid? before the save
This is a very quick solution. The issue is that a new developer could erase the valid? line thinking that the .save will do the job properly.
Calling Delayed Job
This could be any ActionJob provider. You can send the task to banned the user in a separate thread. Depending on your setup this is quite clean but not everybody needs DelayedJob.
If you see anything please add it to a new solution of the comments.
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.
I have a simple model in RoR and I would like to keep eveything people enter on the site. But I also want to be able to hide some content if the user click on "Remove".
So I added a bolean attribute in my model called "displayed".
I would like to know, what would be the best-practices-styled method.
I guess I have to change the controller with something like :
def destroy
#point = Point.find(params[:id])
#point.displayed = false
#point.save
respond_to do |format|
format.html { redirect_to points_url }
format.json { head :no_content }
end
But I am not sure it is clean. What would be the best way to do it.
As you guess I am noobish with RoR. Chunks of code would be appreciated.
Thank you
Implement it yourself (rather than using a gem). It's much, much easier than it seems at first, and it's less complex than any of the gems out there that change the meaning of the destroy method, which is a bad idea, in my opinion.
I'm not saying that using the gems themselves are complex - I'm saying that by changing the meaning of the destroy method you're changing the meaning of something that people in the Rails world take for granted - that when you call destroy that record is going to go away and that destroy maybe also be called on dependent objects if they are chained together via dependent: destroy callbacks.
Changing the meaning of destroy is also bad because in the "convention over configuration" world, when you screw with conventions you're essentially breaking the "automagic-ness" of your Rails code. All that stuff you take for granted because you read a piece of Rails code and you know that certain assumptions generally apply - those go out the window. When you change those assumptions in ways that aren't obvious you're almost certain to introduce a bug down the line because of it.
Don't get me wrong, there's nothing better than actually reading the code for checking your assumptions, but it's also nice, as a community, to be able to talk about certain things and generally have their behavior act in a certain way.
Consider the following:
There's nothing in Rails that says you have to implement the destroy action in the controller, so don't. It's one of the standard actions, but it's not required.
Use the update action to set and clear an archived boolean attribute (or something similarly named)
I've used the acts_as_paranoid gem, and if you need to add any scopes to your models (other than the ones the gem provides) you're going to find yourself having to hack your way around it, turning off the default "hide archived records" scope, and when you run into that it almost immediately loses its value. Besides, that gem does almost nothing on its own, and its functionality could easily be written yourself (and I mean barely more work than installing the gem itself), so there's really no benefit to using it from that perspective.
As previously stated, overriding the destroy method or action is a bad idea because it breaks the Rails (and ActiveRecord) convention as to what it means to call destroy on an object. Any gem that does this (acts_as_paranoid for example) is also breaking that convention, and you're going to wind up confusing yourself or someone else because destroy simply won't mean what it's supposed to mean. This adds confusion, not clarity to your code. Don't do this - you'll pay for it later.
If you want to use a soft-delete gem because you are protecting against some theoretical, future developer who might hork up your data...well, the best solution to that is not to hire or work with those people. People that inexperienced need mentorship, not a gem to prevent them from making mistakes.
If you really, absolutely, must prevent destroying a record of a given model (in addition to being able to simply archive it), then use the before_destroy callback and simply return false, which will prevent it from being destroyed at all unless an explicit call to delete is used (which isn't the same as destroy anyway). Also, having the callback in place makes it (a) really obvious why destroy doesn't work without changing its meaning, and (b) it's easy to write a test to make sure it's not destroyable. This means in the future, if you should accidentally remove that callback or do something else that makes that model destroyable, then a test will fail, alerting you to the situation.
Something like this:
class Point < ActiveRecord::Base
def archive
update_attribute!(:displayed, false)
end
end
And then call #point.archive in the destroy action of your controller where you would normally call #point.destroy. You can also create a default_scope to hide archived points until you explicitly query for them, seethe RoR guide on appling a default scope.
Edit: Updated my answer as per normalocity & logan's comments below.
Look at the acts_as_archive gem. It will do soft deletes seamlessly.
Your solution is good, but you can use the acts_as_paranoid gem to manage that.
In this scenario, instead of adding a new boolean flag its better to added a deleted_at:datetime column
#point = Point.find(params[:id])
#point.touch(:deleted_at)
...
Then later
Point.where(deleted_at: nil) # these are NOT hidden
Point.where.not(deleted_at: nil) # these are hidden
I have a simple has_one/belongs_to relationship between two models.
This is a new association in my application so there are many records that do not yet have the associated record created.
Throughout my application I'm assuming the model has the association and I'm accessing its attributes and methods. However, because the association doesn't exist, I'm running into a lot of errors.
What I would like to do is unobtrusively build the associated record on the fly whenever it's access for the first time through any of its methods and attributes. It does not matter that there is data in record, I simply need it to exist so those methods I'm calling can build the data.
Edit: I do not want to check and create the record on all of the instances where I'm trying to access the relationship, so idealy this needs to be done on the model itself and not in my controllers anywhere.
Any thoughts?
Thanks!
Here's what we ended up with that did the trick. I didn't write it (a co-worker did) but it passes the previously failing tests that I wrote for this case.
def stats_with_create
stats_without_create || create_stats
end
alias_method_chain :stats, :create
In the controller, you could put something like this in the show method (untested, but it should give you an idea:
#thing = Thing.find params[:id]
if #thing.other_thing.nil?
#thing.other_thing = OtherThing.new.save!
#thing.save!
end
This isn't ideal, and you could probably clean it up a lot by putting a method in the Thing model that would check for and create the related model instead of putting it into your controller.
Another option would be to create a new accessor that you use to access the other_thing, creating it as required.
However, the correct thing to do is probably to fix your data, either in a migration or directly, creating the related models properly.
The direct answer is to override method for the relationship. When called it will check if the record exists and create it if it doesn't.
However, I would recommend that you use a migration to create all of the records up front.
I have done this type of thing before but not on the model level. Ive done it on the controller level with a before_filter that ran before all methods which needed to access the model association that did or did not exist yet.
I just realized there is the after_find and after_initialize callbacks that you can use in the model.
You could stick:
def after_initialize
association.build if association.nil?
end
in your model and it should solve your problems.. (disclaimer: untested by me) :)