[warning! Ruby newbie!] OK, I''ve got lots of models, and I want to make sure that, say, all the Float columns are never negative and no fields are left nil. So I wrote a little sample chunk and dropped it in one of the models (sorry for any wrapping): content_columns.each do |column| validates_each column.name, :allow_nil => false do |model, attr, value| if (value.class == Float) && (value < 0) model.errors.add(attr," much be positive. (" + value.to_s + " is not positive)") end end end Great. Now, how do I ''apply'' this to all models in a DRY way? I thought perhaps stick this in a file and ''load'' it in each model, but that''s not working for me for some reason ("content_columns" undefined). I also want to do something similar with defaults. Right now I have a ''visible'' bool in most tables that I want to default to true instead of false (when it exists in the current model). Thanks for any guidance! Phil
Neville Burnell
2005-Oct-25 01:02 UTC
RE: Doing the same validation across multiple models
You make a new model base class from ActiveRecord::Base ... Class MyActiveRecordBase < ActiveRecord::Base ... End Then inherit all your models from MyActiveRecordBase. -----Original Message----- From: rails-bounces-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org [mailto:rails-bounces-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org] On Behalf Of Philip Edelbrock Sent: Tuesday, 25 October 2005 10:35 AM To: rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org Subject: [Rails] Doing the same validation across multiple models [warning! Ruby newbie!] OK, I''ve got lots of models, and I want to make sure that, say, all the Float columns are never negative and no fields are left nil. So I wrote a little sample chunk and dropped it in one of the models (sorry for any wrapping): content_columns.each do |column| validates_each column.name, :allow_nil => false do |model, attr, value| if (value.class == Float) && (value < 0) model.errors.add(attr," much be positive. (" + value.to_s + " is not positive)") end end end Great. Now, how do I ''apply'' this to all models in a DRY way? I thought perhaps stick this in a file and ''load'' it in each model, but that''s not working for me for some reason ("content_columns" undefined). I also want to do something similar with defaults. Right now I have a ''visible'' bool in most tables that I want to default to true instead of false (when it exists in the current model). Thanks for any guidance! Phil _______________________________________________ Rails mailing list Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org http://lists.rubyonrails.org/mailman/listinfo/rails
Neville Burnell wrote: > [Philip Edelbrock wrote] >> OK, I''ve got lots of models, and I want to make sure that, say, all the > > You make a new model base class from ActiveRecord::Base ... > Class MyActiveRecordBase < ActiveRecord::Base > ... > End > > Then inherit all your models from MyActiveRecordBase. Well, then he''s still repeating himself a fair amount for the different models - at least with those 2 lines of code. If your database schema was designed consistently, it''s easier to inspect your database to create your models. For example, in our system that uses the convention that every table named "table" has "table_id" as it''s keys; and that the foreign key relationships also use "table_id", I use the following fragment to create _all_ my models. It reads the table names from the SQL standard INFORMATION_SCHEMA (works on PostgreSQL and Microsoft SQL at least), and uses eval to create one ActiveRecord model for every table we have. The next loop looks at all the columns and finds those that match the convention and calls [Table].has_many to create the links. ################################################################### # Get all tables in the database # And create an ActiveRecord model for it. ################################################################### all_table_sql = ''select TABLE_NAME from INFORMATION_SCHEMA.TABLES'' tables = con.select_all(all_table_sql).map{|x| x[''TABLE_NAME'']} tables.each{|t| ### Foreach table in system eval %Q{ ### Dynamically generate classes. class #{t} < ActiveRecord::Base ### Create a class... set_table_name "#{t}" ### ... corresponding to the table name set_primary_key "#{t}_id" ### ... with the correct primary key end } } ############################################################################### ## Using the convention that columns named "_id" are used ## as foreign keys, create the "has_many" relationships. ## Yeah, I should have used foreign key relationships instead. ############################################################################### tables.each{|t| ### for every table name. cls = eval(t.to_s) ### get the table properties. cols = cls.columns ### get the columns. cols.each {|c| ### for each column cn = c.name ### Get the column name. next unless cn=~/_id\Z/ ### Consider all links. next if cn=~/\A#{t}_id\Z/ ### Ignore links to self. lt = /(.*)_Link/.match(cn)[1] ### Extract table name from link tl = t.downcase ### Lower case version link = "#{lt}.has_many :#{tl}s, :class_name=>''#{t}'', :foreign_key => ''#{cn}''" begin ### Allow for missing tables eval link ### Execute the relationship rescue ### catch exceptions # puts ''Error processing: ''+link ### report missing table end } } ################################################################### # Now I have ActiveRecord classes for *all* my database tables # with all the "has_many_ links in place. ###################################################################> Great. Now, how do I ''apply'' this to all models in a DRY way? I > thought perhaps stick this in a file and ''load'' it in each model, but > that''s not working for me for some reason ("content_columns" undefined).In the code I have above you could of course either have your own base class instead of ActiveRecord::Base as others describe, or put whatever you want in the eval.> I also want to do something similar with defaults. Right now I have a > ''visible'' bool in most tables that I want to default to true instead of > false (when it exists in the current model).I kinda wish the code above were the default behavior of rails (well, except that a foreign-key based convention might be better than naming convention; but since so much of activerecord relies on naming conventions I don''t think this is bad either). On the other hand it was easy enough for me to extend it that I guess I don''t really care that much.
Neville Burnell
2005-Oct-25 06:57 UTC
RE: Doing the same validation across multiple models
I can see the DRY part of your approach, but, correct me if I''m wrong, aren''t you creating every model for every incoming request, regardless of usage ? AFAIK, Rails will discard the controller after each request, so doesn''t this approach result in a lot of unused processing when most actions only use a small portion of the model? <snip> For example, in our system that uses the convention that every table named "table" has "table_id" as it''s keys; and that the foreign key relationships also use "table_id", I use the following fragment to create _all_ my models. It reads the table names from the SQL standard INFORMATION_SCHEMA (works on PostgreSQL and Microsoft SQL at least), and uses eval to create one ActiveRecord model for every table we have. The next loop looks at all the columns and finds those that match the convention and calls [Table].has_many to create the links. <snip
El Martes 25 Octubre 2005 02:34, Philip Edelbrock escribió:> [warning! Ruby newbie!] > > OK, I''ve got lots of models, and I want to make sure that, say, all the > Float columns are never negative and no fields are left nil. So I wrote > a little sample chunk and dropped it in one of the models (sorry for any > wrapping): > > content_columns.each do |column| > validates_each column.name, :allow_nil => false do |model, attr, > value| if (value.class == Float) && (value < 0) > model.errors.add(attr," much be positive. (" + value.to_s + " > is not positive)") > end > end > end > > > Great. Now, how do I ''apply'' this to all models in a DRY way? I > thought perhaps stick this in a file and ''load'' it in each model, but > that''s not working for me for some reason ("content_columns" undefined). > > I also want to do something similar with defaults. Right now I have a > ''visible'' bool in most tables that I want to default to true instead of > false (when it exists in the current model). > > Thanks for any guidance! >You can extend ActiveRecord::Base place a lib/validates_float.rb file. I had somthing similar for validating spanish tax IDs: module ActiveRecord module Validations module ClassMethods # Validates that the specified attributes are valid CIF. Happens by default on save. # # Configuration options: # * <tt>message</tt> - A custom error message (default is: "invalid CIF") # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update) # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. :if => :allow_validation, or :if => Proc.new { |provider| provider.signup_step > 2 }). The # method, proc or string should return or evaluate to a true or false value. def validates_cif(*attr_names) configuration = { :message => _("%s is not a valid CIF"), :on => :save } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) validates_each(attr_names, configuration) do |record, attr_name, value| record.errors.add(attr_name, configuration[:message]) unless value.is_a?(String) and value.valid_cif? end end end end end I extended also String with valid_cif? which does the actual validation Good luck, Koke> > Phil > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails-- Jorge Bernal <jbernal-OqA2i3GDl8k@public.gmane.org> Warp Networks S.L. http://www.warp.es/ [EN] http://koke.amedias.org/ [ES] http://www.amedias.org/koke
Philip Edelbrock
2005-Oct-25 20:02 UTC
Re: Doing the same validation across multiple models
Thanks, all! I went with this solution: Created a file lib/validations.rb (sorry for any line wraps):> module Validations > def self.append_features(base_class) > base_class.content_columns.each do |column| > base_class.validates_each column.name, :allow_nil => false do |model, attr, value| > # Make sure all Floats/Numerics are zero or positive > if (value.class == Float) && (value < 0) > model.errors.add(attr," much be positive. (" + value.to_s + " is not positive)") > end > # Make sure all "name" columns are not empty > if (attr == "name") && (value.length == 0) > model.errors.add(attr," must not be empty.") > end > end > end > end > endAnd then, simply did "include Validations" in my models. Wa-la! Phil