1

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.

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a lot. I wish i could have think that way, i'd have saved 20 hours.
It's all part of the process my friend.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.