I want to simulate an abstract class in Ruby on Rails. I.e. I want to raise an exception if someone tries to call Abstract.new, but he should be able to call Child.new (while Child < Abstract).
How to do this? Overwriting both new and initialize does not work.
In another comment, the OP mentions that the purpose of the abstract class is to share behavior (methods) needed by its children. In Ruby, that's often best done with a module used to "mix in" methods where needed. For example, instead of:
class Abstract
def foo
puts "foo!"
end
end
class Concrete
end
Concrete.new.foo # => "foo!"
this:
module Foo
def foo
puts "foo!"
end
end
class Concrete
include Foo
end
Concrete.new.foo # => "foo!"
But here's how the original request might be satisfied:
#!/usr/bin/ruby1.8
class Abstract
def initialize(*args)
raise if self.class == Abstract
super
end
end
class Concrete < Abstract
end
Concrete.new # OK
Abstract.new # Raises an exception
Why would you want to do this? The point of abstract/interfaced classes are to hack Strongly typed languages into a dynamic paradigm. If you need your class to fit in the signature, name your methods according to the original class or make a facade and plug it in, no need to trick a compiler into allowing it, it just works.
def my_printer obj
p obj.name
end
So I defined the interface as any object with a name property
class person
attr_accessor :name
def initialize
#name = "Person"
end
end
class Employee
attr_accessor :name
def initialize
#name = "Employee"
#wage = 23
end
end
so nothing stops us from calling our printer method with either of these
my_printer Person.new
my_printer Employee.new
both print there names without a hitch :D
You almost always need to do this to enforce an API, when some third party is going to implement some stub, and you're sure they're going to mess it up. You can use specific prefix-templates in your parent class and a module that introspects on creation to achieve this:
module Abstract
def check
local = self.methods - Object.methods
templates = []
methods = []
local.each do |l|
if l =~ /abstract_(.*)/ # <--- Notice we look for abstract_* methods to bind to.
templates.push $1
end
methods.push l.to_s
end
if !((templates & methods) == templates)
raise "Class #{self.class.name} does not implement the required interface #{templates}"
end
end
end
class AbstractParent
include Abstract
def initialize
check
end
def abstract_call # <--- One abstract method here
end
def normal_call
end
end
class Child < AbstractParent # <-- Bad child, no implementation
end
class GoodChild < AbstractParent
def call # <-- Good child, has an implementation
end
end
Test:
begin
AbstractParent.new
puts "Created AbstractParent"
rescue Exception => e
puts "Unable to create AbstractParent"
puts e.message
end
puts
begin
Child.new
puts "Created Child"
rescue Exception => e
puts "Unable to create Child"
puts e.message
end
puts
begin
GoodChild.new
puts "Created GoodChild"
rescue Exception => e
puts "Unable to create GoodChild"
puts e.message
end
Result:
[~] ruby junk.rb
Unable to create AbstractParent
Class AbstractParent does not implement the required interface ["call"]
Unable to create Child
Class Child does not implement the required interface ["call"]
Created GoodChild
If you want this for doing STI, you could follow the suggestions in this thread:
class Periodical < ActiveRecord::Base
private_class_method :new, :allocate
validates_presence_of :type
end
class Book < Periodical
public_class_method :new, :allocate
end
class Magazine < Periodical
public_class_method :new, :allocate
end
Caveat: I'm not sure if this is a working solution. This hides new and allocate in the base class and re-enables them in child classes -- but that alone does not seem to prevent objects being created with create!. Adding the validation on type prevents the base class from being created. I guess you could also hide create!, but I'm not sure if that covers all the ways Rails can instantiate a model object.
Related
Is there a way to implement monkey patching while an object is being instantiated?
When I call:
a = Foo.new
Prior to the instance being instantiated, I would like to extend the Foo class based on information which I will read from a data store. As such, each time I call Foo.new, the extension(s) that will be added to that instance of the class would change dynamically.
tl;dr: Adding methods to an instance is possible.
Answer: Adding methods to an instance is not possible. Instances in Ruby don't have methods. But each instance can have a singleton class, where one can add methods, which will then be only available on the single instance that this singleton class is made for.
class Foo
end
foo = Foo.new
def foo.bark
puts "Woof"
end
foo.bark
class << foo
def chew
puts "Crunch"
end
end
foo.chew
foo.define_singleton_method(:mark) do
puts "Widdle"
end
foo.mark
are just some of the ways to define a singleton method for an object.
module Happy
def cheer
puts "Wag"
end
end
foo.extend(Happy)
foo.cheer
This takes another approach, it will insert the module between the singleton class and the real class in the inheritance chain. This way, too, the module is available to the instance, but not on the whole class.
Sure you can!
method_name_only_known_at_runtime = 'hello'
string_only_known_at_runtime = 'Hello World!'
test = Object.new
test.define_singleton_method(method_name_only_known_at_runtime) do
puts(string_only_known_at_runtime)
end
test.hello
#> Hello World!
Prior to the instance being instantiated, I would like to extend
Given a class Foo which does something within its initialize method:
class Foo
attr_accessor :name
def initialize(name)
self.name = name
end
end
And a module FooExtension which wants to alter that behavior:
module FooExtension
def name=(value)
#name = value.reverse.upcase
end
end
You could patch it via prepend:
module FooPatcher
def initialize(*)
extend(FooExtension) if $do_extend # replace with actual logic
super
end
end
Foo.prepend(FooPatcher)
Or you could extend even before calling initialize by providing your own new class method:
class Foo
def self.new(*args)
obj = allocate
obj.extend(FooExtension) if $do_extend # replace with actual logic
obj.send(:initialize, *args)
obj
end
end
Both variants produce the same result:
$do_extend = false
Foo.new('hello')
#=> #<Foo:0x00007ff66582b640 #name="hello">
$do_extend = true
Foo.new('hello')
#=> #<Foo:0x00007ff66582b280 #name="OLLEH">
My intention is to create custom error classes in various places for my Rails application since most of the error classes have the same methods. I have decided to create a YAML file to contain all the information from various error classes, and use a class factory script to generate all the classes in runtime. Here is what I have:
chat_policy.rb
class ChatPolicy; ... end
class ChatPolicy::Error < StandardError
ERROR_CLASSES = GLOBAL_ERROR_CLASSES['chat_policy']
ERROR_CLASSES.each do |cls|
const_set(cls['class_name'], Class.new(ChatPolicy::Error) {
attr_reader :object
def initialize(object)
#object = object
end
define_method(:message) do
cls['message']
end
define_method(:code) do
cls['code']
end
})
end
the GLOBAL_ERROR_CLASSES is loaded from YAML.load and turned to an object.
error_classes.yml
chat_policy:
- class_name: UserBlacklisted
message: You are not allowed to do this
code: ECP01
- class_name: UserSuspended
message: You are not allowed to do this
code: ECP02
- class_name: UserNotEligibleToRent
message: You are not allowed to do this
code: ECP03
- class_name: MembershipTierNotAllowed
message: You are not allowed to do this
code: ECP04
* __ Question is __ *
Now I have other files like register_policy, checkout_policy, discount_policy ..etc. It would be very duplicated if I have to do the class generation in every policy file. I wonder if I can shorten the code to something like this:
chat_policy_intended.rb
class ChatPolicy::Error < StandardError
ERROR_CLASSES = GLOBAL_ERROR_CLASSES['chat_policy']
error_class_factory(ChatPolicy::Error, ERROR_CLASSES)
end
discount_policy_intended.rb
class DiscountPolicy::Error < StandardError
ERROR_CLASSES = GLOBAL_ERROR_CLASSES['discount_policy']
error_class_factory(DiscountPolicy::Error, ERROR_CLASSES)
end
error_clas_factory.rb
ERROR_CLASSES.each do |cls|
const_set(cls['class_name'], Class.new(/*class_variable*/) {
attr_reader :object
def initialize(object)
#object = object
end
define_method(:message) do
cls['message']
end
define_method(:code) do
cls['code']
end
})
end
What I tried
I tried to create a .rb file basically copying the class factory script. And use eval method to eval it in runtime, but it seems I can pass in variables into the script
eval File.read(File.join(Rails.root, 'lib', 'evals', 'error_class_generator.rb'))
What should I do?
I appreciate the effort of avoiding to repeat yourself at all costs, but I find your code quite complex for the problem you're trying to solve, namely send errors to your app users.
How about sticking to the a simpler < MyAppError inheritance hierarchy to avoid the duplicated code?
class MyAppError < StandardError
attr_reader :object
def message(message)
# does stuff
end
def code(code)
# also does stuff
end
end
class ChatPolicyError < MyAppError
def message(message)
'[CHAT POLICY]' + super
end
end
class UserBlacklisted < ChatPolicyError
def message(message)
# Does stuff too
super
end
end
[...] # You get the idea
I have the following situation:
class A < CommonParent
... some code ...
class IdenticalDescendent < self
identical_statement_0
identical_statement_1
end
end
class B < CommonParent
... some other code ...
class IdenticalDescendent < self
identical_statement_0
identical_statement_1
end
end
I have this situation a lot. Like, there are about forty IdenticalDescendent classes in my app. I like the pattern, it allows me to call A::IdenticalDescendent or B::IdenticalDescendent or whatever to access certain related behaviours in different domains (specified by A or B). For reasons, I can't just completely abstract the problem away by re-designing the behaviour clustering.
So the general form of my question is how do I automate the generation of IdenticalDescendent in all of these. There ARE descendants of CommonParent that don't invoke this pattern, so the action probably shouldn't happen there. I imagine it should happen in a mixin or something, but I find that if I just try to do:
class A < CommonParent
include CommonBehaviour
... some code ...
end
module CommonBehaviour
... what ...
end
I can't figure out how to write CommonBehaviour to allow for the IdenticalDescendent to descend from the including class.
Help me StackOverflow, you're my only hope.
The answer I was looking for is to use block notation for Class.new inside a self.included callback. I have this now:
module CommonDescendant
def self.included(base)
descendant_class = Class.new(base) do
... put my desired common behavior here ...
end
base.const_set :Descendant, descendant_class
end
end
class A
include CommonDescendant
... unique behavior ...
end
class B
include CommonDescendant
... unique other behavior ...
end
And this gives us the design I want!
I believe you can automate your pattern by using the callback (hook) Class#inherited:
class CommonParent
def self.inherited(klass)
return unless klass.superclass == CommonParent
klass.const_set(:Descendent, Class.new(klass) do
def x
puts "in x"
end
end)
end
end
class A < CommonParent
def a
puts "in a"
end
end
d = A::Descendent.new #=> #<A::Descendent:0x007f99620172e8>
d.a # in a
d.x # in x
class B < CommonParent
def b
puts "in b"
end
end
d = B::Descendent.new #=> #<B::Descendent:0x007f99618b18f0>
d.b # in b
d.x # in x
d.a #=> NoMethodError:... (as expected)
Note that, without:
return unless klass.superclass == CommonParent
the creation of A::Descendent would trigger inherited with klass => Descendent, causing an anonymous subclass of Descendent to be created, etc., resulting in a "stack level too deep exception."
I would suggest separating descendant class and method generation. Of course, you could toss everything into a class_eval block (which will positively reek).
Something like the following (completely untested)
module CommonDescendants
Descendant = Class.new(self) do
include CommonDescendantMethods
end
end
module CommonDescendantMethods
end
class A < CommonParent
extend CommonDescendants
end
I am facing a design decision I cannot solve. In the application a user will have the ability to create a campaign from a set of different campaign types available to them.
Originally, I implemented this by creating a Campaign and CampaignType model where a campaign has a campaign_type_id attribute to know which type of campaign it was.
I seeded the database with the possible CampaignType models. This allows me to fetch all CampaignType's and display them as options to users when creating a Campaign.
I was looking to refactor because in this solution I am stuck using switch or if/else blocks to check what type a campaign is before performing logic (no subclasses).
The alternative is to get rid of CampaignType table and use a simple type attribute on the Campaign model. This allows me to create Subclasses of Campaign and get rid of the switch and if/else blocks.
The problem with this approach is I still need to be able to list all available campaign types to my users. This means I need to iterate Campaign.subclasses to get the classes. This works except it also means I need to add a bunch of attributes to each subclass as methods for displaying in UI.
Original
CampaignType.create! :fa_icon => "fa-line-chart", :avatar=> "spend.png", :name => "Spend Based", :short_description => "Spend X Get Y"
In STI
class SpendBasedCampaign < Campaign
def name
"Spend Based"
end
def fa_icon
"fa-line-chart"
end
def avatar
"spend.png"
end
end
Neither way feels right to me. What is the best approach to this problem?
A not very performant solution using phantom methods. This technique only works with Ruby >= 2.0, because since 2.0, unbound methods from modules can be bound to any object, while in earlier versions, any unbound method can only be bound to the objects kind_of? the class defining that method.
# app/models/campaign.rb
class Campaign < ActiveRecord::Base
enum :campaign_type => [:spend_based, ...]
def method_missing(name, *args, &block)
campaign_type_module.instance_method(name).bind(self).call
rescue NameError
super
end
def respond_to_missing?(name, include_private=false)
super || campaign_type_module.instance_methods(include_private).include?(name)
end
private
def campaign_type_module
Campaigns.const_get(campaign_type.camelize)
end
end
# app/models/campaigns/spend_based.rb
module Campaigns
module SpendBased
def name
"Spend Based"
end
def fa_icon
"fa-line-chart"
end
def avatar
"spend.png"
end
end
end
Update
Use class macros to improve performance, and keep your models as clean as possible by hiding nasty things to concerns and builder.
This is your model class:
# app/models/campaign.rb
class Campaign < ActiveRecord::Base
include CampaignAttributes
enum :campaign_type => [:spend_based, ...]
campaign_attr :name, :fa_icon, :avatar, ...
end
And this is your campaign type definition:
# app/models/campaigns/spend_based.rb
Campaigns.build 'SpendBased' do
name 'Spend Based'
fa_icon 'fa-line-chart'
avatar 'spend.png'
end
A concern providing campaign_attr to your model class:
# app/models/concerns/campaign_attributes.rb
module CampaignAttributes
extend ActiveSupport::Concern
module ClassMethods
private
def campaign_attr(*names)
names.each do |name|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{name}
Campaigns.const_get(campaign_type.camelize).instance_method(:#{name}).bind(self).call
end
EOS
end
end
end
end
And finally, the module builder:
# app/models/campaigns/builder.rb
module Campaigns
class Builder < BasicObject
def initialize
#mod = ::Module.new
end
def method_missing(name, *args)
value = args.shift
#mod.send(:define_method, name) { value }
end
def build(&block)
instance_eval &block
#mod
end
end
def self.build(module_name, &block)
const_set module_name, Builder.new.build(&block)
end
end
I'm currently trying to achieve something similar to what is proposed in the chosen answer of this question: Ruby design pattern: How to make an extensible factory class?
class LogFileReader
##subclasses = { }
def self.create type
c = ##subclasses[type]
if c
c.new
else
raise "Bad log file type: #{type}"
end
end
def self.register_reader name
##subclasses[name] = self
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
register_reader :git
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
register_reader :bzr
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class SvnLogFileReader < LogFileReader
def display
puts "Subersion reader, at your service."
end
register_reader :svn
end
LogFileReader.create(:svn).display
The unit tests work flawlessly, but when I start the server no class is being registered. May I be missing something about how the static method call is working? When is the register_reader call made by each subclass?
To answer the OP's question about when the classes call register_reader, it happens when the file is loaded. Add this code to an initializer to load the files yourself.
Dir[Rails.root.join('path', 'to', 'log_file_readers', '**', '*.rb').to_s].each { |log_file_reader| require log_file_reader }