Auto-create the containing module of a class - ruby-on-rails

In Rails you can create a model under app/foo/bar.rb, with bar.rb containing:
class Foo::Bar
def some_method
puts "I work fine"
end
end
If you try to do this in a pure ruby app you'd get a NameError: uninitialized constant Foo unless you've already initialized a module Foo.
What is Rails doing that allows it to create classes without first initializing their containing module? Is it possible to import this behavior through something like activesupport, or are we left to implement on our own?

Rails modifies the Class class to include a const_missing method which gets called when an undefined class is used. It then loads things to try and load the requested class.
The implementation of this in ActiveSupport is in lib/active_support/dependencies.rb.

actually model class created is extend to < ActiveRecord::Base

Related

Accessing namespaced class in gem vs Rails

I've worked with a couple of Ruby gems and also Rails. One thing I've never fully understood is why Rails required explicit class constant references for code defined in the /lib folder. In a ruby gem, I could create something like this:
lib/my_gem/custom_error.rb
module MyGem
class CustomError < StandardError
end
end
lib/my_gem/some_class.rb
module MyGem
class SomeClass
def initialize
raise CustomError
end
end
end
Whether it's an error, another class or whatever, as long as the calling class is in the same namespace as the referenced class, ruby would initialize the correct class CustomError in the case above. Moving to Rails, this is a different story and this code would result in an uninitialized constant error. In Rails I would have to raise MyGem::CustomError instead. Why is this the case? I assume it has something to do with autoloading. Is there a way around this or is this standard?

How to include a module in a class

A module in my gem is included in a class in another gem, which is extended by a custom class in a Rails app:
My gem:
module MyGem
def my_method
end
end
AnotherGem.send :include, MyGem
Another gem:
class AnotherGem
end
Class in Rails app:
class ClassInRailsApp < AnotherGem
end
Running this leads to the following behavior:
$ rails c
Loading development environment (Rails 5.1.4)
irb(main):004:0> MyGem.method_defined? :my_method
=> true
irb(main):005:0> AnotherGem.method_defined? :my_method
=> true
irb(main):006:0> ClassInRailsApp.method_defined? :my_method
NoMethodError: undefined method `my_method' for ClassInRailsApp:Class
How can I make sure my module is included before the class is extended?
EDIT:
I tried to directly include MyGem in ClassInRailsApp and the specified instance method is still not available. Could the issue be related to that?
In your thinking, you're just calling a method, e.g. #object.my_method. In reality, you're calling Class level method, e.g. Object.my_method, but have it defined as an instance level method. The correct way to do what you're trying would be Object.new.my_method, however, don't do that.
To call a method like this you'd have to define it as a method on the class. See this page, for a better understanding. Specifically the section "A Common Idiom" on how to define Class level methods via a module.

Calling a module's methods on a Gem's object?

I've generated an object via a ruby gem (Koala) and I've written a few modules with helper methods. What do I need to do in order to be able to use the methods within the modules on the object?
If I, model_object = Model.new, model_object will have access to all the instance variables but object does not (see below).
Ruby 2.1, Rails 4.1
config/application.rb - Autoloading modules in folder
config.autoload_paths << Rails.root.join('lib/module_folder')
Model
class Model < ActiveRecord::Base
include Module
include Module::Module2
include Module::Module3
def self.create_account(token)
object = Module.module_class_method(token) #this works and generates the Koala object
ERROR: object.module2_instance_method # Error: NoMethodError Exception: undefined method
end
end
Module
module Module
extend ActiveSupport::Concern
end
Module2
module Module
module Module2
def module2_instance_method
end
end
end
SOLVED MYSELF
- the issue was the include statements being within the class, if I moved them outside it worked.
I believe if you include your modules somewhere under the app/ directory - they will be included automatically. Otherwise, you actually have to require them in your rails code explicitly with a require statement
Without seeing the actual code, I think the problem with Module2 in your code snippet is the self. method.
Because you are calling module2_instance_method on an instance of your object, the method in the module cannot have the self. because that designates a class method and, as such, would have to be called as Module::Module2.module2_instance_but_not_really_because_I_am_a_class_method.
I believe if you change def self.module2_instance_method ... end to def module2_instance_method ... end, you should no longer receive the NoMethodError exception.
Apologies if I've misread or misunderstand the OP.
Moved the include statements from inside to above the class declaration and all methods began to work. My assumption is that when they are within the statement they are only available to objects of that class.

Create bare rails controller class

I'm trying to create clean controller based on ActionController::Base. That's what I try:
class MetalController
ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
include left
end
end
From Rails doc:
Shortcut helper that returns all the modules included in
ActionController::Base except the ones passed as arguments:
This gives better control over what you want to exclude and makes it
easier to create a bare controller class, instead of listing the modules
required manually.
My another controller inherits from MetalController :
class API::BaseController < MetalController
#.... my awesome api code
end
So this not work then i launch rails server:
block in <module:AssetPaths>': undefined methodconfig_accessor' for
MetalController:Class (NoMethodError)
Rails 4.1.0, Ruby 2.1.0
Update:
If i include ActiveSupport::Configurable
throws the errors:
_implied_layout_name': undefined local variable or method
controller_path' for MetalController:Class (NameError)
You need to inherit from ActionController::Metal:
class MetalController < ActionController::Metal
ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
include left
end
end

How do I properly include a module and call module functions from my Rails model?

I have a model, Show and a module Utilities
class Show < ActiveRecord::Base
include Utilities
...
def self.something
fix_url("www.google.com")
end
end
My Utilities file is in lib/utilities.rb
module Utilities
def fix_url(u)
!!( u !~ /\A(?:http:\/\/|https:\/\/)/i ) ? "http://#{u}" : u
end
end
But Rails is throwing a NoMethodError for "fix_url" when I call it in my show class. Do I have to do something different when including a module in my model?
Thanks!
try injecting that mixin via the extend instead of include. Basically, because you are calling the mixin method from a class method, but including a mixin only makes its instance methods available. You can use the extend style to get class methods.
Search around for Ruby include and extend to learn the differences. A common pattern is to do it like here:
http://www.dcmanges.com/blog/27
Where you use the included hook to mixin both instance and class level methods.
#Tony - this works for me
class User < ActiveRecord::Base
extend Utilities
def self.test
go()
end
end
module Utilities
def go
puts "hello"
end
end
From console:
>> User.test
hello
=> nil
At no point do I have to explicitly call a method with self.
It worked for me. Have you tried restarting your server/console session?
Edit: If you want to just call Utilities.fix_url you can do that - no include/extend necessary.

Resources