Skip to content

Instantly share code, notes, and snippets.

@esbanarango
Last active December 20, 2015 14:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save esbanarango/6149616 to your computer and use it in GitHub Desktop.
Save esbanarango/6149616 to your computer and use it in GitHub Desktop.
Accessing and editing an attribute from has_many :through join model.

Accessing and editing attributes from has_many :through join model.

This a solution for a problem that I recently faced.

Let's say we have these three classes Store, PaymentMethod and StorePaymentMethodSetting. A store may have multiple payment methods and these payment methods are similar between the stores. So here we have a HABTM (has and belongs to many) relationship between the Store and the Payment Method. I didn't use HABTM relationship, instead, I used has_many :through relationship, that's because I needed to use the relationship model (StorePaymentMethodSetting) as an independent entity to set the availability of a payment method in a store. Although I would always recommend using has_many :through instead of has_and_belongs_to_many, you never know if in the future you'll need the relationship model to have some attributes.

So here comes the problem, I wanted to update the availability of a payment method through the payment method itself. I mean, I didn't wanted to have to use the StorePaymentMethodSetting model.

###Example:

I have this route to update a payment method on a store:

PUT /api/stores/:store_id/payment_methods/:id

I wanted to be able to update the availability for a payment method through this route without having to use the StorePaymentMethodSetting model.

# /api/stores/14/payment_methods/6
{
  payment_method:{
    available: true
  } 
}

To accomplish this I needed to have the available attribute within the payment method when I call it through the store. i. e.

s = Store.last
pm = s.payment_methods.first
pm.available # true or false

Then the solution that I end up with was selecting those attributes that I would need when defining the relationship with payment_methods in the Store.

  has_many :store_payment_method_settings, class_name: "StorePaymentMethodSetting", :dependent => :destroy
  has_many :payment_methods, class_name: "PaymentMethod", :through => :store_payment_method_settings, autosave: true,
             :select => "payment_methods.*, store_payment_method_settings.available AS available, store_payment_method_settings.id AS relation_id"

Thus I would use them on a callback (before_save) in the PaymentMethod class.

If you have a better solution, I would be happy to hear it. So please comment :).

Below I leave the real classes.

class PaymentMethod < ActiveRecord::Base
acts_as_api
include Response::PaymentMethod
set_inheritance_column :none
scope :default, where(:default => true)
has_translations_for :name, :description
has_many :store_payment_method_settings, :dependent => :destroy
has_many :stores, :through => :store_payment_method_settings
before_save :update_availability
# more code...
private
# This method is used to be able to update the availability for the has_many through relation.
def update_availability
if respond_to?("available") and respond_to?("relation_id")
pms = store_payment_method_settings.find(relation_id)
s = pms.store
if type == 'Paypal'
# Only make PayPal Payment Method available if the Store has a verified PayPal account
if !available or (s.paypal_account and s.paypal_account.verified)
pms.available = available
pms.save!
else
errors.add("",I18n.t(:exception_ordering_store_not_configured_message))
end
else
pms.available = available
pms.save!
end
end
end
end
class Store < ActiveRecord::Base
acts_as_api
include Response::Store
# more code...
has_many :store_payment_method_settings, class_name: "StorePaymentMethodSetting", :dependent => :destroy
has_many :payment_methods, class_name: "PaymentMethod", :through => :store_payment_method_settings, autosave: true,
:select => "payment_methods.*, store_payment_method_settings.available AS available, store_payment_method_settings.id AS relation_id"
# more code...
end
# == Schema Information
#
# Table name: store_payment_method_settings
#
# id :integer not null, primary key
# payment_method_id :integer
# store_id :integer
# available :boolean default(TRUE)
#
class StorePaymentMethodSetting< ActiveRecord::Base
belongs_to :store
belongs_to :payment_method
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment