class Model def Model.build_and_route(name, route) klass = name.to_s.singularize.camelize.constantize rescue nil if klass klass.routes << route unless (klass.routes.include?(route) or route.blank? or !route.member?(klass)) #FIXME: see why nil is getting called in the first place klass else x = Model.make_model_class(name) x.routes << (route.to_a.clone << x) x end end def Model.db_type_map { # definitely have to add high-level datatypes. This will need to be partly replicated in generators "str" => "string", "string" => "string", "s" => "string", "int" => "integer", "integer" => "integer", "i" => "integer", "bool" => "boolean", "boolean" => "boolean", "b" => "boolean", "text" => "text", "t" => "text", "email" => "string", "url" => "string", "datetime" => "datetime" } end def Model.make_model_class(name) # routes is the set of urls by which you can get to an instance of a class Object.class_eval " class #{ name.to_s.singularize.camelize} < Model cattr_accessor :associations, :fields, :options, :routes, :foreign_keys, :validations self.associations = { } self.fields = { } self.options = { } self.routes = [] # of the form [:classname, [:otherclass, :classname], ...] self.foreign_keys = [] self.validations = {} class << self # TODO: can some or all of this be moved out of here and into the model class? # thanks to Josh Ladieu for this: it's the array of things needed to get to an instance of this class def resource_tuple # this returns the minimal route to this model, or nothing, if there is no unambiguous minimal route routelist = self.routes.sort {|x, y| x.length <=> y.length } if (routelist.length == 1) || (routelist.first.length == 1) #take the trivial route or the only route routelist.first else #TODO: maybe should deal with a case where there's a simplest route that all the others contain. nil end end def nested_resources APP.models.reject {|k,v| v.routes != [(self.resource_tuple + [v])] } end def cs self.to_s end def cp self.to_s.pluralize end def s self.to_s.underscore end def p self.to_s.underscore.pluralize end end end" name.to_s.singularize.camelize.constantize end class << self def nice_name self.to_s.underscore end def sym_name self.nice_name.pluralize.to_sym end def default_assoc_name(meth) :has_many == meth ? nice_name.pluralize : nice_name end def validates(validation_type, field, opts = { }) self.validations << opts.merge({ :val_type => validation_type, :field => field}) end def belongs_to(m, o = { }) self.validations["validates_presence_of:#{m.s}_id"] = { :type => "validates_presence_of", :target => m.s} associate(:belongs_to, m, o) end def has_many(m, o = { }) associate(:has_many, m, o) m.belongs_to(self, o) unless o[:skip_belongs_to] end def has_one(m, o = { }) associate(:has_one, m, o) m.belongs_to(self, o) unless o[:skip_belongs_to] end def hmt(o = { }) # this should generate the has_many :through and the requisite has_many m = o[:class] # this is the thing which this model has many of thru = o[:through] # this is the join model has_many(thru) has_many(m, { :through => thru, :skip_belongs_to => true }) unless o[:bidi] == false m.has_many(thru) m.has_many( self, { :through => thru, :skip_belongs_to => true }) end end def handle_hash(h) # this is here as part of has_many :through. It receives args like: # {:class => :foos, :through => :bars } m = h[:class].to_s.camelize.constantize thru = h[:through].to_s.camelize.constantize end def associate(meth, model, options = { }) assoc_name = options[:assoc_name] || model.default_assoc_name(meth) self.foreign_keys << ( assoc_name.to_s + "_id" ) if meth == :belongs_to #FIXME: might be something other than assoc_name_id self.associations[assoc_name] = { :model => model, :name => assoc_name, :type => meth}.merge(options) end def add_attrs(*args) if args.is_a? Array and args.length == 1 args.first.split.each do |s| name, field_type = s.split(":") add_field(name, field_type) end else # TODO: add handling for hashes or arrays raise "bad argument type in add_attrs" end end def add_field(name, field_type) self.fields[name] = Model.db_type_map[field_type] end end end