Defining/nesting modules and classes in a Rails app - ruby-on-rails

I'm trying to define a variety of modules/classes in a Rails app. My directory structure looks something like this:
lib/
fruit/ # just a module, with no associated file
fruit_operator.rb
apple.rb # abstract class, defines behavior for inheritance
orange.rb # abstract class, defines behavior for inheritance
apple/
granny_smith.rb # should inherit from apple.rb
red_delicious.rb
orange/
valencia.rb
seville.rb
I want two things:
The sub-classes should inherit from their parent classes (Apple and Orange).
I should be able to access these classes from a top-level (within /fruit file -- ie fruit_operator.rb
All the attempts I've tried to get this working are throwing an error of some sort or another.
Attempt # 1:
apple.rb
module Fruit
class Apple
def juicy
true
end
end
end
apple/granny_smith.rb
module Fruit
class GrannySmith::Apple
end
end
When I try to access GrannySmith from fruit_operator.rb I run into errors. Accessing as simply GrannySmith generates
uninitialized constant Fruit::FruitOperator::GrannySmith
If I try Fruit::GrannySmith, I get
uninitialized constant Fruit::GrannySmith
If I try Apple::GrannySmith or Fruit::Apple::GrannySmith, I hit the error
Unable to autoload constant Fruit::Apple::GrannySmith, expected /lib/fruit/apple/granny_smith.rb to define it
Attempt #2:
apple.rb
class Fruit::Apple
def juicy
true
end
end
apple/granny_smith.rb
class GrannySmith < Fruit::Apple
end
Attempting to access from fruit_operator.rb, I run into identical errors as the above.
Attempt #3:
apple.rb
class Fruit::Apple
def juicy
true
end
end
apple/granny_smith.rb
class Fruit::Apple::GrannySmith
end
This last version allows me to access the class directly from fruit_operator.rb (as Apple::GrannySmith), but it doesn't inherit from Apple!
Any idea how to structure/access these classes and modules? I've looked around quite a bit (on SO and elsewhere), and can't find a great guide for how to do this, particularly in a Rails app.

You must import the definition of the fruit files into the fruit operator file. For example,
require_relative './apple/granny_smith'

I think you're best solution is to implement Fruit as a class, and have Apple and Orange both inherit from Fruit, and GrannySmith inherit from Apple, like so:
Class Fruit
def seeds?
true
end
end
Class Apple < Fruit
def juicy
true
end
end
class GrannySmith < Apple
def color
"green"
end
end
Depending on what your need for the fruit_operator is, you may choose to include those methods/actions via a mixin Module.

Related

Rails namespacing concerns based on model name

I am looking to separate concerns for some subset of function specific to a model.
I have referenced here and followed this pattern
module ModelName::ConcernName
extend ActiveSupport::Concern
included do
# class macros
end
# instance methods
def some_instance_method
end
module ClassMethods
# class methods here, self included
end
end
However, when I try to start the server it would result in the following error
Circular dependency detected while autoloading constant ModelName::ConcernName
I am wondering what is the best way to do concerns for some subset functions of a model.
Edit
Providing the model code:
path: app/models/rent.rb
Now I have a lot of checking logic in my model
class Rent < ActiveRecord::Base
def pricing_ready?
# check if pricing is ready
end
def photos_ready?
# check if photo is ready
end
def availability_ready?
# check if availability setting is ready
end
def features_ready?
# check if features are set
end
end
I want to separate it in concern
class Rent < ActiveRecord::Base
include Rent::Readiness
end
And organise the concern by namespace
path: app/models/concerns/rent/readiness.rb
module Rent::Readiness
extend ActiveSupport::Concern
included do
# class macros
end
# instance methods
def pricing_ready?
# check if pricing is ready
end
...
module ClassMethods
# class methods here, self included
end
end
Now I got it working if I just do class RentReadiness with the path in app/models/concerns/rent_readiness.rb
You can scope it to Rents and place to concerns/rents/readiness.rb:
module Rents
module Readiness
extend ActiveSupport::Concern
included do
# class macros
end
end
end
And in model:
class Rent < ActiveRecord::Base
include Rents::Readiness
end
You can make it work by just moving the folder with model-specific concerns from concerns to models. So, you would have:
models/
rent.rb
rent/
readiness.rb
I like this convention of using the model as the namespace for its concerns because it lets you remove some redundancy from code:
Since you are defining the concern within the model class, in the model you can write include Readiness instead of include Rent::Readiness
When defining the concern, you can use module Rent::Readiness instead of
class Rent < ApplicationRecord
module Readiness
...
Which would be another way of fixing the circular dependency problem you mentioned in your question.
Rails uses activesupport to load classes and modules as they are defined by inferring the file path based on the class or module name, this is done as the Ruby parser loads your files and come across a new constant that has not been loaded yet. In your case, the Rent model is parsed up to the Rent::Readlines reference, at which point activesupport goes off to look for the rent/readlines.rb code file that matches the name. This file is then then parsed by ruby, but on the first line, The still unloaded Rent class is referenced, which triggers activesupport to go off and look for the code file that matches the name.

How to split a module over multiple files in Rails?

In my Rails app I have a module, which holds all the classes that make up an API wrapper. Here's the module's structure:
(file: zway.rb)
module Zway
class API
# instantiates API object
end
class Main
# holds top level functions
end
class Controller
# holds controller functions
end
class Error < StandardError
end
class BadRequestError < Error
end
end
Now that module is getting too big to keep it in one file so I want to split it up. As there are several classes in the module, I figured that every big class should be one file. So I didn't think a lot but tried to take out one class to see if it would work. Like so:
(file: zway.rb)
module Zway
class API
# instantiates API object
end
class Controller
# holds controller functions
end
class Error < StandardError
end
class BadRequestError < Error
end
end
(file: main.rb)
module Zway
class Main
end
end
But now I am getting this error which doesn't sound right to me as I do exactly what the error complains about: defining class Main in main.rb :
Unable to autoload constant Main, expected /bla/homer/app/models/main.rb to define it
I've searched on the net how to use modules but mostly what I found was about multiple inheritance and namespacing. But not about the practics of using modules to organize code.
If you put your classes in modules, as per convention you should define them within folders named with the module_name. That's the whole point of organising. You can do something like this and give it a go. First organize your code and file like this ->
models/
zway/
main.rb
zway.rb
And inside your main.rb you namespace it like this ->
class Zway::Main
...
end
and inside your zway.rb you define the module
module Zway
...
end
In this way, when you're trying to access the main class it, since its namespaced, it will look it up inside a folder zway by convention. And all your models are organized neatly within their respective folders.

Rails - Best practice for abstract class definition and file naming

I want to define 3 classes:
a MotherClass (abstract, can not be inferred)
a SubClassA (inherits from MotherClass)
a SubClassB (inherits from MotherClass)
What is the best solution to declare it in Rails ?
1. Put everything in app/models/
MotherClass < AR::Base in app/models/mother_class.rb
SubClassA < MotherClass in app_models/sub_class_a.rb
SubClassB < MotherClass in app/models/sub_class_b.rb
Advantage: not very complicated to implement
Inconvenient: a big mess in models folder
2. Create a module for the two subclasses
MotherClass < AR::Base in app/models/mother_class.rb
MotherModule::SubClassA < MotherClass in app/models/mother_module/sub_class_a.rb
MotherModule::SubClassB < MotherClass in app/models/mother_module/sub_class_b.rb
Advantage: same than Solution 1
Inconvenient: naming MotherModule and MotherClass with different names, but they mean almost the same thing
3. Create a module for the 3 classes
MotherModule::Base < AR::Base in app/models/mother_module/base.rb
MotherModule::SubClassA < MotherModule::Base in app/models/mother_module/sub_class_a.rb
MotherModule::SubClassB < MotherModule::Base in app/models/mother_module/sub_class_b.rb
Advantage: very clean
Inconvenient: need some functions in Base to override (table_name for example)
So my question is: What is the best practice in Rails and
- how to name my classes?
- what are their directories?
First of all, I think you must already realize that ruby does not have true abstract classes. But we can approximate the behavior. And while doing so, it sounds like you have a preference toward organizational structure which I will attempt to address.
I must start by saying, however, that I'm surprised that you're coming at the problem so strongly from the organizational angle. First on my mind would be whether I really wanted to implement single table inheritance or not and then let that drive the organizational problem. Usually the answer here is that Single Table Inheritance is not what you actually want. But... let's dive in!
Using Single Table Inheritance
Here's the standard way to utilize and organize models using Single Table Inheritance:
# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
# An "abstract" method
def method1
raise NotImplementedError, "Subclasses must define `method1`."
end
def method2
puts method1 # raises NotImplementedError if `method1` is not redefined by a subclass
end
end
# app/models/sub_class_a.rb
class SubClassA < MotherClass
def method1
# do something
end
end
# app/models/sub_class_b.rb
class SubClassB < MotherClass
def method1
# do something
end
end
Given the above, we would get an exception when calling MotherClass.new.method2 but not when calling SubClassA.new.method2 or SubClassB.new.method2. So we've satisfied the "abstract" requirements. Organizationally, you called this a big mess in the models folder... which I can understand if you've got tons of these subclasses or something. But, remember that in single table inheritance even then parent class is a model and is / should be usable as such! So, that said, if you'd really like to organize your models file system better then you are free to do so. For example, you could do:
app/models/<some_organizational_name>/mother_class.rb
app/models/<some_organizational_name>/sub_class_a.rb
app/models/<some_organizational_name>/sub_class_b.rb
In this, we are keeping all other things (i.e. the Code for each of these models) the same. We're not namespacing these models in any way, we're just organizing them. To make this work it's just a matter of helping Rails to find the models now that we've placed them in a subfolder of the models folder without any other clues (i.e. without namespacing them). Please refer to this other Stack Overflow post for this. But, in short, you simply need to add the following to your config/application.rb file:
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**/}')]
Using Mixins
If you decide that Single Table Inheritance is not what you want (and they often aren't, really) then mixins can give you the same quasi-abstract functionality. And you can, again, be flexible on file organization. The common, organizational pattern for mixins is this:
# app/models/concerns/mother_module.rb
module MotherModule
extend ActiveSupport::Concern
# An "abstract" method
def method1
raise NotImplementedError, "Subclasses must define `method1`."
end
def method2
puts method1 # raises NotImplementedError if `method1` is not redefined
end
end
# app/models/sub_class_a.rb
class SubClassA
include MotherModule
def method1
# do something
end
end
# app/models/sub_class_b.rb
class SubClassB
include MotherModule
def method1
# do something
end
end
With this approach, we continue to not get an exception when calling SubClassA.new.method2 or SubClassB.new.method2 because we've overridden these methods in the "subclasses". And since we can't really call MotherModule#method1 directly it is certainly an abstract method.
In the above organization, we've tucked MotherModule away into the models/concerns folder. This is the common location for mixins in Rails these days. You didn't mention what rails version you're on, so if you don't already have a models/concerns folder you'll want to make one and then make rails autoload models from there. This would, again, be done in config/application.rb with the following line:
config.autoload_paths += Dir[Rails.root.join('app', 'concerns', '{**/}')]
The organization with the mixins approach is, in my opinion, simple and clear in that SubclassA and SubClassB are (obviously) models and, since they include the MotherModule concern they get the behaviors of MotherModule. If you wanted to group the subclass models, organizationally, into a folder then you could still do this of course. Just use the same approach outlined at the end of the Single Table Inheritance section, above. But I'd probably keep MotherModule located in the models/concerns folder still.
Even though ruby doesn't really have abstract classes, it's powerful enough to let you implement it yourself by implementing self.included on a mixin module. Hopefully this generic example gives you enough to go on for your particular implementation.
module MotherInterface
def self.included base
required_class_methods = [:method1, :method2]
required_instance_methods = [:fizzle, :fazzle]
required_associations = [:traits, :whatevers]
required_class_methods.each do |cm|
raise "MotherInterface: please define .#{cm} class method on host class #{base.name}" unless base.respond_to?(cm)
end
required_instance_methods.each do |im|
raise "MotherInterface: please define ##{im} instance method on host class #{base.name}" unless base.instance_methods.include?(im)
end
required_associations.each do |ass|
raise "MotherInterface: please be sure #{base.name} has a :#{ass} association" unless base.reflections.key?(ass)
end
base.send :include, InstanceMethods
base.extend ClassMethods
end
# inherited instance methods
module InstanceMethods
def foo
end
def bar
end
end
# inherited class methods
module ClassMethods
def baz
end
def bat
end
end
end
class SubClassA < ActiveRecord::Base
include MotherInterface
# ... define required methods here ...
end
class SubClassB < ActiveRecord::Base
include MotherInterface
end
Some advantages to this approach are:
Yes, you can still technically instantiate the mixin, but it's not actually tied to active record, so it tastes more like an abstract class.
The sub classes get to define their own connection information. You have two databases? Differing columns? Cool, no problem. Just implement your instance methods and stuff appropriately.
The dividing line between parent and child is very obvious.
But, there are disadvantages too:
All the meta programming is a bit more complex. You'll have to think a little abstractly (HA!) about how to organize your code.
There are probably other advantages and disadvantages I haven't considered, kind of in a hurry here.
Now, as far as file locations, I would suggest that the mixin itself, presumably mother_interface.rb, go someplace other than your models folder.
In config/application.rb, throw in a line like this:
config.autoload_paths << File.join(Rails.root, 'app', 'lib')
...and then you can create (rails)/app/lib/mother_interface.rb. Really, you should do it however makes sense to you. I dislike the word "concerns" for this, and other people dislike the word "lib." So, use whatever word you like, or make up your own.
Using Single Table Inheritance with little bit meta programming
# app/models/mother_class.rb
class MotherClass < ActiveRecord::Base
def self.inherited(subclass)
subclass.include(OnlyChildMethods)
end
module OnlyChildMethods
extend ActiveSupport::Concern
included do
def child_method_one
puts 'hi one'
end
def child_method_two
puts 'hi two'
end
end
end
end
# app/models/sub_class_a.rb
class SubClassA < MotherClass
def some_specific_method
#some code
end
end
# app/models/sub_class_b.rb
class SubClassB < MotherClass
def some_specific_method
#some code
end
end
mother_class_instance.child_method_one
=> NoMethodError: undefined method 'child_method_one'
sub_class_a_instance.child_method_one
hi one
=> nil

inject/access methods from a parent module inside a model

In Rails I have the following structure
#.../models/A.rb
module A
def m
end
end
#.../models/a/B.rb
class A::B < ActiveRecord::Base
end
This automatically places A as a parent of B. Is there a way to do something like B.m without modifying B? I know that I could do something like B.parent.m and from there create aliases, but then i would have to change B.
I'm looking to somehow inject a code present in A into B, but I don't know where this automatic association is done behind the scenes.
Something like
module A
module C
def mc
end
end
def placed_as_parent (child) # supposing this is the method called to put this module as a parent
super child
child.include(C) #this is what I would like to do
end
end
The question behind it is that I have a module which is already being shared among several models of that folder and I would like to put some common stuff for the models in there without have to manually include/extend a module in each of my models
[[EDITED]]
I'm not being clear with my question. In rails 3 if you do
rails generate active_record:model A::B
it will generate the files
#.../models/A.rb
module A
def self.table_name_prefix
'a_'
end
end
#.../models/a/B.rb
class A::B < ActiveRecord::Base
end
So if I open a console and type
A::B.table_name # -> 'a_b'
A::B.table_name_prefix # -> ''
A::B.parent # -> A
A.table_name_prefix # 'a_'
This happens automatically without any include/extend in the model B. What I want is to include more stuff in A and access it from B, without changing anything on B as i described earlier.
To be honest I'm not sure I fully understand your question but I'll give it a shot anyway.
There is a hook in the Module class that allows you to get a reference to the class the module is being included into. Thus, you could then do virtually anything with it.
An example:
module A
# you can change the class' behavior here
def self.included(klass)
puts "included in #{klass}"
end
end
And then to use it:
class B
include A #this causes the included hook in the module to be called
end
Is this what you're after?
The OP wrote:
The question behind it is that I have a module which is already being shared among several models of that folder and I would like to put some common stuff for the models in there without have to manually include/extend a module in each of my models
Here's what I would do:
module Stuff1
...
end
module Stuff2
...
end
module StuffIWantInSeveralModels
include Stuff1, Stuff2
end
class X < ActiveRecord::Base
include StuffIWantInSeveralModels
end
class Y < ActiveRecord::Base
include StuffIWantInSeveralModels
end
Then when you want to add a new module to several of your models, you only have to write an "include" statement in one place (in the StuffIWantInSeveralModels module).
Each module should be in its own file in the lib directory, with the file name matching the name of the module so Rails auto-loading will work (e.g. stuff_i_want_in_several_models.rb).
Does this achieve what you wanted?

Inheritance and Polymorphism conflicting in ruby on rails

I have an issue with Ruby on Rails.
I have several model classes that inherit from the same class in order to have some generic behaviour.
The parent class is called CachedElement.
One of the child is called Outcome.
I want an other model, called Flow to belong to any child of CachedElement.
Hence Flow has a polymorphic attributes called element, to which it belongs_to
When I create a new flow, that belongs to an Outcome, the element_type is set to "CachedElement" which is the parent class, instead of "Outcome".
This is confusing because since I have several type of CachedElement which are stored in different tables, the element_id refers to several different element.
In short I would like the element_type field to refer to the child class name and not the parent class name.
How can I do that ?
The field element_type is set to the parent class because ActiveRecord expects you to use single-table inheritance when deriving from other models. The field will reference the base class because it refers to the table that each instance is stored in.
If the children of CachedElement are stored in their own tables, it may be more helpful to replace the use of inheritance with the use of Ruby modules. The standard approach for sharing logic between classes is to use mix-ins instead of inheritance. For example:
module Cacheable
# methods that should be available for all cached models
# ...
end
class Outcome < ActiveRecord::Base
include Cacheable
# ...
end
You can now easily use polymorphic associations as you have been doing already, and element_type will be set to the proper class.
the file should go on you lib folder. but...
you could do the inheritance thing as well.
all you need to do is to tell you parent class to act as an abstract class.
# put this in your parent class then try to save a polymorphic item again.
# and dont forget to reload, (I prefer restart) if your gonna try this in
# your console.
def self.abstract_class?
true
end
and thats pretty much it, this was kinda unespected for me and actually really
hard to find in the documentation and anywhere else.
Kazuyoshi Tlacaelel.
Thanks, that's what I did, but it was kind of tricky to be able to inherits both instance and class methods from the module
Class methods can be done by:
module Cachable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def a_class_method
"I'm a class method!"
end
end
def an_instance_method
"I'm an instance method!"
end
end
class Outcome < ActiveRecord::Base
include Cacheable
end
if you want to add class methods and instance methods through a mixin (Module)
then I recommend you to abstract these in different modules.
module FakeInheritance
def self.included(klass)
klass.extend ClassMethods
klass.send(:include, InstanceMethods)
end
module ClassMethods
def some_static_method
# you dont need to add self's anywhere since they will be merged into the right scope
# and that is very cool because your code is more clean!
end
end
module InstanceMethods
# methods in here will be accessable only when you create an instance
end
end
# fake inheritance with static and instance methods
class CachedElement
include FakeInheritance
end

Resources