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.

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?

James B. wrote:

separated by a word boundary in a string.

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
Ruby, iOS, and Other Development: Mixing in Class Methods

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 K. wrote:

I followed this blog post for mixing in Class level methods
Ruby, iOS, and Other Development: Mixing in Class Methods

The blog you were looking for was not found.

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

2008/3/13, James B. [email protected]:

the top of the model files but I nonetheless threw undefined method
errors on both methods whatever I tried.

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

tell Rails how to find your modules :

config.load_paths += %W( #{RAILS_ROOT}/app/models/concerns )

2: one such shared module :

in app/models/concerns/super_powers.rb
module SuperPowers

end

3: and finally, in your model

class People << AR
include SuperPowers

end

Alain

Andy K. 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.

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

$ cat app/models/entity.rb
require ‘prep_index_string’

class Entity < ActiveRecord::Base

include PrepIndexString

has_one :client

before_save :set_index_values

validates_presence_of :entity_name
validates_presence_of :entity_legal_name
validates_presence_of :entity_legal_form

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

end

NoMethodError in ClientsController#create

undefined method `keycase’ for “A test name”:String

I do not know what I am doing wrong.

James B. 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:inset_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:incallback’
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:incallback’
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:insave_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:insave’
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:intransaction’
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:insave’
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:insave’
from (irb):6

Can anytone tell me why?

James B. wrote:

I do not know what I am doing wrong.

Even if I put the keycase method into the entity model I still get the
same error!?

#require ‘prep_index_string’

class Entity < ActiveRecord::Base

include PrepIndexString

#has_one :person
has_one :client
#has_one :vendor

#has_many :channels
#has_many :contacts
#has_many :identities
#has_many :job_parties
#has_many :locations

before_save :set_index_values

validates_presence_of :entity_name
validates_presence_of :entity_legal_name
validates_presence_of :entity_legal_form

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

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

What is Rails doing?

David A. Black wrote:

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.

Thank you. This explains why placing this in a module did not work
either.

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

James B. 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 –

On Thu, 13 Mar 2008, James B. wrote:

descends from String and adds these two methods. The question is where?

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


Upcoming Rails training from David A. Black and Ruby Power and Light:
ADVANCING WITH RAILS, April 14-17 2008, New York City
CORE RAILS, June 24-27 2008, London (Skills Matter)
See http://www.rubypal.com for details. Berlin dates coming soon!

James B. wrote:

James B. 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?

Hi –

On Thu, 13 Mar 2008, James B. wrote:

self.entity_name = self.entity_name.to_s.strip.squeeze(" ").downcase
#self[:entity_name] = self.entity_name.keycase

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


Upcoming Rails training from David A. Black and Ruby Power and Light:
ADVANCING WITH RAILS, April 14-17 2008, New York City
CORE RAILS, June 24-27 2008, London (Skills Matter)
See http://www.rubypal.com for details. Berlin dates coming soon!

David A. Black wrote:

or you’d put it in a file in the config/initializers directory,
in which case it would be read and loaded automatically.

Works perfectly.

Thank you again.

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.