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