Forum: Ruby on Rails Can I store a piece of code outside a module or class?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Shauna (Guest)
on 2007-01-17 22:44
I asked this question earlier but the lack of replies makes me think I
gave it a screwy subject line or something. I'll try to explain better.

I've got a very long piece of code within the create method in my
payments_controller.rb. This code is only called from that method in
that controller. But I would like to remove it from the controller
itself to a separate file somewhere, so the controller remains lean and
easy to read.

I've tried storing it as a separate module in \lib. I've also tried
storing it in the \helpers\payments_helper.rb module. The problem with
having it as a separate module is it doesn't share session, flash,
params, or any of the private methods already defined in
payments_controller.rb. There must be a simpler way than trying to pass
these objects back and forth.

I've started looking into "include" but so far I'm running into similar
problems.

Isn't there a simpler way to import some lines of code directly into a
method?

Thanks,
Shauna
Adam (Guest)
on 2007-01-17 23:21
Shauna wrote:
> I asked this question earlier but the lack of replies makes me think I
> gave it a screwy subject line or something. I'll try to explain better.
>
> I've got a very long piece of code within the create method in my
> payments_controller.rb. This code is only called from that method in
> that controller. But I would like to remove it from the controller
> itself to a separate file somewhere, so the controller remains lean and
> easy to read.
>
> I've tried storing it as a separate module in \lib. I've also tried
> storing it in the \helpers\payments_helper.rb module.

By default, the helper should help you on your View, not your
Controller.

> The problem with
> having it as a separate module is it doesn't share session, flash,
> params, or any of the private methods already defined in
> payments_controller.rb. There must be a simpler way than trying to pass
> these objects back and forth.
>
> I've started looking into "include" but so far I'm running into similar
> problems.

The standard way of doing what you want is to put the funcionality (i.e.
the code) into a module defined in /lib. You then use `include`, and
voila, problem solved!  You have the method you defined in said module
available on any controller that includes that module.

> Isn't there a simpler way to import some lines of code directly into a
> method?
>
> Thanks,
> Shauna
Stephen B. (Guest)
on 2007-01-17 23:30
(Received via mailing list)
I would personally consider whether or not this long method could be
broken down into separate methods in a standalone class.  It's normally
the case that if you have a large amount of code, it can be broken
down.  It will also be much more readable in a class with separate
methods instead of one big method.

Steve
Shauna (Guest)
on 2007-01-17 23:50
Stephen B. wrote:
> I would personally consider whether or not this long method could be
> broken down into separate methods in a standalone class.  It's normally
> the case that if you have a large amount of code, it can be broken
> down.  It will also be much more readable in a class with separate
> methods instead of one big method.
>
> Steve

Thanks, Steve. But it's all number crunching code that has to do with
posting a single payment transaction. I will consider breaking up the
process based on various factors, but I'm still left with the problem of
where to store the pieces. I don't want to clutter up my CRUD-handling
controller with the details of payment transaction processing.

Shauna
Shauna (Guest)
on 2007-01-18 00:01
Adam wrote:

> The standard way of doing what you want is to put the funcionality (i.e.
> the code) into a module defined in /lib. You then use `include`, and
> voila, problem solved!  You have the method you defined in said module
> available on any controller that includes that module.

Thanks for your quick reply, Adam. I'm tripping over the exact usage
here.

If the file is called /lib/post_payment_module.rb and its contents start
with the line "module PostPayment", what should the include command in
the payments_controller.rb file say and where does it go?

If I use...
   def create
      include post_payment_module
   end
I get the NameError "undefined local variable or method
`post_payment_module' for #<PaymentsController:0x644d758>".

If I use...
   def create
      include PostPayment
   end
I get the NameError "uninitialized constant PostPayment".

If I have to put the "def create" in the module itself, I'm back to the
problem of having to pass it the session, flash, and params variables
and figure out how to pass them back to payments_controller when they
are changed within the module.

Shauna
Adam (Guest)
on 2007-01-18 02:58
Shauna wrote:
> Adam wrote:
>
>> The standard way of doing what you want is to put the funcionality (i.e.
>> the code) into a module defined in /lib. You then use `include`, and
>> voila, problem solved!  You have the method you defined in said module
>> available on any controller that includes that module.
>
> Thanks for your quick reply, Adam.I'm tripping over the exact usage
> here.
>
> If the file is called /lib/post_payment_module.rb and its contents start
> with the line "module PostPayment", what should the include command in
> the payments_controller.rb file say and where does it go?

First of all, in Ruby there's no necessary relation between the filename
containing a piece of code, and its contents. I.e. an .rb file may
contain one module, or several, or none at all. So it doesn't make any
sense to postfix the filename with _module. What if you want to add
another module there, or even a class?

Use a functional name, like maybe `post_payment.rb` in your case.

> If I use...
>    def create
>       include post_payment_module
>    end
> I get the NameError "undefined local variable or method
> `post_payment_module' for #<PaymentsController:0x644d758>".

Sure, because post_payment_module is not escaped there in any way, so it
is assumed to be a name (i.e. var or method), which is sought after by
the interpreter but doesn't exist, hence the error.

> If I use...
>    def create
>       include PostPayment
>    end
> I get the NameError "uninitialized constant PostPayment".

Yes, because you haven't `require`d /lib/post_payment_module.rb. The
file wasn't evaluated, so PostPayment isn't among the constants at your
disposal.

You should `require_dependency 'post_payment_module'` in
conf/environment.rb or in application_controller.rb or the controller
you need the functionality in. Then you'll have PostPayment at your
disposal, and can do `include PostPayment`.

> If I have to put the "def create" in the module itself, I'm back to the
> problem of having to pass it the session, flash, and params variables
> and figure out how to pass them back to payments_controller when they
> are changed within the module.
>
> Shauna

I recommend a good, thorough Ruby tutorial (e.g. the PickAxe2), followed
by a good Rails tutorial.

-Adam
Shauna (Guest)
on 2007-01-18 04:26
Thanks, Adam. The syntax suggestions worked and with a little extra
research I pieced together the complete picture. I really didn't need a
Module at all - just a method that can be called from within my create
method as below.

payments_controller.rb contains:
  require_dependency 'post_payment'
  .
  .
  def create
    validate_and_post(session, flash, params, @loan)
  end

/lib/post_payment.rb contains:
  def validate_and_post(session, flash, params, loan)
  .
  .

From http://wiki.rubyonrails.org/rails/pages/RequireDependency :
"require_dependency is used to reload source files on each request when
in development mode."

So for other newbies, hope this clarifies things for you as well.

Shauna
Shauna (Guest)
on 2007-01-18 06:43
And I don't even need to include the arguments, so this is even simpler:

payments_controller.rb contains:
  require_dependency 'post_payment'
  .
  .
  def create
    validate_and_post
  end

/lib/post_payment.rb contains:
  def validate_and_post
  .
  .

Am I doing something wrong by making this a top-level method rather than
enclosing it in a module and using 'include'?

Thanks,
Shauna
Alex W. (Guest)
on 2007-01-18 07:47
Shauna wrote:
> And I don't even need to include the arguments, so this is even simpler:
>
> payments_controller.rb contains:
>   require_dependency 'post_payment'
>   .
>   .
>   def create
>     validate_and_post
>   end
>
> /lib/post_payment.rb contains:
>   def validate_and_post
>   .
>   .
>
> Am I doing something wrong by making this a top-level method rather than
> enclosing it in a module and using 'include'?
>
> Thanks,
> Shauna

You are much better off using a module.  Just in case you have some
plugin that uses that as a variable name, or other namespace overlap
issues.  It's just good practice to keep things out of the global scope.

Try this:

  #lib/validate_and_post.rb
  module ValidateAndPost
    def validate_and_post(session, flash, params, @loan)
      ...
    end
  end

  # controller
  class FooController < ActionController::Base
    include ValidateAndPost

    def create
      validate_and_post(session, flash, params, @loan)
    end
  end

module are for mixing into other classes like this.  The "include"
method must be called at the class level, not from inside an instance
method.  Once you include the module, it's methods become available.
Alex W. (Guest)
on 2007-01-18 07:53
Alex W. wrote:
>   #lib/validate_and_post.rb
>   module ValidateAndPost
>     def validate_and_post(session, flash, params, @loan)
>       ...
>     end
>   end

Oops, that should be:

  def validate_and_post(session, flash, params, loan)
    ...
  end

since modules can't have instance variables, and instance variables
shouldnt be declared in method declarations
Shauna (Guest)
on 2007-01-18 08:19
Okay, thanks Alex, I'll go with the module and arguments, just to keep
things encapsulated.

Shauna
Shauna (Guest)
on 2007-01-18 08:33
One more note - once I move the include up to the class level, I no
longer need to use method arguments (even though the method is inside a
named module). So my (final?) approach is:

payments_controller.rb contains:
  require_dependency 'post_payment'
  include PostPayment
  .
  .
  def create
    validate_and_post
  end

/lib/post_payment.rb contains:
  module PostPayment
    def validate_and_post
    .
    .

Shauna
Alex W. (Guest)
on 2007-01-18 09:12
Shauna wrote:
> One more note - once I move the include up to the class level, I no
> longer need to use method arguments (even though the method is inside a
> named module). So my (final?) approach is:
>
> payments_controller.rb contains:
>   require_dependency 'post_payment'
>   include PostPayment
>   .
>   .
>   def create
>     validate_and_post
>   end
>
> /lib/post_payment.rb contains:
>   module PostPayment
>     def validate_and_post
>     .
>     .
>
> Shauna

Right, silly me :)

When you include the module, all the other methods would act as local
methods.

One last thing though, make sure the method is private.  Otherwise,
someone can go to:

  http://test.com/payments/validate_and_post

And it will be processed as an action.  If it's a private method it will
not be invokable as an action

  module PostPayment
    private
      def validate_and_post
        ..
      end
  end
Shauna (Guest)
on 2007-01-19 00:53
Alex W. wrote:

> One last thing though, make sure the method is private.  Otherwise,
> someone can go to:
>
>   http://test.com/payments/validate_and_post
>
> And it will be processed as an action.  If it's a private method it will
> not be invokable as an action
>
>   module PostPayment
>     private
>       def validate_and_post
>         ..
>       end
>   end

Oooh, thank you. I like that one.

-Shauna
This topic is locked and can not be replied to.