I'm in the process of learning Ruby/Rails. I'm currently learning to create a model/classes.
I have an existing class that has these methods
def duration
(start_on..end_on).count
end
def items
space.available_items
end
def available_items
duration >= 365 ? items.not_including_insurance : items
end
In the class above, I have a method called available_items that checks if the duration is more than 365 days, then item will not be included in the array.
Rather than coupling the duration logic to the existing class, I think it's better to encapsulate it in another class which could take an array of rules to apply to the items.
So instead, in the above class, in the available_items method I can do something like:
policy.apply(items)
which will return all of the items which satisfy the rules.
And in the future, I can append more rules and keeps it flexible.
After includeing you module you still can define available_items method with custom rules (and have a generic one upstream), this is the easy way.
As for "passing" parameters to a strategy - it can be accomplished in a Concern like:
module SomePolicy
extend ActiveSupport::Concern
module ClassMethods
def my_policiable(param)
has_many :prices, class_name: 'Pricing::SimplePrice', as: :priceable
# ...
define_method(:available_items) {
if param == :foo
# ...
end
}
end
end
end
include SomePolicy
my_policiable(:foo)
trick is that the method is run in class context, there based on params you can define methods differently (but note that in example above if param.. will run for each resulting method invocation)
Related
Consider an abstract Tag concept where there are different kinds of tags, say Topic and Location (amongst others), that are unrelated apart from being tags. They have the same base Tag properties in common but are otherwise different.
A Topic concept is based on a similar Tag concept. An operation like Topic::Update would usually inherit from Topic::Create, but such an operation also needs to inherit from Tag::Update. Ruby doesn't support multiple inheritance - can Trailblazer support this?
Trailblazer operations support inheritance through a builds block that allows them to instantiate a subclass based on the contents of the supplied params hash. This works where the base class (Tag) is public-facing and operations are invoked through the base class. However, in this example, the public-facing class is the Topic subclass.
Operations need to be invoked through the subclass (Topic) but have its operations be based off a common Tag base class (a reverse builder ?).
Here is one way that this can be achieved through single inheritance (but it illustrates the shortcomings of this approach)...
Each type of tag is stored in its own database table and has ActiveRecord classes like this:
class Tag < ActiveRecord::Base
self.abstract_class = true
end
class Topic < Tag; end
The Trailblazer concept would follow a similar design - a Tag operation would provide the base functionality and be subclassed by more a specific operation (Topic). The Tag operation would not be used directly - a Topic controller, for example, would use the Topic operation.
The Topic operation inherits from Tag but must specify its own Topic model which seems to be only possible within each operation, requiring each to be subclassed explicitly:
class Topic < Tag
class Create < Tag::Create
model Topic
end
class Update < Tag::Update
model Topic
end
class Delete < Tag::Delete
model Topic
end
end
A problem with this is that the contract, being defined on the base operation, thinks that it is a Tag rather than a Topic and this leads to issues where it's used as a model. An example showing where this is a problem is in a cell's view: the Topic concept has a cell that presents views to manipulate its objects. It renders forms using simple_form_for, like this:
simple_form_for operation.contract
This doesn't work as expected because the contract thinks it is a Tag and this breaks the form:
its parameters are sent as params[:tag] instead of params[:topic]
the submit button's label is Create Tag instead of Create Topic.
The cell can't use operation.model (which would otherwise work) because it won't see any form errors when rendering after a submited operation fails.
A way to solve this is to be explicit with simple_form_for:
simple_form_for operation.contract, as: :topic, url: topics_path ...
Another problem occurs when adding properties to Topic, because this requires extending the Tag contract. The usual way to do this is to add a contract do..end block to the Topic::Create operation. The problem occurs because such a block would not be seen by Topic::Update and Topic::Delete because they inherit from their Tag counterparts and not from Topic::Create.
An alternative would be for a subclassed Topic::Update operation to inherit from Topic::Create. This would remove the need to specify the model (because Topic::Create does it) but would mean that anything added by the Tag::Update operation would be lost:
class Update < Create
action :update
end
The action needs to be respecified because Tag::Update isn't inherited but, because Topic::Create is inherited, properties added in Topic::Create are available in Topic::Update.
Both of these styles work as long as changes are only in one base class. It breaks whe there are changes in both because Ruby does not support multiple inheritance. Consider the Delete operation which usually looks like this:
class Delete < Create
action :find
def process(params)
# validate params and then delete
end
end
If that is Tag::Delete then Topic::Delete could be either
class Delete < Tag::Delete
model Topic
end
or
class Delete < Create
action :find
end
In the former case Topic::Delete would be unaware of properties added by Topic::Create and, in the latter case, Topic::Delete would lacks the process method defned in Tag::Delete.
How can a Trailblazer concept inherit another and be able to extend its operations ?
The effect of multiple inheritance can be achieved by using modules.
First define the ActiveRecord objects like this:
class Topic < ActiveRecord::Base; end
class Location < ActiveRecord::Base; end
There is no longer a base Tag abstract class, allowing Tag to be defined as a module like this (app/concepts/tag/crud.rb):
module Tag
module Create
def self.included(base)
base.send :include, Trailblazer::Operation::Model
base.send :model, base.parent # e.g. Thing::Tag => Thing
base.send :contract, Form
end
class Form < Reform::Form
property ...
end
def process(params)
...
end
end
module Update
def self.included(base)
base.send :action, :update
end
end
module Delete
def self.included(base)
base.send :action, :find
end
def process(params)
...
end
end
end
Code that would normally be placed inside operation classes (such as include Model and contract) are placed inside a self.included method so that they are executed within the scope of the including class. The ruby send method needs to be used to invoke such methods on the including class from within the module's self.included method.
Using this Tag module, a Topic tag would look like this (app/concepts/tag/topic/crud.rb)
class Topic
class Create < Trailblazer::Operation
include Tag::Create
contract do
property ...
end
end
class Update < Create
include Tag::Update
end
class Delete < Create
include Tag::Delete
def process(params)
....
super
end
end
end
This allows extension of the Tag contract by Topic::Create, which adds properties to the contract, and further customisation of Tag methods like the Delete::process example that calls super to invoke Tag::Delete::process. Aditionally, the contract will know it's a Topic so things like simple_form will work properly.
Using a module to share common data is one (correct) way of inheriting.
However, you shouldn't forget that you can also use Trailblazer's compositional interface, where you could use inheritance across operation classes to inherit generic logic, and then refer to the layer objects using composition.
module Location
class Create < Tag::Create # inheritance.
contract Tag::Contract::Create # compositional API.
end
end
The compositional interface allows you to reference a separate class and is explained in the 2.0 docs. It works for policies, contracts, representers and callback objects.
I have a model called Coupon, which can either be set to have a money_off or percent_off attributes (it can only have one set at a time).
Also depending on whether the Coupon is money_off or percent_off changes which methods are used.
Im wondering if i should be using Single table inheritance to eseentially sub class Coupon and have a sub class that deals with percent off coupons and another dealing with money off coupons?
I would then like to know how a user would be able to select this from the view.
Here's an example that illustrates the usage of strategies (about which Yam posted a more detailed answer):
class Coupon < Struct.new(:original_price, :amount_off, :type)
def price_after_discount
discount_strategy.call(self)
end
private
def discount_strategy
# note: no hardcoding here
klass = type.to_s.camelize # :money_off to 'MoneyOff'
"Coupon::#{klass}".constantize.new
end
class MoneyOff
def call(coupon)
coupon.original_price - coupon.amount_off
end
end
class PercentOff
def call(coupon)
coupon.original_price * (1.0 - coupon.amount_off / 100.0)
end
end
end
Coupon.new(150, 10, :money_off).price_after_discount # => 140
Coupon.new(150, 10, :percent_off).price_after_discount # => 135.0
Now, instead of creating a strategy internally, we can accept it in constructor, thus making the strategy "injectable".
The best way is to determine which functionality you require for each class. If you only need a small amount of changes, then stick to a single class with an enum:
#app/models/coupon.rb
class Coupon < ActiveRecord::Base
enum type: [:percent, :money]
def value
if type.percent?
# ...
elsif type.money?
# ...
end
end
end
This will allow you to use the type in your instance methods, which shouldn't cause such a problem if you didn't have a lot of changes to make within the class.
This would allow you to call:
#coupon = Coupon.find x
#coupon.value #-> returns value based on the type
--
The alternative (STI) would be more of a structured change, and would only work if you were referencing each class explicitly:
#app/models/coupon.rb
class Coupon < ActiveRecord::Base
end
#app/models/percent.rb
class Percent < Coupon
def amount
# ...
end
end
#app/models/money.rb
class Money < Coupon
def takeout
# ...
end
end
An important factor here is how you call these.
For the above classes, you have to reference the subclassed classes on their own:
#percentage_coupon = Percent.find x
#money_coupon = Money.find y
This will obviously be more cumbersome, and may even cause problems with your routes & controllers etc.
.... so it may be best going with the single class :)
What you can do is maintain the strategy internally, and provide methods such as price, discounted?, discounted_price. In addition, whether or not the admin chose to enter percentages or fixed units, you can still supply both methods: discount_pct, discount_units which would internally realize how to compute their return values.
This way the original class still supports the concept (same as the data model), yet is also flexible enough to allow various ways of providing it the necessary input. Whether you wish to show customers the pct off, or the fixed price units, you can do so independently of the admin's preferred method of input.
Even internal methods can use these abstractions. And if it turns out you're if/elsing all over the place internally, you can create nested classes for strategies and instantiate the right one once you get the record from the DB.
I am just getting my hands on Concerns in Rails and try to implement a simple logging for ActiveRecord classes. In there I want to define the field that should go into the log and have the log written automatically after save.
What I have is this:
#logable.rb (the concern)
module Logable
extend ActiveSupport::Concern
#field = nil
module ClassMethods
def set_log_field(field)
#feild = field
end
end
def print_log
p "LOGGING: #{self[#index.to_s]}"
end
end
#houses.rb (the model using the concern)
class House < ActiveRecord::Base
include Logable
after_save :print_log
set_log_field :id
end
Unfortunately the call to set_log_field does not have an effect - or rather the given value does not make it to print_log.
What am I doing wrong?
Thanks for your help!
You probably mean this (btw, why not Loggable?):
# logable.rb
module Logable
extend ActiveSupport::Concern
# Here we define class-level methods.
# Note, that #field, defined here cannot be referenced as #field from
# instance (it's class level!).
# Note also, in Ruby there is no need to declare #field in the body of a class/module.
class_methods do
def set_log_field(field)
#field = field
end
def log_field
#field
end
end
# Here we define instance methods.
# In order to access class level method (log_field), we use self.class.
included do
def print_log
p "LOGGING: #{self.class.log_field}"
end
end
end
Update You also asked about what's the difference between methods in included block and those within method body.
To make a short resume there is seemingly no difference. In very good approximation you can consider them the same. The only minor difference is in dependency management. Great illustration of it is given in the end of ActiveSupport::Concern documentation. It worth reading, take a look!
I have several models that share a concern. Each model passes in a hash, which is meant to handle minor differences in the way they use the concern. I pass the hash in through a class method like so:
add_update_to :group, :user
The full code for the concern is:
module Updateable
extend ActiveSupport::Concern
attr_accessor :streams
module ClassMethods
def add_updates_to(*streams)
#streams = streams
end
end
module InstanceMethods
def update_streams
#streams.collect{|stream| self.public_send(stream)}
end
end
included do
has_one :update, :as => :updatable
after_create :create_update_and_history
end
private
def create_update_and_history
update = self.create_update(:user_id => User.current.id)
self.update_streams.each do |stream|
stream.histories.create(:update_id => update.id)
end
end
end
Most of this code works, but I'm having trouble passing the hash from the class to an instance. At the moment, I'm trying to achieve this effect by creating a virtual attribute, passing the hash to the attribute, and then retrieving it in the instance. Not only does this feel hacky, it doesn't work. I'm assuming it doesn't work because #streams is an instance variable, so the class method add_update_to can't actually set it?
Whatever the case, is there a better way to approach this problem?
You could probably use class variables here, but those are pretty reviled in the Ruby community due to their unpredictable nature. The thing to remember is that classes in Ruby are actually also instances of classes, and can have their own instance variables that are only accessible to themselves, and not accessible to their instances (if that is in any way clear).
In this case, you are defining behavior, and not data, so I think neither instance nor class variables are appropriate. Instead, I think your best bet is to define the instance methods directly within the class method, like this:
module Updateable
extend ActiveSupport::Concern
module ClassMethods
def add_updates_to(*streams)
define_method :update_streams do
streams.collect {|stream| public_send(stream) }
end
end
end
end
BTW, there is no hash involved here, so I'm not sure what you were referring to. *streams collects your arguments into an Array.
Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.