I'm working on exposing some data to a mobile client through the API. Some of that data is defined in a decorator.
app/decorators/customer_opportunity_decorator.rb
def potential_savings
return 'N/A' if opportunity.nil?
multiplier = Customer.get_multiplier(...)
return 'N/A' if multiplier.blank?
...more logic
end
We're using Grape Entities in our API layer and I'm not sure about how to pass this logic through the API. It doesn't seem like it should live in a decorator if being exposed as an API field.
module Entities
class Opportunity < BaseEntity
expose :id
expose :name
expose :potential_savings # need this field on the API
...
And then there's an api model ApiOpportunity
class ApiOpportunity
attr_reader :id, :name, :potential_savings
def initialize(opportunity)
#id = opportunity.id
#name = opportunity.name
#potential_savings = opportunity.potential_savings # this approach doesn't seem to work
end
end
Is this logic that I should move elsewhere? Is it fine to remain as a decorator and somehow pass through in the API?
Related
I have an ActiveRecrod model User and a separate class UsersFilter, which is solely used for filtering the model. Say UsersFilter accepts a parameter hash params = {min_age: '18', max_age: '30', admin: 'true'}. All the values are strings. If I pass these values directly to ActiveRecord, the queries will work. However, I also want to be able to use these values in my code, so that I can build some logic around it. So in order to do that, I need to manually type cast these values. So UsersFilter might look like this:
class UsersFilter
include ActiveRecord::AttributeAssignment
attr_accessor :min_age, :max_age, :admin
def initialize(params)
params[:min_age] = params[:min_age].to_i
params[:max_age] = params[:min_age].to_i
params[:admin] = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:admin])
assign_attributes(params)
end
# some query methods
end
So my question is - is there a rails module I can mix in in order to have this typecasting occur automatically?
P.S. I suppose, I will need to add a mapping between each attribute and its type.
Use custom setters:
class UsersFilter
include ActiveModel::Model
def min_age=(age)
self[:min_age] = age.to_i
end
def max_age=(age)
self[:max_age] = age.to_i
end
def admin(val)
self[:admin] = ActiveRecord::Type::Boolean.new.type_cast_from_user(val)
end
end
If you really need to dry it out to a generic typecasting facility you could use define_method to do it with metaprogramming. But YAGNI.
I'm trying to figure out how to elegantly expose an external API client to models in my rails app which is dependent on the current session. Here's a snippet of my ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
private
def user_api
#api ||= HollerAPI.new(current_user.jwt, :user) if current_user
end
end
However this user_api is not accessible to models. Instead it would be awesome if I could define a constant such as USER_API and then it's accessible everywhere in the application. Is there a way of accomplishing this?
The model world and the controller world are kept separate. You must bridge this gap carefully to avoid causing total chaos where data from one request related to a particular user bleeds over into another request.
If you need to do something in a model with an API configured by the controller, let the controller assume control:
#model = SomeModel.find(...)
user_api.post_model(#model)
Try to avoid making models too clever or smart. That can lead to wildly unpredictable side-effects that are very hard to test.
If you absolutely must provide a bridge to your models, add a property to your model you can use to populate this:
class MyModel < ActiveRecord::Base
attr_accesssor :user_api
end
Then you can populate this via the controller and use it within the model:
#model = MyModel.find(...)
#model.user_api = user_api
That bridges the gap:
class MyModel < ActiveRecord::Base
def do_stuff_with_api
user_api.post_model(self)
end
end
Remember that the primary function of the controller in an MVC world is to directly control things. Giving models too much autonomy confuses the concerns.
I'm coming from the .NET world and I'm trying to figure out what the 'Rails Way' to pass an object across tiers in a multi-tier application.
I'm writing a multi carrier pricing API. Basically in my price controller I have access to the following parameters params[:carrier], params[:address_from], params[:address_to], params[:container_type], etc. I have a validation library, a compliance library and a price-finder library that each deal with a subset of the params.
In .NET the params would be encapuslated in data transfer objects (DTOs) or contracts. Before calling any of the libraries, they would be converted to domain objects (DOs) and each library would work on the DOs, thus avoiding a tight coupling on the DTOs. Ruby programming recommands the use of 'duck typing', so my libraries could work directly on params (even though you would access symbols and not objects/properties). Or should I marshall my params into a PriceRequest object and have my libraries work on the PriceRequest type?
Option 1:
class PricesController < ApplicationController
def get
CarrierValidator.validate(params)
...
end
end
class CarrierValidator
def self.validate(params)
raise CarrierError if !Carrier.find_by_name(params[:carrier_name]).exists?
end
end
Option 2:
class PricesController < ApplicationController
def get
pricesRequest = PricesRequest.new(carrier_name: params[:carrier_name], ...)
pricesRequest.validate
...
end
end
class PriceRequest
attr_accessor : ...
def initalize
...
end
def validate
CarrierValidator.validate(self.carrier_name)
end
end
class CarrierValidator
def self.validate(carrier_name)
raise CarrierError if !Carrier.find_by_name(carrier_name).exists?
end
end
TIA,
J
You should create a type. I would use ActiveModel to encapsulate the data (attributes) & business logic (validations & maybe some layer-specific methods for processing the data).
Basically, you want to be able to do Rails-y things in the controller like:
def get
price_request = PriceRequest.new(params[:price_request])
if price_request.valid?
# do something like redirect or render
else
# do something else
end
end
so you want to declare:
class PriceRequest
include ActiveModel::Model
attr_accessor :carrier, :address_from, :address_to, :container_type
validates :carrier, presence: true
validate :validate_address_from
def validate_address_from
# do something with errors.add
end
# and so on
This is a good place to start: http://edgeguides.rubyonrails.org/active_model_basics.html
More details in the API: http://api.rubyonrails.org/classes/ActiveModel/Model.html
Hope that points you in the right direction...
I'm using Ruby on Rails. and I have a module called PatientFactory and it will be included in a Patient model.
I need to access a Patient's id, from this module.
module PatientFactory
def self.included(base)
# need to access instance variable here
...
end
end
But more importantly, I need it in the self.included(base)
I can easily access it outside of this method but how do I access it inside?
Given you want to do this:
class Patient < ActiveRecord::Base
include PatientFactory
end
then you would access the id like this:
module PatientFactory
def get_patient_id
self.id
end
end
a = Patient.new
a.id #=> nil
a.save
a.id #=> Integer
when your module gets included it a class, all of its methods become instance methods of that class. if you rather extend them, they get inserted in your class's singleton class, therefore they'll be accessible as if they were class methods.
class Patient < ActiveRecord::Base
include PatientFactory
end
Then you can access the instance as if they were part of Patient's methods.
If you still need to preserve your workflow as you mentioned, Yehuda might offer some help;
http://yehudakatz.com/2009/11/12/better-ruby-idioms/
How do you pass data from a controller to a model?
In my application_controller I grab the user's location (state and city) and include a before_filter to make it accesible in all my controllers via
before_filter :community
def community
#city = request.location.city
#state = request.location.state
#community = #city+#state
end
Then I try add the data retrieved in the controller to the model via:
before_save :add_community
def add_community
self.community = #community
end
The data, however, never makes its way from the controller to the model. If I use:
def add_community
#city = request.location.city
#state = request.location.state
#community = #city+#state
self.community = #community
end
The methods request.location.city and request.location.state do not function from the model. I know that everything else is working because if I define #city and #state as strings, under def_community, then everything works, except I don't have a dynamic variable, just a string placed in the model. Also, I know the requests are working in the controller/views, because I can get them to display the proper dynamic info. The issue is simply getting the data from the controller to the model. Thanks a lot for your time.
The concept you're wrestling with is MVC architecture, which is about separating responsibilities. The models should handle interaction with the DB (or other backend) without needing any knowledge of the context they're being used in (whether it be a an HTTP request or otherwise), views should not need to know about the backend, and controllers handle interactions between the two.
So in the case of your Rails app, the views and controllers have access to the request object, while your models do not. If you want to pass information from the current request to your model, it's up to your controller to do so. I would define your add_community as follows:
class User < ActiveRecord::Base
def add_community(city, state)
self.community = city.to_s + state.to_s # to_s just in case you got nils
end
end
And then in your controller:
class UsersController < ApplicationController
def create # I'm assuming it's create you're dealing with
...
#user.add_community(request.location.city, request.location.state)
...
end
end
I prefer not to pass the request object directly, because that really maintains the separation of the model from the current request. The User model doesn't need to know about request objects or how they work. All it knows is it's getting a city and a state.
Hope that helps.
The class instance variables (those that start with #) in the controllers are separate from those in the models. This is the Model vs the Controller in MVC architecture. The Model and Controller (and view) are separated.
You move info from a controller to a model explicitly. In Rails and other object oriented systems, you have several options:
Use function parameters
# In the controller
user = User.new(:community => #community)
# In this example, :community is a database field/column of the
# User model
Docs
Use instance variables attribute setters
# In the controller
user = User.new
user.community = #community
# same as above, :community is a database field
Passing data to models when the data is not a database field
# In the model
class User < ActiveRecord::Base
attr_accessor :community
# In this example, :community is NOT a database attribute of theĀ
# User model. It is an instance variable that can be used
# by the model's calculations. It is not automatically stored in the db
# In the controller -- Note, same as above -- the controller
# doesn't know if the field is a database attribute or not.
# (This is a good thing)
user = User.new
user.community = #community
Docs