I''m excited to announce that the "Philly on Rails" user group (already 50+ members and growing) will be holding its first meeting on Wednesday, November 2 at 6pm in Fort Washington, PA. We''ve planned two great presentations with plenty of time between them for introductions, demos, etc. We''ll also have yummy food and Ruby giveaways (books, t-shirts, mugs). To get more information and join the mailing list, visit: http://www.phillyonrails.org/ See you there! Cheers, Erin
Hi, I''m sure that in keeping with the DRY principal there must be a means of making model class methods available to every class in the application, but I''m just not seeing it. If /app/controllers/application.rb can be used to define filters and methods which are available to all controllers, how would you go about defining class methods which are available to all classes? cheers, Mark
On 27 Oct 2005, at 19:04, Mark Beattie wrote:> Hi, > > I''m sure that in keeping with the DRY principal there must be a > means of > making model class methods available to every class in the > application, but > I''m just not seeing it. > > If /app/controllers/application.rb can be used to define filters > and methods > which are available to all controllers, how would you go about > defining class > methods which are available to all classes?Sure, define it the Object class, the ruby-enforced root of all classes: class Object def self.thing puts ''thing'' end end -- Phillip Hutchings phillip.hutchings-QrR4M9swfipWk0Htik3J/w@public.gmane.org
An example of this (for me) is: I have a few different user classes
(admin, customer) and they all have separate tables. However, they all
log in the same way and have a similar base structure. So, I made them
all like this:
eg admin_user.rb
class AdminUser < ActiveRecord::Base
include UserModule
...
# The plain-text password, which is not stored in the database
attr_accessor :password
# Never allow the hashed password to be set from a form
attr_protected :sha1
...
end
and then, in the user_module.rb file, there is this:
require ''digest/sha1''
module UserModule
# Add the contents of this module into the calling object so that
# they are tightly integrated. It''s some crazy Ruby voodoo but it
works.
# Seen throughout ActiveRecord to add modules into Base without
# triggering Single Table Inheritance
def self.append_features(base)
super
base.extend(ClassMethods)
base.class_eval do
class << self
alias_method :login, :login
alias_method :hash_password, :hash_password
end
alias_method :username, :username
alias_method :before_save, :before_save
alias_method :after_save, :after_save
alias_method :try_to_login, :try_to_login
end
end
# Only allow lowercase usernames. Also, remove whitespace without being fussy
def username=(value)
write_attribute("username", self.class.caseinsensitive(value) )
end
# Only update the saved pass if it has been populated
def before_save
self.sha1 = self.class.hash_password(self.password) if
self.password and self.password.length > 0
end
def after_save
@password = nil
end
# Log in if the username and password (after hashing)
# match the database, or if the username matches
# an entry in the database with no password
def try_to_login
self.class.login(self.username, self.password)
end
module ClassMethods
# Return the User with the given username and plain-text password
def login(username, password)
username = self.caseinsensitive(username || "")
sha1 = self.hash_password(self.caseinsensitive(password || ""))
find(:first,
:conditions => ["username = ? and sha1 = ? and active =
1",
username, sha1])
end
def hash_password(password)
Digest::SHA1.hexdigest(PASSWORD_SALT+caseinsensitive(password))
end
# Transform input to lowercase. Also, remove whitespace
# without being fussy
def caseinsensitive(val)
val.downcase.gsub(/\s+/, '''')
end
end
end
I was going to make this a module at some point... but i don''t know how
yet ;)
HTH,
Dan
I have two points on this code re: security
1. Having a fixed PASSWORD_SALT is bad for security. A fixed salt value is
almost as bad as having no salt. It''s much better to have a salt value
per
user
2. Having case sensitivity in a password is a good thing. Converting to
lowercase is much less secure.
To make per-user salt, I do something like this:
a. When setting a password, generate a new salt value
b. Save this salt value in the user model to be saved in the db (column name
of ''salt'')
c. The login code is a little more complex, but is a lot more secure:
# Return the User with the given username and plain-text password
def login(username, password)
username = self.caseinsensitive(username || "")
user_to_test = find(:first,
:conditions => ["username = ? and active = 1",
username])
sha1_to_test = self.hash_password(salt, password || "")
if user_to_test.sha1 == sha1_to_test
user_to_test
else
nil
end
end
def hash_password(user_salt, password)
Digest::SHA1.hexdigest(user_salt+ password)
End
You will also need to make the username unique for this to work.
-----Original Message-----
From: Dan Sketcher [mailto:dansketcher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org]
Sent: Wednesday, October 26, 2005 11:22 PM
To: rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org
Subject: Re: [Rails] Base model class methods
An example of this (for me) is: I have a few different user classes
(admin, customer) and they all have separate tables. However, they all
log in the same way and have a similar base structure. So, I made them
all like this:
eg admin_user.rb
class AdminUser < ActiveRecord::Base
include UserModule
...
# The plain-text password, which is not stored in the database
attr_accessor :password
# Never allow the hashed password to be set from a form
attr_protected :sha1
...
end
and then, in the user_module.rb file, there is this:
require ''digest/sha1''
module UserModule
# Add the contents of this module into the calling object so that
# they are tightly integrated. It''s some crazy Ruby voodoo but it
works.
# Seen throughout ActiveRecord to add modules into Base without
# triggering Single Table Inheritance
def self.append_features(base)
super
base.extend(ClassMethods)
base.class_eval do
class << self
alias_method :login, :login
alias_method :hash_password, :hash_password
end
alias_method :username, :username
alias_method :before_save, :before_save
alias_method :after_save, :after_save
alias_method :try_to_login, :try_to_login
end
end
# Only allow lowercase usernames. Also, remove whitespace without being
fussy
def username=(value)
write_attribute("username", self.class.caseinsensitive(value) )
end
# Only update the saved pass if it has been populated
def before_save
self.sha1 = self.class.hash_password(self.password) if
self.password and self.password.length > 0
end
def after_save
@password = nil
end
# Log in if the username and password (after hashing)
# match the database, or if the username matches
# an entry in the database with no password
def try_to_login
self.class.login(self.username, self.password)
end
module ClassMethods
# Return the User with the given username and plain-text password
def login(username, password)
username = self.caseinsensitive(username || "")
sha1 = self.hash_password(self.caseinsensitive(password || ""))
find(:first,
:conditions => ["username = ? and sha1 = ? and active =
1",
username, sha1])
end
def hash_password(password)
Digest::SHA1.hexdigest(PASSWORD_SALT+caseinsensitive(password))
end
# Transform input to lowercase. Also, remove whitespace
# without being fussy
def caseinsensitive(val)
val.downcase.gsub(/\s+/, '''')
end
end
end
I was going to make this a module at some point... but i don''t know how
yet
;)
HTH,
Dan
_______________________________________________
Rails mailing list
Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org
http://lists.rubyonrails.org/mailman/listinfo/rails
1. Yep, ok, fine point. 2. It''s a requirement. Not my choice. 3. My main priority in posting was answering the question :) Cheers, Dan
And fine answers they are indeed, much appreciated. The module approach and Ruby Object class both seem like fine solutions. I ended up having some success with adding "model :application" to controllers/application.rb, and then defining global methods in models/application.rb. I might not have had to bother if there was a built-in method for modifying arbitrary fields in h_a_b_t_m bridge tables. cheers, Mark