Here's the setup:
# app_controller.rb
class AppController; end
# org/app_controller.rb
module Org
class AppController < ::AppController; end
end
# org/admin/app_controller.rb
module Org
class Admin::AppController < AppController; end
end
Why does Org::Admin::AppController inherit from AppController, and not Org::AppController, considering that the class definition is namespaced?
This is because by the time you opened Org::Admin::AppController, Org::AppController must not have been defined, but ::AppController must have been . Perhaps your files are not being 'required' in the order you assumed them to be? You might solve this by adding a require <file containing base class> in the file where you create your derived class.
(Minor style guideline: Don't use :: to refer to classes and modules that you are opening for definition.)
Edit reason: I ran some tests and I must have been mistaken.
Related
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.
I would need some help with restructuring a Rails application. Below you can find the extra details. The main questions:
I modified the code after #Nathan suggestions. Got one step further to heaven but still can smell some sulfur.
SOLVED: Why am I getting the error: "superclass mismatch for class Report"
SOLVED: Can I have some guidelines, how to structure complex namespacings, extensions and STI in Rails4?
After server restart, I get the error on the Spock main page: --"Unable to autoload constant Bridge, expected ...route goes here... app/models/spock/report/bridge.rb to define it"-- After refreshing the page, the error is gone. But still, indicates some problem. This is reproducible after every server restart. HINT: Possibly an autoload issue... Any idea is welcome.
Environment:
WEBrick 1.3.1
Rails 4.0.9
Ruby 2.1.5
Error details:
Unable to autoload constant Bridge, expected ...route goes here... bridge.rb to define it
app/views/spock/spock/index.html.erb:11:in `block in _app_views_spock_spock_index_html_erb___3895075684249237486_132397353980'
app/views/spock/spock/index.html.erb:6:in `map'
app/views/spock/spock/index.html.erb:6:in `_app_views_spock_spock_index_html_erb___3895075684249237486_132397353980'
File structure:
/app/controllers/spock/spock_controller.rb
/app/controllers/spock/marketplaces_controller.rb
/app/controllers/spock/reports_controller.rb
/app/modules/spock/spock_connector.rb
/app/modules/spock/report.rb
/app/modules/spock/report/bridge.rb
/app/modules/spock/report/metric.rb
Files:
# application.rb
...
config.autoload_paths += Dir[Rails.root.join('app', 'models', 'spock', '{**}')]
...
# routes.rb
namespace :spock do
get '/', :to => 'spock#index'
resources :marketplaces, only: [] do
resources :reports, only: [:show]
end
# spock_controller.rb
module Spock
class SpockController < ApplicationController
def index
#report_categories = Spock::Report.distinct.pluck(:category)
...
# marketplaces_controller
module Spock
class MarketplacesController < SpockController
end
end
# reports_controller
module Spock
class ReportsController < MarketplacesController
helper SpockHelper
...
# spock_connector.rb
module Spock
class SpockConnector < ::ActiveRecord::Base
self.abstract_class = true
establish_connection "spock_#{Rails.env}"
end
end
# report.rb
module Spock
class Report < SpockConnector
attr_accessor :legal_entity
end
end
# bridge.rb
module Spock
class Bridge < Report
...
# metric.rb
module Spock
class Metric < Report
...
Database:
/* reports table */
id, type, category,
1 Spock::Bridge Bridge
2 Spock::Metric Metric
You are opening/defining the Report class in 3 separate files.
In report.rb, you specify ::Spock::SpockConnector as the superclass for Report, but in bridge.rb and metric.rb, you are not specifying an explicit superclass, so Ruby uses Object as its superclass.
You cannot have both Object and ::Spock::SpockConnector as the immediate superclass of Report.
Solution:
In all 3 files you need to use class Report < ::Spock::SpockConnector
As soon as you fix that, you will encounter a similar error, because you are opening SpockConnector in several files, once with ::ActiveRecord::Base as the superclass, and other times with implicit Object as the superclass.
This is another contradiction, so in all files that open/define SpockConnector, you need to use ::ActiveRecord::Base as the superclass.
As for your namespacing question, that's opinion-based, but I will say that in Ruby there is no need to nest subclasses inside namespaces.
For example, you could keep Report, SpockConnector, Metric, and Bridge all at the same namespace level:
module Spock
class SpockConnector < ActiveRecord::Base
end
end
module Spock
class Report < SpockConnector
end
end
module Spock
class Metric < Report
end
end
module Spock
class Bridge < Report
end
end
The fully scoped names are then:
Spock::SpockConnector
Spock::Report
Spock::Bridge
Spock::Metric
OK. After a day of trial and error I found the cause of autoload issue.
If you find your self touching the autoload structure, that is a good sign on being on the wrong path.
So I removed the autoload path from the application.rb file.
And modified the STI classes a little bit, to match the Rails autoload logic;
# bridge.rb
module Spock
class Report
class Bridge < Report
...
# metric.rb
module Spock
class Report
class Metric < Report
...
Database:
/* reports table */
id, type, category,
1 Spock::Report::Bridge Bridge
2 Spock::Report::Metric Metric
Including the STI classes in the superclass makes them able to be autoloaded from the subfolder without including explicitly the folder in the application.rb file.
The downside is that the classes absolute paths gets longer...
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.
I have a folder structure like the following in one of my projects:
lib
bar.rb
bar
other_bar.rb
another_bar.rb
next_bar.rb
...
bar.rb
require File.expand_path(File.dirname(__FILE__) + "/bar/other_bar.rb")
class Bar
puts "running BarBase"
end
bar/other_bar.rb
module Bar
class OtherBar
puts "running module Bar with class OtherBar"
end
end
If I now run ruby bar.rb I get this:
running module Bar with class OtherBar
bar.rb:3:in `': Bar is not a class (TypeError)
I'd like to have a similar structure to a rails model inheritance structure. How can I fix this? So far as I know ruby does not support this out of the box. Is there a workaround for such a situation?
Bar can't be a module and a class, they are different things.
Change bar.rb to module Bar or change other_bar.rb to class Bar.
Whichever it is, it has to be consistent. You can't change one to the other. The question is which should it be? If Bar is a container for other classes and only has a few global singleton methods? Then it's a module. But if it can be instantiated, then it's a class.
And yes, you can nest classes. This is totally acceptable:
class Bar
class OtherBar
puts "running module Bar with class OtherBar"
end
end
Bar::OtherBar.new # yay!
Modules and Classes can be nested inside either other in any way you see fit.
Edit with some commented examples to help clear this all up:
module Foo
# Foo::A
class A
# simple namespaced class
end
# Foo::B, inherits from Foo::A
class B < A
# inherting from a class in the same namespace
end
# modify Foo::B
class B
# When modifying an existing class you don't need to define the superclass
# again. It will raise an error if you reopen a class and define a different
# superclass. But leaving it off is fine.
end
# nested module Foo::Inner
module Inner
# Foo::Inner::C
class C
# simple more deeply namespaced class
end
# Foo::Inner::D, inherits from Foo::A
class D < A
# inherits from a class in a parent namespace
# works because ruby looks upward in the nesting chain to find missing constants.
end
# Foo::Inner::Foo
class Foo
# simple nested class with the same name as something in a parent namespace
# This is a totally different Foo, because it's in a different namespace
end
# Foo::Inner::E, inherits from Foo::Inner::Foo
class E < Foo
# class inhereting from another class in the same namespace
# Foo::Inner::Foo is "closer" than the global Foo, so that gets found as the superclass
end
# Foo::Inner::F, which mixes in the gloabl module Foo
class F
# the :: constant prefix says to start looking in the global namespace
# so here we include the top level module Foo, and not the "closer" in namespace Foo::Inner::Foo
include ::Foo
# This is an error. This attempts to include the class Foo::Inner::Foo since thats the closest by namespace
# thing that matches the constant Foo. (you can't include classes, only modules)
# You need the :: prefix to grab the global Foo module
include Foo
end
end
end
# Z decalred in the global namespace, which inherits from the deeply nested class Foo::Inner::C
class Z < Foo::Inner::C
# Any class anywhere can inherit from any other class in any namespace.
# Just drill in!
end
# the following 2 declarations at this point would be identical
# This defines a class deep with in a namespace
class Foo::Inner::Foo::Bar < Foo::A
end
# same as above, but reopens each namespace
module Foo
module Inner
class Foo
class Bar < ::Foo::A
end
end
end
end
Just use class Bar instead of module Bar. In Ruby, classes can be reopened and added to.
It is recommended not to use a class as a namespace, especially if you use Rails.
If you want to keep the Bar class in lib/bar.rb, an option is to move the other classes in the Bars namespace in lib/bars.rb.
Read more as to why here: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/
Update: this behavior is fixed in ruby 2.5
See: https://blog.bigbinary.com/2017/10/18/ruby-2.5-has-removed-top-level-constant-lookup.html
In my app I am having to interface with some 3rd party software I hope one day to replace. So, rather than keeping all the code that maps between my models' data and the form it needs to be in for the 3rd party software in the models themselves, I've created a mapper module for each model, isolating the code somewhere that's easy to delete when the time comes.
So I have something like the following:
app/
models/
people.rb
mappers/
people_mapper.rb
Ideally, I'd like to automatically include the modules in the model class with the matching name, the same way that helpers are automatically included in views of the same name. How/where are the helpers automatically included, and is this also the best place for me to add my own code?
you can try something like this :
module Mapper::Core
def self.included( base )
base.extend( ClassMethods )
end
module ClassMethods
# model class method to include matching module
# this will throw an error if matching class constant name does not exist
def has_mapping
#mapper_class = Kernel.const_get( "Mapper::#{self}Mapper" )
include #mapper_class
end
# an accessor to the matching class mapper may come in handy
def mapper_class
#mapper_class
end
end
end
then then require and include the module in ActiveRecord::Base in an initializer (make sure that your Mapper module requires all the files in your 'mappers' folder, or use config.autoload_paths).
If you don't want to use the has_mapping class method at all, you can try to override ActiveRecord::Base's self.inherited callback, but it might become dangerous:
def self.included( base )
base.extend( ClassMethods )
base.instance_eval <<-EOF
alias :old_inherited :inherited
def self.inherited( subclass )
subclass.has_mapping
old_inherited( subclass )
end
EOF
end
I did not try any of this, so proceed with caution.
EDIT :
i was tired when i wrote this. there is a much simpler way to autoinclude the matching module :
module Mapper::Core
def self.included( base )
begin
mapper_class = Kernel.const_get( "Mapper::#{base.name}Mapper" )
base.instance_eval( "include #{mapper_class}" )
rescue
Logger.info "No matching Mapper Class found for #{base.name}"
end
end
end
initialize this with :
ActiveRecord::base.instance_eval( 'include Mapper::Core' )
all inheriting class will now include Mapper::Core, which will trigger inclusion of matching class.