Determine in model that which controller is trigger the save action? - ruby-on-rails

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

Related

Include third party data when Rails model is retrieved?

I have a Product model which I just basically use to interface with Stripe, it only has a stripe_product_id column.
I have multiple controllers that are dealing with products, and in every case, when a product or multiple products are retrieved, I do something like this:
#product = Product.find(params[:id]
#stripe_product = Stripe::Product.retrieve(#product.stripe_product_id)
#product.name = #stripe_product.name
# ...
render json: ProductSerializer.new(#product, {}).serializable_hash.to_json
It's not really DRY, and in this case it even works, however, I have similar structures with other Stripe entities (prices, customers) as well.
Products have many prices, and when I want to include them through the product serializer, that process obviously doesn't go through a controller's show method, but rather tries to work directly on the model.
How can I tweak a model to include attributes retrieved from a third party API, in this case Stripe?
I feel like this kind of data belongs more to the model than to a bunch of controllers, since I basically use it in ways models are used.
I would do it like this, I didn't test it, but leave it for you, I hope you get the idea
class Product < ApplicationRecord
delegate :name, to :#stripe_wrapper
def stripe_wrapper
#stripe_wrapper ||= Stripe::Product.new(self.stripe_product_id).retrieve
end
end
module Services
class StripeWrapper
delegate :name, to :#result
Product = Struct.new(:name)
def initialize(product_id)
#product_id = product_id
#result = Product.new('')
end
def retrieve
# API call to stripe
# set fields to result
# result.name = json['name']
end
end
end
and in your controller is skinny.
if you calling somewhere #product.name in your serializer it will be available
class ProductController < ApplicationController
def show
#product = Product.find(params[:id])
render json: ProductSerializer.new(#product, {}).serializable_hash.to_json
end
end

Safest way to override the update method of a model

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

what is the 'Rails Way' to pass objects across multi (logical) tiers?

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...

Ruby on Rails - Creating and Using a Custom Method

I am rather new to Rails, and would greatly appreciate any bit of help. I have created the following method:
def name_fix
name = self.split
mod_name = []
name.each do |n|
n.split("")
if n[0]
n.upcase
else
n.downcase
end
mod_name.push(n)
end
mod_name.join
end
I would like to use this method in my Controller as such:
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name].name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
How can I go about accomplishing this? Will this method reside within my Model or Controller? Previously, I've run into an undefined method error.
Note: I'm sure that there is a way to better write my code. I am grateful for help with that as well.
#app/models/patient.rb
class Patient < ActiveRecord::Base
protected
def name=(value)
mod_name = []
value.split.each do |n|
n.split("")
type = n[0] ? "up" : "down"
n.send("#{type}case")
mod_name.push(n)
end
#name = mod_name.join
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient = Patient.new patient_params
#patient.save ? redirect_to(patients_path) : render(:new)
end
private
def patient_params
params.require(:patient).permit(:name)
end
end
What you're doing is trying to override the setter method, which can be done using the above code. Much more efficient and out of the way.
I have created the following method
Since you're new, let me explain something else.
It is important to note where you're using this method.
You've currently put it in the model, which means you'll have to call it to manipulate some attribute / functionality of any object created with said model.
--
Models - in Rails - build the objects which populate your app. Ruby is an object orientated language, which means that every element of your program should revolve around data objects in some degree.
As you can see above, the method of building objects in your system is really about invoking classes. These classes contain methods which can be called, either at class level (IE invoking the class through the method), or at instance level (IE calling a method on an already invoked object).
This is where you get "class" methods (Model.method) and "instance" methods (#model.method) from:
#app/models/patient.rb
class Patient < ActiveRecord::Base
def explode
#this is an instance method
puts "Instance Explode"
end
def self.explode
#this is a class method
puts "Exploded"
end
end
Thus you can call the following:
#patient = Patient.find params[:id]
#patient.explode #-> "Instance explode"
Patient.explode #-> "Exploded"
--
This is important because it gives you a strict framework of where you should, and shouldn't use methods in your models.
It explains why you have controllers & helpers, and allows you to formulate the best way to structure your application as to get the most out of the least code.
For example...
Your use of #patient.name = params[:params][:name].name_fix is incorrect.
It's wrong because you're calling the instance method .name_fix on a piece of data totally unrelated to your model. If you wanted to use .name_fix in a general sense like this, you'd probably use a helper:
#app/helpers/patients_helper.rb
class PatientsHelper
def name_fix value
# stuff here
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient.name = name_fix params[:patient][:name]
end
end
Since you're using the method to populate the .name attribute of your model, it makes sense to override the name= setter. This will not only provide added functionality, but is much smoother and efficient than any other way.
Methods that are called directly are best put in the Controller (or in ApplicationController if you think more than one controller might want to use it).
These are methods like
# app/controllers/my_controller.rb
def foo(bar)
# do something here
end
def create
id = params[:id]
value = foo(id)
end
If you want a chained method that acts as a property method of whatever you're calling it on. Those are characteristic of how Models work - you have your main model and you call attributes or methods on the instance of that model.
# app/models/my_model.rb
def full_name
first_name + " " + last_name
end
# app/controller/my_controller.rb
def create
id = params[:id]
model = MyModel.find(id)
full_name = model.full_name
end
In your case, you want to call name_fix ON whatever is returned by params[:params][:name], which is (I'm guessing) a String.
You have two options
Modify the String class to define a method named name_fix. I highly recommend against this. It's call "monkeypatching" and shouldn't be done without good reason. Just letting you know you can do it in some cases.
Use a direct method in your controller or ApplicationController like the first example above.
#patient.name = name_fix(params[:params][:name])
Edit: As for your request about a better way to write your code... that's difficult to teach or convey in one answer. I'd say read some open source projects out there to see how people write Ruby and some common idioms used to clean up the code. To get you started, here's how I'd re-write your code
def create
#patient = Patient.new(params[:patient])
# 1. Be descriptive with your method names. `name_fix` is vague
# 2. Why is `:name` nested under another `[:params]` hash?
#patient.name = capitalize_name(params[:name])
if #patient.save
# 1. I think `patient_path` has to be singular
# 2. It needs a `Patient` object to know how to construct the URL
# e.g. `/patients/:id`
redirect_to patient_path(#patient)
else
render :new
end
end
def capitalize_name(full_name)
# Example: julio jones
#
# 1. `split` produces an array => ["julio", "jones"]
# 2. `map` applies a function (`capitalize`) to each element
# => ["Julio", "Jones"]
# 3. `join(" ")` rejoins it => "Julio Jones"
full_name.split.map(&:capitalize).join(" ")
end
Assuming your goal with the name_fix method is just to capitalize the first letter of each name, you could just pass name as an argument and store it as a private method on the Controller:
# app/controllers/patient_controller.rb
private
def name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do
#patient.name = name_fix(params[:params][:name])
in the create method.
OR, you could store this method in the model:
# app/models/patient.rb
def self.name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do this instead, in the controller:
#patient.name = Patient.name_fix(params[:params][:name])
I would also suggest renaming your name_fix method to something like capitalize_name.
update your create method as below
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name]
#patient = #patient.name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
It should work.

Ruby On Rails - Using model methods with Serializers

I have a patient model where I defined:
def weight
###Some Code###
end
def height
###Some Code###
end
Inside my patient serializer, I am also sending both height and weight to my angularJS app when performing a /GET.
Now, willing to add some date filters to my angularJS app, I have to change my patient model methods to be this way:
def weight(date)
###Some Code###
end
def height(date)
###Some Code###
end
I was wondering if there is a possibility to tell my RoR app to send me both weight and height at a chosen date?
I was thinking about creating a new Controller to do the work but I want to avoid this if it is possible.
Make weight and height virtual attributes on the model. They will be set in memory on the instance when you call setter methods. They should serialize as normal. Something like:
class Patient
attr_accessor :weight, :height
def filter_weight(date)
#weight = ### previous result of #weight(date) Some Code###
end
def filter_height(date)
#height = ###previous result #height(date) Some Code###
end
end
Then you would send the date as parameters in your GET request.
class PatientsController
def show
patient = Patient.find(params[:id])
patient.filter_weight(params[:date])
patient.filter_height(params[:date])
render json: patient, status: 200
end
end
You could probably refactor this into a method Patient#filter_weight_and_height(date)

Resources