Advice on Using Modules with Ruby on Rails

Advice on using modules with Ruby on Rails

Modules have 2 primary purposes :

  1. Namespacing
  2. Mixins

Module Namespacing is generally used to organize code better and to facilitate safer and coherent scoping.

But modules find their major use as mixins. Its Ruby's way of providing multiple inheritance. For example, lets say you have methods that need to be accessed across Classes (for instance across different models/controllers etc). Instead of repeating those methods in each class, that don't necessarily apply just to that class, you abstract those methods out into a module and include or extend the module in the appropriate class.

It depends on how tightly coupled a module is with the app directory to decide as to
where to store the module. A few patterns in storing modules :

  1. The /lib directory, if the module does not particularly 'interact' with app/.
  2. The app/models directory, but may cause confusion with actual ActiveRecord models.
  3. 37 Signals introduced a pattern of treating them as 'concerns' and storing them in app/concerns.

However, if in case you have a method that you're using just for your Users Controller, it is recommended that you put that code into your User model, because that's where the business logic for the 'User' should be.

By 'ActiveRecords', I'm assuming you mean Model classes (such as User).
You CAN access model classes and perform ActiveRecord operations (such as User.find(:all)) on them in modules.

However, as you guessed right, you can't use params, you'll have to pass that as an argument to the module's method.

Advice on Modular Design in Rails App

Rails engines have the option of an isolated namespace, which works as you describe with AddressBook:User.contacts, but it isn't always best to use it. Without it you can define a User model in the app and in each engine, extend that model as needed. E.g. in the app:

class User < ActiveRecord::Base
def embiggen
self.bigness *= 2
end
end

... and in the engine:

class User
has_many :contacts
end

Then you always call User and don't have to add an Engine::User prefix.

How to create and use a module using Ruby on Rails 3?

To 1. A module is created/opened
by simply saying:

module MyModule
def first_module_method
end
end

To 2. The lib folder. If you want to organize your modules in the lib folder, you can put them into modules themselves. For example, if you wanted a subfolder super_modules your modules would be defined as follows:

module SuperModules
module MyModule
def first_module_method
end
end
end

To 3./5. When including the module in a class you can simply call the modules methods as if they were defined within the class:

class MyClass
include MyModule
def some_method
first_module_method #calls module method
end
end

To 4.
Frst, make sure that your module is really needed in every class of your application. If it isn't it makes sense to only include it where it is need so as not to bloat the classes that don't need it anyways. If you really want the module everywhere, include look at the class hierarchy of your classes in the app. Do you want the module in all models? You could open ActiveRecord::Base and add add your module there.

How to use modules in Rails application

You might have to restart the rails server for it to recognize stuff in the lib directory.

How do I use modules in `spec/support/` for Ruby on Rails RSpec tests

Nothing in spec is autoloaded by default.

Typically one adds a line to load spec/support files in spec/rails_helper.rb

Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f }

Note that this will add load time to all test runs regardless of whether they use your support files. This is fine for general use support files, but not such a good idea for support files specific to a single class. Those test classes will pollute every test.

Instead, consider writing them as a shared context and including as needed. Put them in a shared_context directory. You can also load all shared contexts and examples.

Dir[Rails.root.join('spec/**/shared*/*.rb')].sort.each { |f| require f }

Note that spec/support/page_objects/foo/foo_bar.rb would normally be for Foo::FooBar. FooBar should go into spec/support/page_objects/foo_bar.rb. This will have no material effect on your code, but it's good to have naming consistent with Rails standards.

Ruby (rails) - including module in model

The other answers have pointed out your issue you should have extend instead of include:

class Job
extend API

You might also like to consider using the following pattern which aside from being useful can also help to alleviate the confusion, since you can tell straight away that your class methods are in the ClassMethods module while your instance methods are directly in the MyModule module.

module MyModule

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def my_class_method
end
end

def my_instance_method
end
end

This uses Ruby's included callback (which is fired every time a module in included in a class), we redefine the callback (which normally doesn't do anything), to extend the class in which the module was included with the ClassMethods sub module. This is a very common metaprogramming pattern in Ruby. If you use this pattern you no longer have to worry about extend, just use include:

class MyClass
include MyModule

then:

MyClass.my_class_method
MyClass.new.my_instance_method

You can also take this pattern a little bit further:

module MyModule
include ClassMethods

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def my_class_method
end
end

def my_instance_method
end
end

Notice that the parent module includes its child ClassMethods module directly. This way my_class_method becomes both an instance and a class method:

class MyClass
include MyModule

MyClass.my_class_method
MyClass.new.my_class_method

You have to be a bit careful if you do that about how you code your method in the ClassMethods child module. But I have found this pattern extremely handy on occasion.

Ruby on Rails: where should I store modules?

As you might know, modules are generally used either as namespaces or as mixins.

Where you place a module depends on how tightly coupled a module is with the app directory . A few patterns in storing modules :

  1. The /lib directory, if the module does not particularly 'interact' or concern the app/ and you treat the module as an internal plug-in.

  2. The app/models directory, would be an appropriate place if your module is central to your business logic. A popular use case here, is where you use a module as a mixin to DRY your models/controllers.

  3. 37 Signals introduced a pattern of treating them as 'concerns' and storing them in app/concerns.

If your module uses a gem, you may need to require the gem in the module (sometimes a require is not at all necessary).

Your 3rd question is not clear. Sorry about that. Not quite sure what you're trying to do.

Loading module into User Model in Rails

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.



Related Topics



Leave a reply



Submit