require 'ostruct' module Storefront class ModelProxy class << self def model_names(array) Array(array).flatten.map do |object| case object when ::Class object.ancestor_classes.map { |i| i.name.underscore } when ::String, ::Symbol object else object.class.ancestor_classes.map { |i| i.name.underscore } end end.flatten.compact.uniq end end attr_reader :object, :parent, :errors, :keys def initialize(options = {}, &block) @object = options[:object] @parent = options[:parent] if options[:keys].present? @keys = options[:keys] elsif @object.is_a?(::OpenStruct) && @object.klass.present? @keys = [@object.klass.to_s.underscore] else @keys = [@object.class.name.underscore] end end def new_record? @object.respond_to?(:new_record?) && !@object.new_record? end def config Storefront.configuration end def self_and_ancestor_names @self_and_ancestor_names ||= self.class.model_names(@object) end def get(attribute) @object.send(attribute) end def validators? @object.class.respond_to?(:validators_on) end def validators_on(attribute) @object.class.validators_on(attribute) end def validation_message(attribute, key, options = {}) @object.errors.generate_message(attribute, key, options) end def errors_on?(attribute, options = {}) @object.respond_to?(:errors) ? @object.errors[attribute].present? : false end def default_input_type(method, options = {}) #:nodoc: if column = column_for(method) # Special cases where the column type doesn't map to an input method. case column.type when :string return :password if method.to_s =~ /password/ return :country if method.to_s =~ /country$/ return :time_zone if method.to_s =~ /time_zone/ return :email if method.to_s =~ /email/ return :url if method.to_s =~ /^url$|^website$|_url$/ return :phone if method.to_s =~ /(phone|fax)/ return :search if method.to_s =~ /^search$/ when :integer return :select if reflection_for(method) return :numeric when :float, :decimal return :numeric when :timestamp return :datetime end # Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database. return :select if column.type == :string && options.key?(:collection) # Try 3: Assume the input name will be the same as the column type (e.g. string_input). return column.type else if @object return :select if reflection_for(method) return :file if is_file?(method, options) end return :select if options.key?(:collection) return :password if method.to_s =~ /password/ return :string end end def is_file?(method, options = {}) @files ||= {} @files[method] ||= (options[:as].present? && options[:as] == :file) || begin file = @object.send(method) if @object && @object.respond_to?(method) file && file_methods.any?{|m| file.respond_to?(m)} end end def file_methods [:file?, :public_filename, :filename] end def errors_for(name) return @object.errors[name] if errors_on?(name) end def humanized_attribute_name(method) #:nodoc: if @object && @object.class.respond_to?(:human_attribute_name) @object.class.human_attribute_name(method.to_s) else method.to_s.send(config.label_method) end end def default(attribute) if @object.respond_to?(attribute) result = case macro_for(attribute) when :belongs_to, :has_one @object.send(attribute) || @object.send("build_#{attribute}") when :has_many @object.send(attribute.to_s.pluralize).build else @object.send(attribute) end result elsif @object.is_a?(::OpenStruct) result = ::OpenStruct.new result.klass = attribute.to_s.camelize @object.send("#{attribute}=", result) result else nil end end # If an association method is passed in (f.input :author) try to find the # reflection object. # def reflection_for(method) #:nodoc: @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association) end def macro_for(method) reflection = reflection_for(method) reflection ? reflection.macro : nil end # Get a column object for a specified attribute method - if possible. # def column_for(method) #:nodoc: @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute) end def validations_for(method, mode = :active) # ActiveModel? validations = if @object && @object.class.respond_to?(:validators_on) @object.class.validators_on(method) else # ValidationReflection plugin? if @object && @object.class.respond_to?(:reflect_on_validations_for) @object.class.reflect_on_validations_for(method) else [] end end validations = validations.select do |validation| (validation.options.present? ? options_require_validation?(validation.options) : true) end unless mode == :all return validations end def get_maxlength_for(method) validation = validations_for(method).find do |validation| (validation.respond_to?(:macro) && validation.macro == :validates_length_of) || # Rails 2 validation (validation.respond_to?(:kind) && validation.kind == :length) # Rails 3 validator end if validation validation.options[:maximum] || (validation.options[:within].present? ? validation.options[:within].max : nil) else nil end end # remove this, it's for old rails def required?(attribute) attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym if @object && @object.class.respond_to?(:reflect_on_validations_for) @object.class.reflect_on_validations_for(attribute_sym).any? do |validation| (validation.macro == :validates_presence_of || validation.macro == :validates_inclusion_of) && validation.name == attribute_sym && (validation.options.present? ? options_require_validation?(validation.options) : true) end else if @object && @object.class.respond_to?(:validators_on) !@object.class.validators_on(attribute_sym).find{|validator| (validator.kind == :presence || validator.kind == :inclusion) && (validator.options.present? ? options_require_validation?(validator.options) : true)}.nil? else true end end end def options_require_validation?(options) allow_blank = options[:allow_blank] return !allow_blank unless allow_blank.nil? if_condition = !options[:if].nil? condition = if_condition ? options[:if] : options[:unless] condition = if condition.respond_to?(:call) condition.call(@object) elsif condition.is_a?(::Symbol) && @object.respond_to?(condition) @object.send(condition) else condition end if_condition ? !!condition : !condition end def default_string_options(method, type) validation_max_limit = get_maxlength_for(method) column = column_for(method) if type == :text {:rows => default_text_area_height, :cols => config.default_text_area_width} elsif type == :numeric || column.nil? || !column.respond_to?(:limit) || column.limit.nil? {:maxlength => validation_max_limit, :size => config.default_text_field_size} else {:maxlength => validation_max_limit || column.limit, :size => config.default_text_field_size} end end end end