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