I have config in my application.rb for loading all models from subfolders:
config.autoload_paths += Dir["#{config.root}/app/models/**/"]
Lets assume, that I want to create model Bar in /models/foo/bar.rb
And now i can namespace the model Bar into folder Foo and name it like this:
class Foo::Bar
end
An opposite approach is to place it into module:
module Foo
class Bar
end
end
Name of the class from global namespace in both cases is the same.
Considering, that i do not need to include this module or include to this module, and made this only for placing models from one domain in namespace, what is the difference between these approaches?
One different is that your second example does not work if you also want to have a Foo class:
class Foo
end
module Foo
class Bar
end
end
# => TypeError: Foo is not a module
class Foo::Bar
end
# no error
Related
Trying to make available the methods I have stored in a Module which is located in app/models (side note: not sure if this is the correct place for modules?).
Module:
module MyModule
class MyClass
def some_method
# do something
end
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
I am getting a NoMethodError:
NoMethodError (undefined method 'some_method' for #<User:0x00007f6a3ce452c0>
You seem to have missunderstood what what modules and classes do in Ruby. In Ruby a module is simply an object that wraps a set of methods and constants.
A module can extend other modules, classes and objects and can be included in classes thus implementing multiple inheritance. Modules in Ruby fill the role that traits, namespaces and singletons do in other languages.
Classes are actually modules (Module is part of the ancestors chain of Class) with the key difference that you can make instances of a class and that class can inherit from a single other class and cannot extend other objects or be included.
The code example here actually doesn't make sense. If you want to declare a method that will be available to classes that include a module you want to declare it in the module itself:
module MyModule
def some_method
# do something
end
end
When you then call User#another_method it will look in the ancestors chain of the User class until it finds the method which is defined in MyModule.
module MyModule
class MyClass
def some_method
# do something
end
end
end
Will actually definte the class MyClass with an instance method that is only available to instances of MyClass. The only thing that the module does here is change the module nesting so that the class is defined in MyModule instead of the global namespace.
If you want to mix in a method from a method into your class then just put the methods directly in the module (without an intermediate class).
Module:
module MyModule
def some_method
# do something
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
Have a look at this answer, you need to instantiate your Class first. Or if you want to
class User < ApplicationRecord
include MyModule
def another_method
my_instance = MyClass.new
my_instance.some_method
end
end
As for a place to store your Module, have a look at this guide about service objects, it gave me some inspiration when it comes to different modules.
I defined one class SomeClass that includes methods with basic functionality. This class is defined in the Foo module.
# foo/
# |--some_class.rb
module Foo
class SomeClass
def some_method
puts "Hi!"
end
end
end
I also have two other modules: Bar and Baz. I want to include the entire namespacing of Foo inside of Bar and Baz, and inherit all methods defined under Foo, and thus to be able to overwrite Foo methods.
I also want to be able to overwrite this method only by specifying a new file in the right folder:
# baz/
# |--foo/
# |--some_class.rb
module Baz
module Foo
class SomeClass < ::Foo::SomeClass
def some_method
puts "Hey!"
end
end
end
end
This works fine. However, I want to be able to use Bar::Foo::SomeClass.some_method without having to create bar/foo/some_class.rb, and define empty modules and an empty SomeClass.
The same applies to inheritance of modules (by including).
Is there a way do accomplish this easily by using include, extend, or require, or something else? Since this is part of a Rails project (under the lib folder), can I use autoloading in a specific way?
The reason that I want to accomplish this is because I have many classes and modules with classes in it under Foo. The Bar and Baz modules should be able to inherit all modules/classes under Foo, and overwriting instance or class methods where it is needed by creating an explicit file. I would like to avoid creating a file for each class or module Foo has for Bar and Baz.
I have the following solution so far. I defined two new files: bar.rb and baz.rb. In bar.rb:
module Bar
def self.const_missing(name)
klass = Object.const_get(name.to_s) if name.to_s =~ /Foo/
return klass if klass
raise
rescue
super
end
end
> Bar
=> Bar # as expected
> Foo
=> Foo # as expected
> Bar::Foo
=> Foo # works!
> Bar::Foo::SomeClass
=> Foo::SomeClass # right
> Baz::Foo::SomeClass
=> Foo::SomeClass # not right...
I am not sure if I am on my way to a good solution now. Any feedback is highly appreciated.
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.
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
I wrote an upsert method for one of my models. I would like all my models to have this upsert method. It seemed to me that the logical solution was to define a model that inherits from ActiveRecord::Base and then have all my other models inherit from that. But if I do that, Rails complains that the new model I created doesn't have a table to go with it, which is true, but I don't care.
Since the way I tried is apparently not the right way to do it, what's the right way to do it?
You can extend ActiveRecord with a module. you only do it in one place and it will be accessible for all models that inherits from ActiveRecord.
module YourModule
def self.included(recipient)
recipient.extend(ModelClassMethods)
recipient.class_eval do
include ModelInstanceMethods
end
end # #included directives
# Class Methods
module ModelClassMethods
# A method accessible on model classes
def whatever
end
end
# Instance Methods
module ModelInstanceMethods
#A method accessible on model instances
def another_one
end
end
end
#This is where your module is being included into ActiveRecord
if Object.const_defined?("ActiveRecord")
ActiveRecord::Base.send(:include, YourModule)
end
There are two ways to do this.
1) To have a parent model, but not need to create a table for it (i.e. an abstract class) you should set
class YourAbstractClass < ActiveRecord::Base
self.abstract_class = true
# rest of class code
end
2) Put the method in a module, that you include from all your models that need it (as in #Mark's answer)
You can move that method to a module and include that module in all the models that require that method.
Like I have this Utils module in lib folder of my app
module Utils
...
def to_name(ref)
ref.gsub('_', ' ').split.collect { |w| w.capitalize }.join(' ')
end
...
end
Then in my model, I say
class MyModel < AR::Base
include Utils
...
end
Probably, if you are using Rails 3, you should load the files in the lib folder by configuring your application.rb
config.autoload_paths += %W(#{config.root}/lib)