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...
Related
Developing rails app for both api and front end. so we have products controller for api and products controller for the front and Product model is one for both.
Like that
class Api::V1::ProductsController < ActionController::API
def create
#product.save
end
end
class ProductsController < ActionController::Base
def create
#product.save
render #product
end
end
class Product < ActiveRecord::Base
def weight=(value)
weight = convert_to_lb
super(weight)
end
end
Basically in product we have 'weight field' and this field is basically capture weight from the warehouse. it will be different unit for the user. so i'm going to save whatever weight is capture by unit, its lb,g or stone but it will convert to lb and store into database.
So i write the overide method for the conversation. but i want this override method should only call for front app only and not for the api. because api will always post weight in lb(its need to be convert in client side)
Can you guys anyone suggest the solution? what should i use or what should i do for this kind of scenario.suggest if its any other solution for that kind of situation as well. Thanks in advance.
It's better to keep Product model as simple as possible (Single-responsibility principle) and keep weight conversion outside.
I think it would be great to use Decorator pattern. Imagine class that works like this:
#product = ProductInKilogram.new(Product.find(params[:id]))
#product.update product_params
#product.weight # => kg weight here
So, you should use this new ProductInKilogram from Api::V1::ProductsController only.
You have options to implement that.
Inheritance
class ProductInKilogram < Product
def weight=(value)
weight = convert_to_lb
super(weight)
end
end
product = ProductInKilogram.find(1)
product.weight = 1
It's easy, but complexity of ProductInKilogram is high. For example you can't test such class in an isolation without database.
SimpleDelegator
class ProductInKilogram < SimpleDelegator
def weight=(value)
__getobj__.weight = convert_to_lb(value)
end
end
ProductInKilogram.new(Product.find(1))
Plain Ruby (My Favourite)
class ProductInKilogram
def initialize(obj)
#obj = obj
end
def weight=(value)
#obj.weight = convert_to_lb(value)
end
def weight
convert_to_kg #obj.weight
end
def save
#obj.save
end
# All other required methods
end
Looks a little bit verbose, but it is simple. It's quit easy to test such class, because it does nothing about persitance.
Links
Single-responsibility principle
Delegate gem
Decorator Pattern in Ruby
I have the following model:
class TwitterEngagement < ApplicationRecord
end
And I would like to override create (and create!), update (and
update!) methods of it so no one can manually entry fake data. I would like the help of someone more experienced with active record and rails so I don't mess anything up. Right now what I have is:
class TwitterEngagement < ApplicationRecord
belongs_to :page
def create
super(metrics)
end
def update
super(metrics)
end
private
def metrics
client.get_engagements(page.url)
def client
TwitterClient.new
end
end
Thank you.
TL;DR:
class FacebookEngagement < ApplicationRecord
def create_or_update(*args, &block)
super(metrics)
end
Probably depends on your Rails version, but I traced the ActiveRecord::Persistence sometime before in Rails 5, and found out that both create and update eventually calls create_or_update.
Suggestion:
If ever possible, I'll just do a validation, because it kinda makes more sense because you are validating the inputs, and then probably set an optional readonly?, to prevent saving of records. This will also prevent "silent failing" code / behaviour as doing TL;DR above would not throw an exception / populate the validation errors, if say an unsuspecting developer does: facebook_engagement.update(someattr: 'somevalue') as the arguments are gonna basically be ignored because it's instead calling super(metrics), and would then break the principle of least surprise.
So, I'll probably do something like below:
class FacebookEngagement < ApplicationRecord
belongs_to :page
validate :attributes_should_not_be_set_manually
before_save :set_attributes_from_facebook_engagement
# optional
def readonly?
# allows `create`, prevents `update`
persisted?
end
private
def attributes_should_not_be_set_manually
changes.keys.except('page_id').each do |attribute|
errors.add(attribute, 'should not be set manually!')
end
end
def set_attributes_from_facebook_engagement
assign_attributes(metrics)
end
def metrics
# simple memoization to prevent wasteful duplicate requests (or remove if not needed)
#metrics ||= graph.get_object("#{page.url}?fields=engagement")
end
def graph
Koala::Facebook::API.new
end
end
I have a model that requires a ton of dependencies... it looks something like this:
after_create :create_dependencies
def create_dependencies
create_foo
create_bar
create_baz
# ...
end
This is really polluting my model class with a bunch of this nonsense. Is there another way I can go about this? I would love to use a form object or something like that, but I want to ensure all of these objects come with their dependent relationships no matter what, even when created through command line, in a test suite, etc.
My first reaction was to create a Form object like you mentioned (as described by Ryan Bates). However you're right that if you save a model directly none of the dependencies will be created.
One approach you could take is to refactor the dependency creation out into a separate class:
# lib/my_model_dependency_creator.rb
class MyModelDependencyCreator
def initialize(my_model)
#my_model = my_model
end
def create_dependencies
create_foo
create_bar
# etc
end
private
def create_foo
# create dependency associated with #my_model
end
def create_bar
end
end
Then in your model class:
...
after_create :create_dependencies
def create_dependencies
MyModelDependencyCreator.new(self).create_dependencies
end
First, any thought about observers?
Second, I guess it's not that hard to extract the code.
#include this module in your model
module AutoSaveDependency
def auto_save_dependencies *deps
##auto_save_dependencies = deps
end
def auto_save
##auto_save_dependencies.each {|dep| send "create_#{dep}" }
end
def self.included(model)
model.send :after_create, :auto_save
end
end
So in your model, you just include this module, and write auto_save_dependencies :foo, :bar, ...
It could be more complicated but I think it's doable
We have a set of reports that run out of /lib. These have grown so voluminous that we now have written many methods that would count as helpers or other 'decorator-style' methods relating specifically to Reporting.
These additional methods live in the report, and look like:
class report
def get_latest_credential_updated_date
credentials.map(&:updated_at).compact.max
end
def initialize
# set up stuff
end
end
Is there a way to load a module, or otherwise inject code to a Model when the reporting lib loads:
class Loan < ActiveRecord::Base
def get_latest_credential_updated_date
credentials.map(&:updated_at).compact.max
end
end
Is there a better pattern to represent this architecture?
Maybe you can create a "inject helper" with class_eval, something like:
def inject_to(class_name, &block)
eval "#{class_name.name}.class_eval &block"
end
inject_to Loan do
def get_latest_credential_updated_date
credentials.map(&:updated_at).compact.max
end
end
When adding caching to a model in Rails, there is the repetitive nature that looks like the following:
class Team < ActiveRecord::Base
attr_accessible :name
end
Before caching, to retrieve a name, everything was trivial,
team = Team.new(:name => "The Awesome Team")
team.save
team.name # "The Awesome Team"
With caching introduced using memcached or redis I find myself adding methods to my models and it's super repetitive:
def get_name
if name_is_in_cache
return cached_name
else
name
end
end
def set_name(name)
# set name in cache
self.name = name
end
Is there some obvious way that I'm missing to clean this up? I'm caching a lot of fields in different ways and it seems attr_accessible is virtually redundant at this point. How can this be cleaned up?
Create a mixin that just provides wrappers around instance_eval. Untested:
module AttributeCaching
def cache(name)
instance_eval(<<-RUBY)
def get_#{name}
if #{name}_is_in_cache
return cached_#{name}
else
#{name}
end
end
RUBY
instance_eval(<<-RUBY)
def set_#{name}(name)
self.#{name} = name
end
RUBY
end
end
Then in your model:
class Team < ActiveRecord::Base
extend AttributeCaching
cache :name
cache :something_else
end
You could probably make your life a lot easier, however, by not naming each of your caching methods differently. Couldn't you do something like get_cached(name) and set_cached(name, value), then your problem suddenly becomes a lot less repetitive.