Where to put and how to use modules for models

I have written two helper methods that I wish to have these available in
all of my models. I have placed them in a module called
prep_index_string.rb which looks like this:

module PrepIndexString

  # This method prepares descriptive strings that are used as
  # indices for lookups. The keycase strips method leading and
  # trailing spaces, squeezes out extra whitespace between words,
  # and then downshifts all.

James Byrne wrote:

I have written two helper methods that I wish to have these available in
all of my models. I have placed them in a module called
prep_index_string.rb which looks like this:

module PrepIndexString

  # This method prepares descriptive strings that are used as
  # indices for lookups. The keycase strips method leading and
  # trailing spaces, squeezes out extra whitespace between words,
  # and then downshifts all.
  #
  # Do not use ! methods as they return nil under certain circumstances
  #
  # If the attribute passed does not respond to the to_s message then
throw
  # an error. There is nothing useful we can do here.
  def keycase(value)
    value = value.to_s.strip.squeeze(" ").downcase
  end

  # This method simply upshifts the first character of every element
  # separated by a word boundary in a string.
  def titlecase(value)
    value = value_to_s.gsub(/\b\w/){$&.upcase}
  end

end

How and where is the best way to mixin these two methods for every model
(and possibly every view and controller as well) in a particular rails
project? I initially added these to application_helper.rb but have
since discovered that this is completely ignored by models. I tried
requiring "prep_index_string" and including/extending PrepIndexString at
the top of the model files but I nonetheless threw undefined method
errors on both methods whatever I tried.

It almost seems to me as if I should subclass String and mix them in
there but how should I do this for just one Rails project?

I followed this blog post for mixing in Class level methods "http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html"

but in your case, couldn't you just drop your module in "lib" and "include YourModule" in your model classes?

that's what I would try

Andy Koch wrote:

I followed this blog post for mixing in Class level methods
"http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html"

The blog you were looking for was not found.

Thanks. I will try moving the file as you suggest.

Here's the relevant chapter in the old Pickaxe:
http://ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html

Put prep_index_string.rb in the lib/ directory. Then include the module
in all the classes you want to use the methods:

    require "prep_index_string"

    class SomeModelOrController
      include PrepIndexString
      # blah blah
    end

This makes the methods available at the _instance_ level.
If you want to have them at the class level too, add the line
"extend PrepIndexString" near the include line.

If you don't wan't to manually include it in every class you can
go the hardcore route and extend ActionController::Base
and ActiveRecord::Base.

Put this in lib/prep_index_string_extension.rb:

    require "prep_index_string"

    class ActiveRecord::Base
      include PrepIndexString
      extend PrepIndexString
    end

    class ActionController::Base
      include PrepIndexString
      extend PrepIndexString
    end

and require "prep_index_string_extension" in environment.rb.
And make all the methods in PrepIndexString private.
You might also consider putting it in a plugin.

Stefan

another solution :
   http://weblog.jamisbuck.org/2007/1/17/concerns-in-activerecord

In short : place your model modules in a dedicated directory
(../concerns) that you add to the load path.

1: file config/environement.rb:

Andy Koch wrote:

but in your case, couldn't you just drop your module in "lib" and
"include YourModule" in your model classes?

that's what I would try

]$ ll lib/*rb
-rw-rw-r-- 1 byrnejb byrnejb 728 Mar 13 14:43 lib/prep_index_string.rb

$ cat lib//prep_index_string.rb
module PrepIndexString

  # This method prepares descriptive strings that are used as
  # indices for lookups. The keycase strips method leading and
  # trailing spaces, squeezes out extra whitespace between words,
  # and downshifts all.

James Byrne wrote:

In console this works:

class Entity < ActiveRecord::Base

  before_save :keycase

  def keycase
    self.entity_name = self.entity_name.to_s.strip.squeeze(" ").downcase
  end
end

and this works:

class Entity < ActiveRecord::Base

  before_save :set_index_values

  def set_index_values
    #self[:entity_name] = self.entity_name.keycase
    keycase
  end

  def keycase
    self.entity_name = self.entity_name.to_s.strip.squeeze(" ").downcase
  end

But his does not:

class Entity < ActiveRecord::Base

  before_save :set_index_values

  def set_index_values
    self.entity_name = self.entity_name.keycase
  end

  def keycase( value )
    value.to_s.strip.squeeze(" ").downcase
  end
end

$ ruby script/console
Loading development environment (Rails 2.0.2)

load "entity.rb"

=> ["Entity"]

en = Entity.new

=> #<Entity id: nil, entity_name: nil, entity_legal_name: nil,
entity_legal_form: nil, created_at: nil, updated_at: nil>

en.entity_name = "UPERAND"

=> "UPERAND"

en.entity_legal_name = "UperAnd Ltd."

=> "UperAnd Ltd."

en.entity_legal_form = "PERS"

=> "PERS"

en.save

NoMethodError: undefined method `keycase' for "UPERAND":String
        from
/home/byrnejb/Software/Development/Projects/invert/app/models/entity.rb:27:in
`set_index_values'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/callbacks.rb:307:in
`send'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/callbacks.rb:307:in
`callback'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/callbacks.rb:304:in
`each'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/callbacks.rb:304:in
`callback'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/callbacks.rb:212:in
`create_or_update'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:1972:in
`save_without_validation'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/validations.rb:934:in
`save_without_transactions'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/transactions.rb:108:in
`save'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/abstract/database_statements.rb:66:in
`transaction'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/transactions.rb:80:in
`transaction'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/transactions.rb:100:in
`transaction'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/transactions.rb:108:in
`save'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/transactions.rb:120:in
`rollback_active_record_state!'
        from
/usr/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/transactions.rb:108:in
`save'
        from (irb):6

Can anytone tell me why?

Hi --

James Byrne wrote:

In console this works:

class Entity < ActiveRecord::Base

before_save :keycase

def keycase
   self.entity_name = self.entity_name.to_s.strip.squeeze(" ").downcase
end
end

and this works:

class Entity < ActiveRecord::Base

before_save :set_index_values

def set_index_values
   #self[:entity_name] = self.entity_name.keycase
   keycase
end

def keycase
   self.entity_name = self.entity_name.to_s.strip.squeeze(" ").downcase
end

But his does not:

class Entity < ActiveRecord::Base

before_save :set_index_values

def set_index_values
   self.entity_name = self.entity_name.keycase
end

def keycase( value )
   value.to_s.strip.squeeze(" ").downcase
end
end

[...]

Can anytone tell me why?

self.entity_name is a string. You're calling keycase on that string,
but strings don't have a keycase method.

You'd have to do:

   self.entity_name = keycase(self.entity_name)

if you're writing keycase to take an argument.

David

James Byrne wrote:

Now my question becomes: where would I subclass String to add these two
methods for a specific rails project?

I need to clarify this. What I want to accomplish is to add these two
methods to the String class, but only for a particular project. What is
the recommended manner to accomplish this? I speculate that somewhere
in the load path I will need to create a class called String that
descends from String and adds these two methods. The question is where?

Class String

  def keycase(value)
    value = value.to_s.strip.squeeze(" ").downcase
  end

  def titlecase(value)
    value = value_to_s.downcase.gsub(/\b\w/){$&.upcase}
  end

end

Hi --

James Byrne wrote:

Now my question becomes: where would I subclass String to add these two
methods for a specific rails project?

I need to clarify this. What I want to accomplish is to add these two
methods to the String class, but only for a particular project. What is
the recommended manner to accomplish this? I speculate that somewhere
in the load path I will need to create a class called String that
descends from String and adds these two methods. The question is where?

Class String

def keycase(value)
   value = value.to_s.strip.squeeze(" ").downcase
end

def titlecase(value)
   value = value_to_s.downcase.gsub(/\b\w/){$&.upcase}
end

end

If you want to add methods to the String class, you don't need to
create a class; you just need to re-open the existing String class
(which is what you've done above, except you've capitalized "class"
:slight_smile: Furthermore, if you're defining the methods on the class, you
don't want them to take an argument:

   class String
     def keycase
       strip.squeeze(" ").downcase
     end
   end

Typically you would put a modification like this in the lib directory,
and require the file at runtime, or you'd put it in a file in the
config/initializers directory, in which case it would be read and
loaded automatically.

Note that Rails already adds titlecase to String.

David

James Byrne wrote:

James Byrne wrote:

Now my question becomes: where would I subclass String to add these two
methods for a specific rails project?

or should I do something like this instead?

String.class_eval do

  def keycase
    self.to_s.strip.squeeze(" ").downcase
  end

  def titlecase
    self.to_s.downcase.gsub(/\b\w/){$&.upcase}
  end

end

But again, where do I put this?

David A. Black wrote:

Note that Rails already adds titlecase to String.

Which demonstrates yet again that if one wants to find out what is
already available, try to do it yourself first. Sigh.

Thanks.