lib/extend_at.rb in extend_at-0.0.3 vs lib/extend_at.rb in extend_at-0.1.0

- old
+ new

@@ -1,7 +1,9 @@ # encoding: utf-8 require "extend_at/version" +require "extend_at/model_manager" +require "extend_at/models/all" module ExtendModelAt def self.included(base) base.extend(ClassMethods) end @@ -11,10 +13,11 @@ def initialize(options={}) @model = options[:model] @column_name = options[:column_name].to_s @columns = options[:columns] @value = get_defaults_values options + @model_manager = ::ExtendModelAt::ModelManager.new(@column_name, @model, @columns) raise "#{@column_name} should by text or string not #{options[:model].column_for_attribute(@column_name.to_sym).type}" if not [:text, :stiring].include? options[:model].column_for_attribute(@column_name.to_sym).type out = YAML.parse(@model[@column_name].to_s) if out == false @@ -24,27 +27,37 @@ end @value.merge! db_value if db_value.kind_of? Hash initialize_values + @model.attributes[@column_name] = @value - @model.save(:validate => false) end def [](key) - @value[key.to_s] + @model_manager.get_value(key) end def []=(key, value) - if @columns[key.to_sym].kind_of? Hash and ((@columns[key.to_sym][:type] == :boolean and (not [true.class, false.class].include? value.class)) or - ((not [:boolean, nil].include?(@columns[key.to_sym][:type])) and @columns[key.to_sym][:type] != value.class )) - raise "#{value.inspect} is not a valid type, expected #{@columns[key.to_sym][:type]}" + if not valid_type? value, @columns[key.to_sym].try(:[],:type) + # Try to adapt the value + adapter = get_adapter key, value + raise "#{value.inspect} is not a valid type, expected #{@columns[key.to_sym][:type]}" if adapter.nil? # We can't adapt the value + value = value.send adapter end @value[key.to_s] = value - @model.send :"#{@column_name}=", @value.to_yaml + @model_manager.assign(key,value) end + def self.respond_to?(symbol, include_private=false) + true + end + + def respond_to?(symbol, include_private=false) + true + end + # Use the undefined method as a column def method_missing(m, *args, &block) # If the method don't finish with "=" is fore read if m !~ /\=$/ self[m.to_s] @@ -55,10 +68,25 @@ end end private + def get_adapter(column, value) + if @columns[column.to_sym][:type] == String + return :to_s + elsif @columns[column.to_sym][:type] == Fixnum + return :to_i if value.respond_to? :to_i + elsif @columns[column.to_sym][:type] == Float + return :to_f if value.respond_to? :to_f + elsif @columns[column.to_sym][:type] == Time + return :to_time if value.respond_to? :to_time + elsif @columns[column.to_sym][:type] == Date + return :to_date if value.respond_to? :to_date + end + nil + end + def initialize_values if not @value.kind_of? Hash @model.attributes[@column_name] = {}.to_yaml @model.save end @@ -69,10 +97,22 @@ options[:columns].each do |column, config| defaults_[column.to_s] = @columns[column.to_sym][:default] || nil end defaults_ end + + def update_model_manager + @model_manager.send :update + end + + def valid_type?(value, type) + @model.send :valid_type?, value, type + end + + def search(column, method, value) + @model_manager.send:search, column, method, value + end end module ClassMethods def extend_at(column_name, options = {}) assign_attributes_eval = " @@ -102,11 +142,10 @@ # Write the value of <<attributes>> methods like <column name>_<extended column name> def []=(column, value) if column.to_s =~ /^#{column_name}_[a-zA-Z_][a-zA-Z_0-9]*\=?$/ rb = \"self.#{column_name}.\#\{column.to_s.gsub(/^#{column_name}_/,'').gsub(/\=$/, '')\} = value\" - puts \"Evaluando: #\{rb}\" eval rb, binding else super end end @@ -118,45 +157,83 @@ else super end end + # Respond to ethod like <column name>_<extended column name> for read or write + def respond_to?(symbol, include_private=false) + if symbol.to_s =~ /^#{column_name}_[a-zA-Z_][a-zA-Z_0-9]*\=?$/ + return true + else + super + end + end + + def self.method_missing(m, *args, &block) + if m.to_s =~ /^#{column_name}_[a-zA-Z_][a-zA-Z_0-9]+_(#\{VALID_COMPARATIONS.join('|')})$/ + method = m[/(#\{VALID_COMPARATIONS.join('|')})$/] + column = m.to_s.gsub(/^#{column_name}_/, '').gsub(/_(#\{VALID_COMPARATIONS.join('|')})$/, '') + + code = (self.last || self.new).send :search_in_extention, column, method, args.first + + value = args.first + + return eval code, binding + else + super + end + end + # Accept method like <column name>_<extended column name> for read or write def method_missing(m, *args, &block) - if m.to_s =~ /^#{column_name}_[a-zA-Z_][a-zA-Z_0-9]*\=$/ + if m.to_s =~ /^#{column_name}_[a-zA-Z_][a-zA-Z_0-9]+_(#\{VALID_COMPARATIONS.join('|')})$/ + method = m[/(#\{VALID_COMPARATIONS.join('|')})$/] + column = m.to_s.gsub(/^#{column_name}_/, '').gsub(/(#\{VALID_COMPARATIONS.join('|')})/, '') + return search_in_extention column, method, args.first + elsif m.to_s =~ /^#{column_name}_[a-zA-Z_][a-zA-Z_0-9]*\=$/ rb = \"self.#{column_name}.\#\{m.to_s.gsub(/^#{column_name}_/, '').gsub(/\=$/, '')} = args.first\" return eval rb, binding elsif m.to_s =~ /^#{column_name}_[a-zA-Z_][a-zA-Z_0-9]*$/ rb = \"self.#{column_name}.\#\{m.to_s.gsub(/^#{column_name}_/, '')}\" return eval rb, binding else super end end + + protected + VALID_COMPARATIONS = ['gt', 'gt_eq', 'lt', 'lt_eq', 'eq', 'in', 'match'] + + def search_in_extention(column, method, value) + #{column_name.to_s}.send :search, column, method, value + end " self.class_eval <<-EOS eval assign_attributes_eval EOS class_eval <<-EOV public validate :extend_at_validations + after_save :update_model_manager, :on => :create def #{column_name.to_s} if not @#{column_name.to_s}_configuration.kind_of? ExtendModelAt::Extention opts = initialize_options(#{options}) options = { :extensible => true # If is false, only the columns defined in :columns can be used - }.merge! opts + }.merge!(opts) columns = initialize_columns expand_options(options, { :not_call_symbol => [:boolean], :not_expand => [:validate, :default] }) if options.kind_of? Hash @#{column_name.to_s}_configuration ||= ExtendModelAt::Extention.new({:model => self, :column_name => :#{column_name.to_s}, :columns => columns}) end @#{column_name.to_s}_configuration end protected + VALID_SYMBOLS = [:any, :binary, :boolean, :date, :datetime, :decimal, :float, :integer, :string, :text, :time, :timestamp] + def extend_at_validations self.#{column_name}.valid? @extend_at_validation ||= {} if not @extend_at_validation.kind_of? Hash @extend_at_validation.each do |column, validation| if validation.kind_of? Symbol @@ -196,20 +273,15 @@ column_config = {} # Stablish the type if config[:type].class == Class # If exist :type, is a static column + column_config[:type] = get_type_for_class config[:type] + elsif config[:type].class == Symbol and VALID_SYMBOLS.include? config[:type] column_config[:type] = config[:type] else - # if not, is a dynamic column - if config[:type].to_sym == :any - column_config[:type] = nil - elsif config[:type].to_sym == :boolean - column_config[:type] = :boolean - else - raise "\#\{config[:type]\} is not a valid column type" - end + raise "\#\{config[:type]\} is not a valid column type" end # Stablish the default value # if is a symbol, we execute the function from the model if config[:default].kind_of? Symbol @@ -217,12 +289,11 @@ elsif config[:default].kind_of? Proc column_config[:default] = config[:default].call else # If the column have a type, we verify the type if not column_config[:type].nil? - if (column_config[:type] == :boolean and (not [true.class, false.class].include? config[:default].class)) or - ((not [:boolean, nil].include?(column_config[:type])) and column_config[:type] != config[:default].class ) + if not valid_type?(config[:default], column_config[:type]) raise "The column \#\{column\} has an invalid default value. Expected \#\{column_config[:type]}, not \#\{config[:default].class}" end column_config[:default] = config[:default] else # If is dynamic, only we set the default value @@ -232,25 +303,38 @@ # Set the validation if [Symbol, Proc].include? config[:validate].class column_config[:validate] = config[:validate] create_validation_for column, config[:validate] - else + elsif not config[:validate].nil? raise "The validation of \#\{column\} is invalid" end column_config end + def get_type_from_symbol(type) + type = type.to_s + return nil if type == 'any' or type == '' + return :boolean if type == 'boolean' + return Float if type == 'float' + return Fixnum if type == 'integer' + return String if type == 'string' or type == 'text' + return Time if type == 'time' or type == 'timestamp' + return Date if type == 'date' or type == 'datetime' + return eval type.classify + end + def create_validation_for(column, validation) column = column.to_sym @extend_at_validation ||= {} @extend_at_validation[column] = validation end def expand_options(options={}, opts={}) + options = get_value_of options config_opts = { :not_expand => [], :not_call_symbol => [] }.merge! opts if options.kind_of? Hash @@ -284,9 +368,43 @@ elsif value.kind_of? Proc return value.call else return value end + end + + def update_model_manager + self.#{column_name}.send :update_model_manager + end + + def get_type_for_class(type) + type = type.name + return :any if type == 'NilClass' + return :float if type == 'Float' + return :integer if type == 'Fixnum' + return :text if type == 'String ' + return :timestamp if type == 'Time' + return :datetime if type == 'Date' + return :any + end + + def compatible_type(value,type) + return true if value.class == String and [:string, :text, :binary].include? type + return true if value.class == Fixnum and [:integer, :float].include? type + return true if [true.class, false.class].include? value.class and [:boolean].include? type + return true if value.class == BigDecimal and [:decimal].include? type + return true if [Date, Time].include? value.class and [:date, :time].include? type + return true if value.class == BigDecimal and [:decimal].include? type + return true if [Date, Time, ActiveSupport::TimeWithZone].include? value.class and [:datetime, :timestamp].include? type + false + end + + def valid_type?(value, type) + type = type.to_s.to_sym + [:"", :any].include? type or + value.nil? or + (type == :boolean and ([true.class, false.class].include? value.class)) or + ((not [:boolean, nil].include?(type)) and not value.nil? and compatible_type(value, type)) end EOV end end end \ No newline at end of file