I am trying to assign session values to model object as below.
# models/product.rb
attr_accessor :selected_currency_id, :selected_currency_rate, :selected_currency_icon
def initialize(obj = {})
selected_currency_id = obj[:currency_id]
selected_currency_rate = obj[:currency_rate]
selected_currency_icon = obj[:currency]
end
but this works only when I initialize new Product object
selected_currency = (session[:currency].present? ? session : Currency.first.attributes)
Product.new(selected_currency)
While, i need to set these setter methods on each product object automatically even if was fetched from Database.(active record object) ie. Product.all
or Product.first
Earlier i was manually assigning values to each product object after retrieving it from db on controller side.
@products.each do |product|
product.selected_currency_id = session[:currency_id]
product.selected_currency_rate = session[:currency_rate]
product.selected_currency_icon = session[:currency]
end
But then i need to do it on every method where product details need to be displayed. Please suggest a better alternative to set these setter methods automatically on activerecord objects.
I don't think you really want to do this on the model layer at all. One thing you definitely don't want to do is override the initializer on your model and change its signature and not call super.
Your model should only really know about its own currency. Displaying the price in another currency should be the concern of another object such as a decorator or a helper method.
For example a really naive implementation would be:
class ProductDecorator < SimpleDelegator
attr_accessor :selected_currency
def initialize(product, **options)
# Dynamically sets the ivars if a setter exists
options.each do |k,v|
self.send "#{k}=", v if self.respond_to? "#{k}="
end
super(product) # sets up delegation
end
def price_in_selected_currency
"#{ price * selected_currency.rate } #{selected_currency.icon}"
end
end
class Product
def self.decorate(**options)
self.map { |product| product.decorate(options) }
end
def decorate(**options)
ProductDecorator.new(self, options)
end
end
You would then decorate the model instances in your controller:
class ProductsController
before_action :set_selected_currency
def index
@products = Product.all
.decorate(selected_currency: @selected_currency)
end
def show
@product = Product.find(params[:id])
.decorate(selected_currency: @selected_currency)
end
private
def set_selected_currency
@selected_currency = Currency.find(params[:selected_currency_id])
end
end
But you don't need to reinvent the wheel, there are numerous implementations of the decorator pattern like Draper and dealing with currency localization is complex and you really want to look at using a library like the money gem to handle the complexity.