Can I store a piece of code outside a module or class?


#1

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


#2

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


#3

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


#4

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


#5

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


#6

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


#7

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


#8

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 required /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


#9

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.


#10

Okay, thanks Alex, I’ll go with the module and arguments, just to keep
things encapsulated.

Shauna


#11

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


#12

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


#13

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 :slight_smile:

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


#14

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