Identical inheritance pattern in multiple classes - ruby-on-rails

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

Related

How to improve lisibility/split "fat" class method (inside ruby class/rails model)

as a new Rubyist, I'm running into a recurring problem when it comes to structure my models.
When a method is too long:
I try to refactor to a better/shorter syntax
I try to split some parts into "sub methods"
PROBLEM: I don't know how to split the method properly + whith which tool (private method, modules etc.)
For example:
I need to run Foo.main_class_method
My model looks like this:
class Foo < Applicationrecord
def self.main_class_method
[...] # way too long method with nasty iterations
end
end
I try to split my method to improve lisibility. It becomes :
class Foo < Applicationrecord
def self.main_class_method
[...] # fewer code
self.first_splitted_class_method
self.second_splitted_class_method
end
private
def self.first_splitted_class_method
[...] # some code
end
def self.second_splitted_class_method
[...] # some code
end
end
Result: It works, but I fell like this is not the proper way to do it + I have side effects
expected: splitted_methods are not accessible, except inside main_class_method
got: I can call Foo.first_splitted_class_method since class methods "ignore" Private. splitted_class_methods under Private are not private
Question: Is it an acceptable way to split main_class_method or is it a complete misuse of private method ?
Using private method to split your code:
Possible but not the real solution if the code belongs somewhere else
It's rather about "does it belongs here?" than "does it look nicer?"
To fix the "not private" private class method (original post) :
use private_class_method :your_method_name after you defined it
or right before
private_class_method def your_method_name
[...] # your code
end
If your splitting a class/instance method:
the splitted_method must be the same type(class/instance) as the main_class_method calling it
In the main_method you can call the splitted_method with or without using self.method syntax
class Foo < Applicationrecord
def self.main_class_method
# Here, self == Foo class
# first_splitted_class == class method, I can call self.first_splitted_class_method
self.first_splitted_class_method
# I can also call directly without self because self is implicit
second_splitted_class_method
end
def self.first_splitted_class_method
end
def self.second_splitted_class_method
end
private_class_method :first_splitted_class_method, :second_splitted_class_method
end

Ruby include/extend Module: a class method - Beginner

I've been reading this article on the difference between include & extend in ruby.
If I have this module, I understand how the first and second methods of the module will be used in the class. What I don't understand is how the class << self will be used by include or extend.
module Direction
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
class << self
def stop
puts "stopping!"
end
end
end
# This will work because `include` brings them in as instance methods
class Car
include Direction
end
Car.new.straight
Car.new.turn
# ---------------------
# Now this will also work because `extend` brings them in as class methods
class Car
extend Direction
end
Car.straight
Car.turn
# ---------------------
Now, the issue is, doing Car.stop or Car.new.stop will always result in an error:
/Users/<name>/Projects/ruby-testing/main.rb:34:in `<main>': undefined method `stop' for Car:Class (NoMethodError)
Why are class methods not carried over via include and extend?
I started thinking about this because of my research into the [forwardable source code at line 119].(https://github.com/ruby/ruby/blob/master/lib/forwardable.rb#L119)
Thank you for any help you may have!
Update from Answer Below
The following was an example given:
module Direction
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
end
class Car
include Direction
end
This I understand now, and I understand how I can implement class methods from a module into a class using def self.included(base). My question is, if we used extend inside of Car instead of include, would we still be able to get at those class methods using def self.included(base)?
When you define a method with class << self you are defining a class method. It's the same as defining the methed like this:
class Foo
def self.foo
puts 'foo'
end
# the above definition is the same as doing:
class << self
def foo
puts 'foo'
end
end
end
The above shows 2 ways of defining class methods which are called directly on the class and not on instances of the class. You might use the 2nd syntax if you want to define only class methods or several of them inside of the class << self block. But either style has the same result.
Since you've defined a class method on the Direction module, include or extend will not inherit the class method of that module. This is the expected behavior.
If you want to use inheritance with class methods from a module, you should do it like this which is explained further down in the article you've linked
module Direction
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
end
class Car
include Direction
end
Now calling class methods on Car will inherit as defined in the Direction class.
Car.stop
stopping!
=>nil # calling a method will return nil unless the method returns a value.
However always be careful using inheritance of any kind as Ruby is a dynamic language. So if you do the above code and then later redefine this method:
module Direction
module ClassMethods
def stop
puts 'go!'
end
end
end
Guess what will happen if you do this:
Car.stop
Since the method was defined inside Direction module, when the method gets called on Car it will be calling the method from the Direction module.
Car.stop
go!
=>nil
Updated based on comments:
If you prefer to use extend vs include you would need to do this instead:
module Direction
def self.extended(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
end
class Car
extend Direction
end
In this example, all the methods which were inherited from the module are "copied" to the class extending them. This avoids the problem of possible result of redefining the module method which I warned about when using include previously in my answer.
But you may want to look at answers to this question for ideas about when and why to use either case.

Returning Module Class instead of Model Class with self.class Ruby/Rails

I am trying to DRY my code by implementing modules. However, I have constants stored in models (not the module) that I am trying to access with self.class.
Here are (I hope) the relevant snippets:
module Conversion
def constant(name_str)
self.class.const_get(name_str.upcase)
end
end
module DarkElixir
def dark_elixir(th_level)
structure.map { |name_str| structure_dark_elixir(name_str, th_level) if constant(name_str)[0][:dark_elixir_cost] }.compact.reduce(:+)
end
end
class Army < ActiveRecord::Base
include Conversion, DarkElixir
TH_LEVEL = [...]
end
def structure_dark_elixir(name_str, th_level)
name_sym = name_str.to_sym
Array(0..send(name_sym)).map { |level| constant(name_str)[level][:dark_elixir_cost] }.reduce(:+) * TH_LEVEL[th_level][sym_qty(name)]
end
When I place the structure_dark_elixir method inside the DarkElixir module, I get an error, "uninitialized constant DarkElixir::TH_LEVEL"
While if I place it inside the Army class, it finds the appropriate constant.
I believe it is because I am not scoping the self.constant_get correctly. I would like to keep the method in question in the module as other models need to run the method referencing their own TH_LEVEL constants.
How might I accomplish this?
Why not just use class methods?
module DarkElixir
def dark_elixir(th_level)
# simplified example
th_level * self.class.my_th_level
end
end
class Army < ActiveRecord::Base
include DarkElixir
def self.my_th_level
5
end
end
Ugh. Method in question uses two constants. It was the second constant that was tripping up, not the first. Added "self.class::" prior to the second constant--back in business.
def structure_dark_elixir(name_str, th_lvl)
name_sym = name_str.to_sym
Array(0..send(name_sym)).map { |level| constant(name_str)[level][:dark_elixir_cost] }.reduce(:+) * self.class::TH_LEVEL[th_lvl][sym_qty(name_str)]
end

How do I encapsulate included module methods in Ruby?

I want to be able to have methods in a module that are not accessible by the class that includes the module. Given the following example:
class Foo
include Bar
def do_stuff
common_method_name
end
end
module Bar
def do_stuff
common_method_name
end
private
def common_method_name
#blah blah
end
end
I want Foo.new.do_stuff to blow up because it is trying to access a method that the module is trying to hide from it. In the code above, though, Foo.new.do_stuff will work fine :(
Is there a way to achieve what I want to do in Ruby?
UPDATE - The real code
class Place < ActiveRecord::Base
include RecursiveTreeQueries
belongs_to :parent, {:class_name => "Place"}
has_many :children, {:class_name => 'Place', :foreign_key => "parent_id"}
end
module RecursiveTreeQueries
def self_and_descendants
model_table = self.class.arel_table
temp_table = Arel::Table.new :temp
r = Arel::SelectManager.new(self.class.arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(model_table[:parent_id].eq(temp_table[:id]))
nr = Place.scoped.where(:id => id)
q = Arel::SelectManager.new(self.class.arel_engine)
as = Arel::Nodes::As.new temp_table, nr.union(r)
arel = Arel::SelectManager.new(self.class.arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
self.class.where(model_table[:id].in(arel))
end
def self_and_ascendants
model_table = self.class.arel_table
temp_table = Arel::Table.new :temp
r = Arel::SelectManager.new(self.class.arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(temp_table[:parent_id].eq(model_table[:id]))
nr = Place.scoped.where(:id => id)
q = Arel::SelectManager.new(self.class.arel_engine)
as = Arel::Nodes::As.new temp_table, nr.union(r)
arel = Arel::SelectManager.new(self.class.arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
self.class.where(model_table[:id].in(arel))
end
end
Clearly this code is hacked out and due some serious refactoring, and the purpose of my question is to find out if there is a way I can refactor this module with impunity from accidentally overwriting some method on ActiveRecord::Base or any other module included in Place.rb.
I don't believe there's any straightforward way to do this, and that's by design. If you need encapsulation of behavior, you probably need classes, not modules.
In Ruby, the primary distinction between private and public methods is that private methods can only be called without an explicit receiver. Calling MyObject.new.my_private_method will result in an error, but calling my_private_method within a method definition in MyObject will work fine.
When you mix a module into a class, the methods of that module are "copied" into the class:
[I]f we include a module in a class definition, its methods are effectively appended, or "mixed in", to the class. — Ruby User's Guide
As far as the class is concerned, the module ceases to exist as an external entity (but see Marc Talbot's comment below). You can call any of the module's methods from within the class without specifying a receiver, so they're effectively no longer "private" methods of the module, only private methods of the class.
This is quite an old question, but I feel compelled to answer it since the accepted answer is missing a key feature of Ruby.
The feature is called Module Builders, and here is how you would define the module to achieve it:
class RecursiveTreeQueries < Module
def included(model_class)
model_table = model_class.arel_table
temp_table = Arel::Table.new :temp
nr = Place.scoped.where(:id => id)
q = Arel::SelectManager.new(model_class.arel_engine)
arel_engine = model_class.arel_engine
define_method :self_and_descendants do
r = Arel::SelectManager.new(arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(model_table[:parent_id].eq(temp_table[:id]))
as = Arel::Nodes::As.new temp_table, nr.union(r)
arel = Arel::SelectManager.new(arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
self.class.where(model_table[:id].in(arel))
end
define_method :self_and_ascendants do
r = Arel::SelectManager.new(arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(temp_table[:parent_id].eq(model_table[:id]))
as = Arel::Nodes::As.new temp_table, nr.union(r)
arel = Arel::SelectManager.new(arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
self.class.where(model_table[:id].in(arel))
end
end
end
Now you can include the module with:
class Foo
include RecursiveTreeQueries.new
end
You need to actually instantiate the module here since RecursiveTreeQueries is not a module itself but a class (a subclass of the Module class). You could refactor this further to reduce a lot of duplication between methods, I just took what you had to demonstrate the concept.
Mark the method private when the module is included.
module Bar
def do_stuff
common_method_name
end
def common_method_name
#blah blah
end
def self.included(klass)
klass.send(:private, :common_method_name)
end
end

Simulating abstract classes in Ruby (Rails)

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.

Resources