0

In my Application Controller I set up a currency variable based on geolocation:

class ApplicationController < ActionController::Base

  before_action :currency

  protected

  def currency
    cookies[:country] ||= request.location.country
    case cookies[:country]
    when "MY"
      c = "MYR"
    when "SG"
      c = "SGD"
    else
      c = "USD"
    end
    @currency = Currency.find_by(name: c)
  end
end

I have Model Product with price method and many currencies, many prices, ie: one product can have multiple currencies with custom pricing.

class Product < ApplicationRecord
  has_many :prices
  has_many :currencies, through: :prices

  def price
    # how to access @currency?
  end

end

class Price < ApplicationRecord
  belongs_to :product
  belongs_to :currency
end

class Currency < ApplicationRecord
  has_many :prices
  has_many :products, through: :prices
end

What is the best way to access @currency in the Model Product.price? Or how can I tell the method to return the price only in the @currency? This is probably not the best way to deal it so please advice.

Petr
  • 1,853
  • 2
  • 25
  • 49
  • I believe [this](https://stackoverflow.com/questions/2419120/ruby-on-rails-access-controller-variable-from-model/2420015#2420015) might help you – rowinbot Jul 01 '18 at 03:13

3 Answers3

3

You have things a little backwards so you're trying to solve the wrong problem. Models shouldn't be trying to get information out of the controller layer, the controller should be sending that information into the model:

class Product < ApplicationRecord
  #...
  def price_in(currency)
    # Access the associations however you need to and handle missing
    # information however fits your application in here...
  end
end

and then in your controller or view:

price = product.price_in(@currency)

You should be able to call methods on your models from anywhere (controllers, rake tasks, jobs, the console, ...) without having to worry about all the request-specific state.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
1

You shouldn't. You're violating a lot of design principles by doing so, and you'll only frustrate yourself down the road.

Your model shouldn't care about the context of your controller. It should only care about data as it relates to itself.

What you can do, though, is use the ActiveModel::Attributes API.

In your model:

class Product < ApplicationRecord
  has_many :prices
  has_many :currencies, through: :prices

  attribute :currency 

  def price
    self.currency 
  end

end

In your controller:

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
    @product.currency = @currency 
  end 
end

There's a lot more you can do with the ActiveModel::Attributes API, like set defaults, run validations, and even set what type of object it is (boolean/true/false, integer, string, etc.) — it behaves just like your regular attributes do on a model, they just aren't backed by your database.

More info on this great API https://apidock.com/rails/ActiveRecord/Attributes/ClassMethods/attribute

Josh Brody
  • 5,153
  • 1
  • 14
  • 25
0

Take a look at this answer for info on how to access cookies in a model. But you should consider moving this method out of the controller and into the Currency class, which seems like a more logical place for it. Then you can call the method from your Product class as Currency.get_currency, for example.

jesellers
  • 36
  • 3