Converting inheritance to composition in rails - ruby-on-rails

I have a model in a legacy system that looks something like this:
class Prize < ActiveRecord::Base
def win
# do a bunch of things
end
end
We started off with one prize, but like anything else the type of prizes we are dealing with is starting to expand. So now def win is doing a whole bunch of case/switching to decide prize type.
For that reason I decided to do this:
class DailyPrize < Prize
def win
#do only daily prize stuff, no type checking.
end
end
This code came under review before we sent it off to QA and now I am asked to do this using composition (mixin) and not subclassing. I cannot think of a clean way to do this.
The legacy code base is doing the following in a bunch of places and I did not want to go changing stuff all over the place:
prize = Prize.new
prize.win
So, my question how to make this happen using composition?

Here is what I understand by replacing your code by composition over inheritance.
class Prize < ActiveRecord::Base
def prize
#prize ||= PrizeFactory.build(self)
end
def win
prize.win
end
end
class PrizeFactory
def self.build(prize)
if prize.daily?
DailyPrize.new(prize)
# other condition to build specific prize
end
end
end
class DailyPrize
def initialize(prize)
#prize = prize
end
def win
#do only daily prize stuff
#access #prize to get #prize attribute
#if you use it, you have coupling (see below)
end
end
The thing is, this may not be better than your implementation, it really depend of what your are achieving in term of domain logic.
With composition, one goal is to reduce the coupling between object, if you are calling a lot of #prize object methods in the DailyPrice win method , you have a tight coupling between this two classes and you may lost the benefits of composition.

One way I can think of is having specific prizes as Modules, e.g.
module DailyPrize
def specific_method_1
end
def specific_method_2
end
end
... and then having the Prize class as :
class Prize
def win
# do something in common
specific_method_1
# do something in common
specific_method_2
# ...
end
end
And then you can mixin a module as you choose, for example when instantiating the class
def initialize (prize_type)
# mixin the appropriate module
end

Related

Determine in model that which controller is trigger the save action?

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

Building dependent relationships in Rails - is there a better pattern than after_create?

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

What's a really good pattern to extract logic from models and have the results persisted to the db?

I've been experimenting with different ways of structuring my app, and particularly with ActiveRecord-based models, looking into the notion of having a separate class whose results are saved as a single field. Now composition might have worked, but there's been a lot of discussion on whether its staying or not, so I was wondering what alternatives you might have.
Example:
class Gadget < ActiveRecord::Base
attr_accessor: lights
end
Now, I would like the lights property to be 'managed' by a completely separate class:
class Lights
def doSomething
end
def doSomethingElse
end
end
What's a good way of, say, proxying or delegating this logic out of the ActiveRecord model and into the Lights class in order to transparently store the results?
e.g. using an instance of the Lights class as the lights property - but that won't work, since there's no association, right?
e.g. use method_missing to push all requests out to the lights instance - but that won't work either, because it won't be persisted to the database.
Maybe it's not possible, but I'm interested in ways of persisting the results of logical operations. All suggestions welcome.
What about something like:
class Gadget < ActiveRecord::Base
def lights
#lights ||= Lights.new(self)
end
end
class Lights
attr_reader :root
delegate :save, :save!, to: :root
def initialize(root)
#root = root
end
def doSomething
end
def doSomethingElse
end
def method_missing(method_name, *args, &block)
#delegate all setters to root
if method_name =~ /.*=$/
root.send(method_name, *args, &block)
else
super
end
end
end
Thus you can do:
gadget = Gadget.new
#say gagdet has a name column
gadget.lights.name = 'foo'
gadget.lights.save
gadget.name #=> 'foo'
Still unsure why you need it but it should work

How can I conditionally, or contextually, load an interface of methods into a model from a Rails /lib?

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

rails: stow away long method in model

I have this method (50+ lines) in one of my models, I prefer not having to scroll so much and not losing my cursor sometimes because of its taking up so much space. I wonder if I can put it away in a separate file and sort of include it in the model instead.
Thanks!
You can put it into a module and include it (mix it in) to your model class. For example:
app/models/my_long_method.rb
module MyLongMethod
def my_long_method
....
end
end
app/models/my_class.rb
class MyClass < ActiveRecord::Base
include MyLongMethod
end
If your method really is that long though you might want to consider breaking it down into smaller sections as methods in that module too.
module MyLongMethod
def my_long_method
first_part
second_part
end
private
def first_part
...
end
def second_part
...
end
end

Resources