I have the following classes:
class Account
has_many :payment_methods
has_many :orders
class PaymentMethod
belongs_to :account, inverse_of: :payment_methods
has_many :payments
class Order
belongs_to :account, inverse_of: :orders
has_many :payments
class Payment
belongs_to :order, inverse_of: :payments
belongs_to :payment_method, inverse_of: :payments
I have an order form that creates a payment as a child. That payment also has an associated payment_method. The idea is that placing an order will build the order and payment objects, the order will call payment.process (below), the payment will call a charge method on the payment_method object, and if all that passes, the payment and order are saved. Still with me?
Ok, so I need to validate at the payment level that both order and payment_method have the same parent (account), otherwise it needs to fail validation and not try to process. I have this method in the payment class:
def process
charge = payment_method.charge amount
self.is_paid = true
self.transaction_id = charge.id
rescue Exception => e
# handle exception
end
And this validation:
def payment_method_belongs_to_account
if payment_method and payment_method.account != order.account
# add error
end
end
I'm running into a few issues.
I am calling payment_method.charge before the payment is validated.
I can solve this by explicitly calling self.validate inside the payment.process method before calling payment_method.charge, but that leads to #2 below.
If I attempt to call validate on the payment object, I get an exception because payment.order is nil. The order hasn't been saved yet, so the reference inside the child object (payment) hasn't been set up.
It seems like solving for #2 is the easiest solution. I'm not sure why the reference from the child (payment) to the parent (order) isn't getting set up. Any suggestions?
I figured out the answer to my own question. I'll leave this here in case it can help someone else.
Adding this to my Order class fixed the problem:
has_many :payments, inverse_of: :order
It seems like that should happen automatically, but obviously not.
Related
I have three activerecord classes: Klass, Reservation and Certificate
A Klass can have many reservations, and each reservation may have one Certificate
The definitions are as follows...
class Klass < ActiveRecord::Base
has_many :reservations, dependent: :destroy, :autosave => true
has_many :certificates, through: :reservations
attr_accessible :name
def kill_certs
begin
p "In Kill_certs"
self.certificates.destroy_all
p "After Destroy"
rescue Exception => e
p "In RESCUE!"
p e.message
end
end
end
class Reservation < ActiveRecord::Base
belongs_to :klass
has_one :certificate, dependent: :destroy, autosave: true
attr_accessible :klass_id, :name
end
class Certificate < ActiveRecord::Base
belongs_to :reservation
attr_accessible :name
end
I would like to be able to delete/destroy all the certificates for a particular klass within the klass controller with a call to Klass#kill_certs (above)
However, I get an exception with the message:
"In RESCUE!"
"Cannot modify association 'Klass#certificates' because the source
reflection class 'Certificate' is associated to 'Reservation' via :has_one."
I('ve also tried changing the reservation class to "has_many :certificates", and then the error is...
"In RESCUE!"
"Cannot modify association 'Klass#certificates' because the source reflection
class 'Certificate' is associated to 'Reservation' via :has_many."
It's strange that I can do Klass.first.certificates from the console and the certs from the first class are retrieved, but I can't do Klass.first.certificates.delete_all with out creating an error. Am I missing something?
Is the only way to do this..
Klass.first.reservations.each do |res|
res.certificate.destroy
end
Thanks for any help.
RoR docs have clear explanation for this (read bold only for TLDR):
Deleting from associations
What gets deleted?
There is a potential pitfall here: has_and_belongs_to_many and
has_many :through associations have records in join tables, as well as
the associated records. So when we call one of these deletion methods,
what exactly should be deleted?
The answer is that it is assumed that deletion on an association is
about removing the link between the owner and the associated
object(s), rather than necessarily the associated objects themselves.
So with has_and_belongs_to_many and has_many :through, the join
records will be deleted, but the associated records won’t.
This makes sense if you think about it: if you were to call
post.tags.delete(Tag.find_by(name: 'food')) you would want the ‘food’
tag to be unlinked from the post, rather than for the tag itself to be
removed from the database.
However, there are examples where this strategy doesn’t make sense.
For example, suppose a person has many projects, and each project has
many tasks. If we deleted one of a person’s tasks, we would probably
not want the project to be deleted. In this scenario, the delete
method won’t actually work: it can only be used if the association on
the join model is a belongs_to. In other situations you are expected
to perform operations directly on either the associated records or the
:through association.
With a regular has_many there is no distinction between the
“associated records” and the “link”, so there is only one choice for
what gets deleted.
With has_and_belongs_to_many and has_many :through, if you want to
delete the associated records themselves, you can always do something
along the lines of person.tasks.each(&:destroy).
So you can do this:
self.certificates.each(&:destroy)
In a small app I am building, I have a controller that creates an exchange. When a user creates an exchange they are simultaneously the organizer of the exchange and a participant in the exchange. Participants are tracked by a join table that joins a user_id and an exchange_id. Organizers are tracked by a foreign user_id key in the exchange table.
I am trying to figure out where to put the code that will automatically create a new membership record for the organizer of the exchange. Should I put this in the exchange_controller's create action itself, or in an after_filter triggered by the create action? Or maybe somewhere else? Part of the problem is that I could not find any good examples of proper after_filter use (guides.rubyonrails.org only had sparse mention of it), so any links pointing in the correct direction would be appreciated as well.
Here is relevant model code:
app/models/user.rb:
# Returns array of exchanges user is participating in
has_many :participations,
:through => :memberships,
:source => :exchange
# Returns array of exchanges user has organized
has_many :organized_exchanges,
:foreign_key => :organizer_id,
:class_name => "Exchange"
app/models/membership.rb:
class Membership < ActiveRecord::Base
attr_accessible :exchange_id, :user_id, :role
belongs_to :exchange
belongs_to :user
end
app/modles/exchange.rb:
belongs_to :organizer,
:foreign_key => :organizer_id,
:class_name => "User"
has_many :memberships, :dependent => :destroy
has_many :participants,
:through => :memberships,
:source => :user
And here is the relevant controller code:
app/controllers/exchanges_controller.rb:
def create
#exchange = Exchange.new(params[:exchange])
#exchange.organizer_id = current_user.id
if #exchange.save
redirect_to exchange_path(#exchange.id)
else
render 'new'
end
end
after_filter is a completely different thing in this context. It is called when your view is completely processed and so you want to call some action to do something.
You can use after_create callback that is triggered when a record is created in the database.
In your case, a user is creating an exchange and so after the exchange is created, the after_create callback is triggered and you can apply your functionality over there to make the current user who created the exchange to be a participant.
The way to write in a model is like this:
after_create :do_something
def do_something
something.do!
end
Note: It is not good to use after_save here because it is triggered every time you save a record or even if you update a record.
There is a nice SO post that clearly tells you the difference between the after_create and after_save.
See this SO post for the difference between the two.
More on the callbacks is here.
I have a customer model that has_many phones like so;
class Customer < ActiveRecord::Base
has_many :phones, as: :phoneable, dependent: :destroy
accepts_nested_attributes_for :phones, allow_destroy: true
end
class Phone < ActiveRecord::Base
belongs_to :phoneable, polymorphic: true
end
I want to make sure that the customer always has at least one nested phone model. At the moment I'm managing this in the browser but I want to provide a backup on the server (I've encountered a couple of customer records without phones and don't quite know how they got like that so I need a backstop).
I figure this must be achievable in the model but I'm not quite sure how to do it. All of my attempts so far have failed. I figure a before_destroy callback in the phone model is what I want but I don't know how to write this to prevent the destruction of the model. I also need it to allow the destruction of the model if the parent model has been destroyed. Any suggestion?
You can do it like this:
before_destory :check_phone_count
def check_phone_count
errors.add :base, "At least one phone must be present" unless phonable.phones.count > 1
end
I have a simple set up of User and UserProfile model with User has_one :user_profile and UserProfile belongs_to :user.
But I am unable to wrap my head around how Rails defines execution order of after_create callback and accepts_nested_attributes_for defined in my model. Lets consider these two cases.
Case 1:
class User < ActiveRecord::Base
has_one :user_profile
accepts_nested_attributes_for :user_profile
after_create :test_test
end
Now, if I create a user(with user_profile_attributes hash too) via the console, the after_create callback is triggered after the user and its user profile is created.
Case 2:
If the after_create is placed at the top,
class User < ActiveRecord::Base
after_create :test_test
has_one :user_profile
accepts_nested_attributes_for :user_profile
end
the callback is triggered after a user has been created but before creating a user profile.
Is this the way it is expected to function. What does Rails do internally here? Is the execution sequence simply determined by the order of the code?
Where do I start to dig deeper into or debug this ?
The order of the declarations in your model can have an impact on the execution order of the code. This is a source for various weird things. (for example, currently callback definitions and has_and_belongs_to_many associations are order dependent: https://github.com/rails/rails/pull/8674 )
To debug the issue you need to browse the rails source. Since your problem has to do with execution order, callbacks and nested attributes I would start by reading up on:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L256
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/callbacks.rb#L302
https://github.com/rails/rails/blob/master/activemodel/lib/active_model/callbacks.rb#L98
This gives you the necessary background to dig deeper. You'll notice that accepts_nested_attributes_for calls into add_autosave_association_callbacks https://github.com/rails/rails/blob/master/activerecord/lib/active_record/autosave_association.rb#L173
This method adds an after_create callback and as far as I know callbacks are executed in order of definition.
I have a User model with devise, a Team model, a Player model and a Trainer model. I want that when the User signs up, its associated team is created along with the associated trainer and players. I have set up the models as below:
user.rb
has_one :team
has_one :trainer, :through => :team
has_many :players, :through => :team
accepts_nested_attributes_for :team, :allow_destroy => true
before_save :create_team
def create_team
#team = Team.new(params[:team])
#team.user = self
#team.save
end
team.rb
belongs_to :user
has_one :trainer
has_many :players
accepts_nested_attributes_for :trainer, :allow_destroy => true
accepts_nested_attributes_for :player, :allow_destroy => true
trainer.rb and player.rb
belongs_to :team
I have not added the create_trainer and create_player functions, since I want the user to select them later in the game. So they should be empty during the creation of the user.
But the sign up process gives the following error:
No association found for name `trainer'. Has it been defined yet?
and refers to the line:
accepts_nested_attributes_for :trainer, :allow_destroy => true
in team.rb. What is wrong with having the Trainer item not defined yet, if there is no validation of presence of Trainer defined in the Team model? I tried adding some lines to the Trainer model, to set the attributes to default values like:
morale.default => (5..12).to_a.sample
but it gave further errors, so is probably wrong. Any comment is greatly appreciated, especially anything criticising the basis of tinking here, since I am a noob.
A few things:
Dont use instance variables in your model. To access the team in the user model, just do team or self.team.
Don't use before_save since you don't want to create the team each time you save your user.
Your create_team method should be:
after_create :my_create_team
def my_create_team
create_team #create an empty team
end
But if the data for the new team is already present in the form when the user signs up, then the team should automatically be created since you have accepts_nested_attributes_for :team.
I'm going to answer some of your questions in the comments here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one
So when you add has_one :team you have now access to all these methods (build_team, create_team, team= etc)
By "empty", I just meant that if you just call create_team without any attributes, it would create a "default" team: no name etc. But it would be linked to your user though.
If you wanted to just create an "empty" team, you could just do this I think:
after_create :create_team
Creating your own method would just allow you to pass default parameters.
But you have probably added validation to the team, like validating the presence of its name.
Anyways, since you have accepts_nested_attributes_for :team, :allow_destroy => true, it should create the team automatically if you have the required fields for the user's team in the sign up form.