hobo_files/plugin/lib/hobo/model.rb in hobo-0.5.3 vs hobo_files/plugin/lib/hobo/model.rb in hobo-0.6
- old
+ new
@@ -1,60 +1,108 @@
module Hobo
module Model
+ Hobo.field_types.update({ :html => HtmlString,
+ :markdown => MarkdownString,
+ :textile => TextileString,
+ :password => PasswordString,
+ :text => Hobo::Text,
+ :boolean => TrueClass,
+ :date => Date,
+ :datetime => Time,
+ :integer => Fixnum,
+ :big_integer => BigDecimal,
+ :float => Float,
+ :string => String,
+ :email_address => EmailAddress
+ })
+
def self.included(base)
Hobo.register_model(base)
base.extend(ClassMethods)
- base.set_field_type({})
+ base.class_eval do
+ @field_specs = HashWithIndifferentAccess.new
+ set_field_type({})
+ end
class << base
alias_method_chain :has_many, :defined_scopes
+ alias_method_chain :belongs_to, :foreign_key_declaration
end
end
module ClassMethods
# include methods also shared by CompositeModel
include ModelSupport::ClassMethods
+ private
+
+ def return_type(type)
+ @next_method_type = type
+ end
+
def method_added(name)
- # avoid error when running model generators before
- # db exists
- return unless connected?
+ if @next_method_type
+ set_field_type(name => @next_method_type)
+ @next_method_type = nil
+ end
+ end
+
+
+ class FieldDeclarationsDsl
- aliased_name = "#{name}_without_hobo_type"
- return if name.to_s.ends_with?('without_hobo_type') or aliased_name.in?(instance_methods)
+ def initialize(model)
+ @model = model
+ end
- type_wrapper = self.field_type(name)
- if type_wrapper && type_wrapper.is_a?(Class) && type_wrapper < String
- aliased_name = "#{name}_without_hobo_type"
- alias_method aliased_name, name
- define_method name do
- res = send(aliased_name)
- if res.nil?
- nil
- elsif res.respond_to?(:hobo_undefined?) && res.hobo_undefined?
- res
- else
- type_wrapper.new(res)
- end
- end
+ attr_reader :model
+
+ def timestamps
+ field(:created_at, :datetime)
+ field(:updated_at, :datetime)
end
+
+ def field(name, *args)
+ type = args.shift
+ options = extract_options_from_args!(args)
+ @model.send(:set_field_type, name => type) unless
+ type.in?(@model.connection.native_database_types.keys - [:text])
+ @model.field_specs[name] = FieldSpec.new(@model, name, type, options)
+ end
+
+ def method_missing(name, *args)
+ field(name, *args)
+ end
+
end
+
+ def fields(&b)
+ FieldDeclarationsDsl.new(self).instance_eval(&b)
+ end
+
+
+ def belongs_to_with_foreign_key_declaration(name, *args, &block)
+ res = belongs_to_without_foreign_key_declaration(name, *args, &block)
+ refl = reflections[name]
+ fkey = refl.primary_key_name
+ field_specs[fkey] ||= FieldSpec.new(self, fkey, :integer)
+ if refl.options[:polymorphic]
+ type_col = "#{name}_type"
+ field_specs[type_col] ||= FieldSpec.new(self, type_col, :string)
+ end
+ res
+ end
+
+
+ attr_reader :field_specs
+ public :field_specs
+
def set_field_type(types)
types.each_pair do |field, type|
-
- # TODO: Make this extensible
- type_class = case type
- when :html; HtmlString
- when :markdown; MarkdownString
- when :textile; TextileString
- when :password; PasswordString
- else type
- end
-
+ type_class = Hobo.field_types[type] || type
field_types[field] = type_class
end
end
@@ -73,14 +121,14 @@
def never_show(*fields)
@hobo_never_show ||= []
@hobo_never_show.concat(fields.omap{to_sym})
end
-
def never_show?(field)
@hobo_never_show and field.to_sym.in?(@hobo_never_show)
end
+ public :never_show?
def set_creator_attr(attr)
class_eval %{
def creator
#{attr};
@@ -141,35 +189,44 @@
end
validates_format_of id_name_field, :with => /^[^_]+$/, :message => "cannot contain underscores" if
underscore
end
-
+ public
+
def id_name?
respond_to?(:find_by_id_name)
end
-
attr_reader :id_name_column
def field_type(name)
name = name.to_sym
field_types[name] or
- reflections[name] or
- ((col = columns.find {|c| c.name == name.to_s}) and case col.type
- when :boolean
- TrueClass
- when :text
- Hobo::Text
- else
- col.klass
- end)
+ reflections[name] or begin
+ col = columns.find {|c| c.name == name.to_s} rescue nil
+ return nil if col.nil?
+ case col.type
+ when :boolean
+ TrueClass
+ when :text
+ Hobo::Text
+ else
+ col.klass
+ end
+ end
end
+
+ def nilable_field?(name)
+ col = columns.find {|c| c.name == name.to_s} rescue nil
+ col.nil? || col.null
+ end
+
def conditions(&b)
ModelQueries.new(self).instance_eval(&b).to_sql
end
@@ -242,24 +299,39 @@
end
class ScopedProxy
def initialize(klass, scope={})
- @klass, @scope = klass, scope
+ @klass = klass
+
+ # If there's no :find, or :create specified, assume it's a find scope
+ @scope = if scope.has_key?(:find) || scope.has_key?(:create)
+ scope
+ else
+ { :find => scope }
+ end
end
def method_missing(name, *args, &block)
- klass.with_scope(@scope) do
+ @klass.send(:with_scope, @scope) do
@klass.send(name, *args, &block)
end
end
+
+ def all
+ self.find(:all)
+ end
+
+ def first
+ self.find(:first)
+ end
end
(Object.instance_methods +
Object.private_instance_methods +
Object.protected_instance_methods).each do |m|
ScopedProxy.send(:undef_method, m) unless
- m.in?(%w{initialize method_missing}) || m.starts_with?('_')
+ m.in?(%w{initialize method_missing send}) || m.starts_with?('_')
end
attr_accessor :defined_scopes
@@ -276,28 +348,39 @@
module DefinedScopeProxyExtender
attr_accessor :reflections
def method_missing(name, *args, &block)
- scopes = proxy_reflection.klass.defined_scopes
- scope = scopes && scopes[name.to_sym]
- if scope
- scope = scope.call(*args) if scope.is_a?(Proc)
-
- # Calling directly causes self to get loaded
+ scope = (proxy_reflection.klass.respond_to?(:defined_scopes) and
+ scopes = proxy_reflection.klass.defined_scopes and
+ scopes[name.to_sym])
+
+ scope = scope.call(*args) if scope.is_a?(Proc)
+
+ # If there's no :find, or :create specified, assume it's a find scope
+ find_scope = if scope && (scope.has_key?(:find) || scope.has_key?(:create))
+ scope[:find]
+ else
+ scope
+ end
+
+ if find_scope
+ # Calling instance_variable_get directly causes self to
+ # get loaded, hence this trick
assoc = Kernel.instance_method(:instance_variable_get).bind(self).call("@#{name}")
+
unless assoc
options = proxy_reflection.options
- has_many_conditions = options.has_key?(:condition)
- scope_conditions = scope.delete(:conditions)
+ has_many_conditions = options.has_key?(:conditions)
+ scope_conditions = find_scope.delete(:conditions)
conditions = if has_many_conditions && scope_conditions
"(#{scope_conditions}) AND (#{has_many_conditions})"
else
scope_conditions || has_many_conditions
end
- options = options.merge(scope).update(:conditions => conditions,
+ options = options.merge(find_scope).update(:conditions => conditions,
:class_name => proxy_reflection.klass.name,
:foreign_key => proxy_reflection.primary_key_name)
r = ActiveRecord::Reflection::AssociationReflection.new(:has_many,
name,
options,
@@ -334,22 +417,11 @@
end
end
end
- def method_missing(name, *args, &b)
- val = super
- if val.nil?
- nil
- else
- type_wrapper = self.class.field_type(name)
- (type_wrapper && type_wrapper.is_a?(Class) && type_wrapper < String) ? type_wrapper.new(val) : val
- end
- end
-
-
- def created_by(user)
+ def set_creator(user)
self.creator ||= user if self.class.has_creator? and not user.guest?
end
def duplicate
@@ -379,13 +451,60 @@
def compose_with(object, use=nil)
CompositeModel.new_for([self, object])
end
+ def created_date
+ created_at.to_date
+ end
+ def modified_date
+ modified_at.to_date
+ end
+
def typed_id
id ? "#{self.class.name.underscore}_#{self.id}" : nil
end
+ def to_s
+ if respond_to? :title
+ title
+ elsif respond_to? :name
+ name
+ else
+ "#{self.class.name.humanize} #{id}"
+ end
+ end
+
end
end
+
+# Hack AR to get Hobo type wrappers in
+
+module ActiveRecord::AttributeMethods::ClassMethods
+
+ # Define an attribute reader method. Cope with nil column.
+ def define_read_method(symbol, attr_name, column)
+ cast_code = column.type_cast_code('v') if column
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
+
+ unless attr_name.to_s == self.primary_key.to_s
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
+ end
+
+ # This is the Hobo hook - add a type wrapper around the field
+ # value if we have a special type defined
+ src = if connected? && respond_to?(:field_type) && (type_wrapper = field_type(symbol)) &&
+ type_wrapper.is_a?(Class) && type_wrapper < String
+ "val = begin; #{access_code}; end; " +
+ "if val.nil?; nil; " +
+ "elsif val.respond_to?(:hobo_undefined?) && val.hobo_undefined?; val; " +
+ "else; #{type_wrapper}.new(val); end"
+ else
+ access_code
+ end
+
+ evaluate_attribute_method(attr_name,
+ "def #{symbol}; @attributes_cache['#{attr_name}'] ||= begin; #{src}; end; end")
+ end
+end