Rails: Creating And Using Modules

Reading Time: 4 minutes

Let’s talk about using Ruby Modules in Rails applications.

I want to apply this tutorial to one refactoring task, where two pieces of code look very similar.

    controllers/actions_controller.rb
if @retrospective.can_delete_action?
  @action.destroy
  @action.pusher_destroy(params[:socket_id])
  respond_with @action, location: retrospectives_url
else
  render text: 'Forbidden', status: 403
end
controllers/items_controller.rb
if @retrospective.can_delete_item?
  @item.destroy
  @item.pusher_destroy(params[:socket_id])
  respond_with @item, location: retrospectives_url
else
 render text: 'Forbidden', status: 403
end

We can attack this common problem using Ruby Modules. In said module, we can remove all references to specific resources and make it generic, because we want to use it in any controller.

First of all, we need to create our module. The basic syntax to create a module is:

Open and close a module
module MyModule
   def a_module_method
   end
end

For my example, I’ll be placing our modules in the “lib” directory, within a subfolder called “modules”; after that our module looks like:


      module Modules
        class ResourceDestroyer

          def self.destroy_with_pusher(resource, retrospective, socket_id)
            if retrospective.send("can_delete_#{resource.class.name.underscore}?".to_sym)
             resource.destroy
             resource.pusher_destroy(socket_id)
             resource
           end
         end
       end
     end

##### Let's discuss the code above:

It won't matter the class of the resource that we send to this method, by using `send`, we'll be able to ask if the current user is allowed to delete the resource using the magic of metaprogramming.

This is how our Retrospective presenter looks like: 

      class RetrospectivePresenter < SimpleDelegator  
        def can_delete_action_item?
          # Code that determines if a user can delete the action item
        end

        def can_delete_story?
          # Code that determines if a user can delete the story
        end
      end

What we did in our module is, determine the method that we need to call by using the resource's class name:

    resource = Story.new

    "can_delete_#{resource.class.name.underscore}?" # => can_delete_story?

    resource = ActionItem.new

    "can_delete_#{resource.class.name.underscore}?" # => can_delete_action_item?


Now, let's continue with our test in Rspec

The first thing to do is create our spec.

```spec/lib/modules/resource_destroyer_spec.rb```

Add the following code:

    require ‘spec_helper’

    describe Modules::ResourceDestroyer do
      subject{Modules::ResourceDestroyer}
    end

At this point, if you execute your test, it should pass.

Describe that we're testing our “destroy_with_pusher” method

     describe :destroy_with_pusher do
     end

Now, we need to create two contexts:

First context when the user can delete resource.

Second context when the use can’t delete resource.

      context 'when user can delete resources' do
      end

      context 'When user can not delete resources' do
      end

Let’s start with the first context and the method that we want to test.

      context 'when user can delete resources' do
      before do
        retrospective.should_receive(:send).with(:can_delete_resource?),and_return true
        retrospective.should_receive(:destroy)
        retrospective.should_receive(:pusher_destroy).with(socket_id)
      end
      it 'destroy a resource with the specified params' do
        subject.destroy_with_pusher(resource, retrospective, socket_id). should eq(resource)
       end
     end

At this point, if we execute the test, it will fail because we did not declare our helper methods(let), now let’s continue with the helper methods declaration:

      let(:resource) { mock(‘Resource’, class: stub(Class, name:  ‘Resource’)) }
      let(:retrospective) { mock(Retrospective) }
      let(:socket_id){ “1” }

Now let's continue with the other context, when the user can’t delete a resource for whatever reason, in general we need to confirm that this method should return false:

      context 'When user can not delete resources' do
        before do
          retrospective.should_receive(:send).with(:can_delete_resource?).and_return false
        end

        it 'does not invoke destroy on resource nor its pusher socket' do
          subject.destroy_with_pusher(resource, retrospective, socket_id). should be_false
        end
      end

##### Finally, here is the complete spec

    require 'spec_helper'

    describe Modules::ResourceDestroyer do
      subject { Modules::ResourceDestroyer }

      describe :destroy_with_pusher do
        let(:resource) { mock('Resource', class: stub(Class, name: 'Resource')) }
        let(:retrospective) { mock(Retrospective) }
        let(:socket_id) { "1" }

        context 'when user can delete resource' do
          before do
            retrospective.should_receive(:send).with(:can_delete_resource?).and_return true
            resource.should_receive(:destroy)
            resource.should_receive(:pusher_destroy).with(socket_id)
          end

          it 'destroys a resource with its pusher socket' do
            subject.destroy_with_pusher(resource, retrospective, socket_id).should eq(resource)
          end
        end

        context 'when user can not delete resources' do
          before do
            retrospective.should_receive(:send).with(:can_delete_resource?).and_return false
          end

          it 'does not invoke destroy on resource nor its pusher socket' do
            subject.destroy_with_pusher(resource, retrospective, socket_id).should be_false
          end
        end
      end
    end

#### How to use modules in Rails 3?

To use our modules, we need to load the module files, for example, we can use a Rails initializer, because they are loaded and executed at the moment the application is started and all the modules files that we specify will be loaded with our application files.

Another way to include our modules files is editing and configuring the config/application.rb file, we can uncomment the following line:

     config.autoload_paths += %W(#{config.root}/lib)

After we’ve done all the instructions above, we now can use the Module's methods our controller like this:

      Modules::ResourceDestroyer.destroy_with_pusher(@action_item, @retrospective, params[:socket_id]).

But if we need to share that method with other controllers we can always add the following method into application_controller.rb

     def resource_destroyer(resource, restrospective, socked_id)
       if Modules::ResourceDestroyer.destroy_with_pusher(resource, restrospective, socked_id)
         respond_with(resource, location: retrospectives_url)
       else
         render text: 'Forbidden', status: 403
       end
     end

Now we can use the method in any controller that inherits from application_controller.

    def destroy
      resource_destroyer(@story, @retrospective, params[:socket_id])
    end

### More Ruby Module examples.

We will continue creating new modules, the next module that we will create should satisfy the next specifications.

For developers, it is very important to debug our applications, in this case we want to use `Rails.logger`, but with some custom functionality that we want to share among all of our controllers.

The basic idea behind this solution is, to create a module called “Logger” with a method called “log”, that will receive two params, and the log method will print the result:

    #
    # spec/lib/modules/logger_spec.rb
    #

    require 'spec_helper'

    describe Modules::Logger do
      describe "#log" do
        let(:title) { "SomeClass"}
        let(:message) { "SomeContent"}

        specify do
          subject.log title, message
        end
      end
    end

The code to make it pass:

    #
    # lib/modules/logger.rb
    #

    module Modules
      module Logger
        extend self

        def log(title, message)
          Rails.logger.debug "\033[38;5;148mSTART #{self.class} - #{title}\033[39m"
          Rails.logger.debug message
          Rails.logger.debug "\033[38;5;148mEND #{self.class}\033[39m"
        end
      end
    end

Now, whenever we need to print some debugging string and we need to know the class and method that called it, we can just:

     class SomeClass
       include Modules::Logger

       def any_method(params)
          some_result = MyModel.find(:all)
          log __method__, some_result
          some_result
        end  
     end


Well, that's it for now, thanks for reading this article! Any questions or suggestions are very welcome and appreciated.  You can follow me @heridev or email me at heriberto.perez@magmalabs.io.  Regards.

Know more about us!

0 Shares:
You May Also Like