class AssignableValues::ActiveRecord::Restriction::Base

Attributes

default[R]
model[R]
options[R]
property[R]
secondary_default[R]
values[R]

Public Class Methods

new(model, property, options, &values) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 8
def initialize(model, property, options, &values)
  @model = model
  @property = property
  @options = options
  @values = values
  ensure_values_given
  setup_default
  define_assignable_values_method
  setup_validation
end

Public Instance Methods

assignable_values(record, options = {}) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 50
def assignable_values(record, options = {})
  additional_assignable_values = []
  current_values = assignable_values_from_record_or_delegate(record)

  if options.fetch(:include_old_value, true) && has_previously_saved_value?(record)
    old_value = previously_saved_value(record)
    if @options[:multiple]
      if old_value.is_a?(Array)
        additional_assignable_values = old_value
      end
    elsif !old_value.blank? && !current_values.include?(old_value)
      additional_assignable_values << old_value
    end
  end

  if options[:decorate]
    current_values = decorate_values(current_values)
    additional_assignable_values = decorate_values(additional_assignable_values)
  end

  if additional_assignable_values.present?
    # will not keep current_values scoped
    additional_assignable_values | current_values
  else
    current_values
  end
end
set_default(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 32
def set_default(record)
  if record.new_record? && record.send(property).nil?
    default_value = evaluate_default(record, default)
    begin
      if secondary_default? && !assignable_value?(record, default_value)
        secondary_default_value = evaluate_default(record, secondary_default)
        if assignable_value?(record, secondary_default_value)
          default_value = secondary_default_value
        end
      end
    rescue AssignableValues::DelegateUnavailable
      # skip secondary defaults if querying assignable values from a nil delegate
    end
    record.send("#{property}=", default_value)
  end
  true
end
validate_record(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 19
def validate_record(record)
  value = current_value(record)
  unless allow_blank?(record) && value.blank?
    begin
      unless assignable_value?(record, value)
        record.errors.add(error_property, not_included_error_message)
      end
    rescue DelegateUnavailable
      # if the delegate is unavailable, the validation is skipped
    end
  end
end

Private Instance Methods

active_record_2?() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 210
def active_record_2?
  ::ActiveRecord::VERSION::MAJOR < 3
end
allow_blank?(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 165
def allow_blank?(record)
  evaluate_option(record, @options[:allow_blank])
end
assignable_multi_value?(record, value) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 120
def assignable_multi_value?(record, value)
  (has_previously_saved_value?(record) && value == previously_saved_value(record)) ||
    (value.blank? ? allow_blank?(record) : subset?(value, assignable_values(record)))
end
assignable_single_value?(record, value) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 100
def assignable_single_value?(record, value)
  (has_previously_saved_value?(record) && value == previously_saved_value(record)) ||
    (value.blank? && allow_blank?(record)) ||
    included_in_assignable_values?(record, value)
end
assignable_value?(record, value) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 92
def assignable_value?(record, value)
  if @options[:multiple]
    assignable_multi_value?(record, value)
  else
    assignable_single_value?(record, value)
  end
end
assignable_values_from_delegate(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 265
def assignable_values_from_delegate(record)
  delegate = delegate(record)
  delegate.present? or raise DelegateUnavailable, "Cannot query a nil delegate for assignable values"
  delegate_query_method = :"assignable_#{model.name.underscore.gsub('/', '_')}_#{property.to_s.pluralize}"
  args = delegate.method(delegate_query_method).arity == 0 ? [] : [record]
  delegate.send(delegate_query_method, *args)
end
assignable_values_from_record_or_delegate(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 238
def assignable_values_from_record_or_delegate(record)
  assignable_values = if delegate?
    assignable_values_from_delegate(record)
  else
    record.instance_exec(&@values)
  end

  if is_scope?(assignable_values)
    assignable_values
  else
    Array(assignable_values)
  end
end
current_value(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 137
def current_value(record)
  record.send(property)
end
decorate_values(values) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 149
def decorate_values(values)
  values
end
default?() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 157
def default?
  @options.has_key?(:default)
end
define_assignable_values_method() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 225
def define_assignable_values_method
  restriction = self
  enhance_model do
    assignable_values_method = :"assignable_#{restriction.property.to_s.pluralize}"
    define_method assignable_values_method do |*args|
      # Ruby 1.8.7 does not support optional block arguments :(
      options = args.first || {}
      options.merge!({:decorate => true})
      restriction.assignable_values(self, options)
    end
  end
end
delegate(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 252
def delegate(record)
  evaluate_option(record, delegate_definition)
end
delegate?() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 153
def delegate?
  @options.has_key?(:through)
end
delegate_definition() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 169
def delegate_definition
  options[:through]
end
enhance_model(&block) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 177
def enhance_model(&block)
  @model.class_eval(&block)
end
enhance_model_singleton(&block) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 173
def enhance_model_singleton(&block)
  @model.singleton_class.class_eval(&block)
end
ensure_after_initialize_callback_enabled() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 199
def ensure_after_initialize_callback_enabled
  if active_record_2?
    enhance_model do
      # Old ActiveRecord version only call after_initialize callbacks only if this method is defined in a class.
      unless method_defined?(:after_initialize)
        define_method(:after_initialize) {}
      end
    end
  end
end
ensure_values_given() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 273
def ensure_values_given
  @values or @options[:through] or raise NoValuesGiven, 'You must supply the list of assignable values by either a block or :through option'
end
error_property() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 80
def error_property
  property
end
evaluate_default(record, value_or_proc) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 129
def evaluate_default(record, value_or_proc)
  if value_or_proc.is_a?(Proc)
    record.instance_exec(&value_or_proc)
  else
    value_or_proc
  end
end
evaluate_option(record, option) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 256
def evaluate_option(record, option)
  case option
  when NilClass, TrueClass, FalseClass then option
  when Symbol then record.send(option)
  when Proc then record.instance_exec(&option)
  else raise "Illegal option type: #{option.inspect}"
  end
end
has_previously_saved_value?(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 141
def has_previously_saved_value?(record)
  raise NotImplementedError
end
included_in_assignable_values?(record, value) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 106
def included_in_assignable_values?(record, value)
  values_or_scope = assignable_values(record, :include_old_value => false)

  if is_scope?(values_or_scope)
    values_or_scope.exists?(value.id) unless value.nil?
  else
    values_or_scope.include?(value)
  end
end
is_scope?(object) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 116
def is_scope?(object)
  object.respond_to?(:scoped) || object.respond_to?(:all)
end
not_included_error_message() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 84
def not_included_error_message
  if @options[:message]
    @options[:message]
  else
    I18n.t('errors.messages.inclusion', :default => 'is not included in the list')
  end
end
previously_saved_value(record) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 145
def previously_saved_value(record)
  raise NotImplementedError
end
secondary_default?() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 161
def secondary_default?
  @options.has_key?(:secondary_default)
end
setup_default() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 181
def setup_default
  if default?
    @default = options[:default] # for attr_reader
    @secondary_default = options[:secondary_default] # for attr_reader
    ensure_after_initialize_callback_enabled
    restriction = self
    enhance_model do
      set_default_method = :"set_default_#{restriction.property}"
      define_method set_default_method do
        restriction.set_default(self)
      end
      after_initialize set_default_method
    end
  elsif secondary_default?
    raise AssignableValues::NoDefault, "cannot use the :secondary_default option without a :default option"
  end
end
setup_validation() click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 214
def setup_validation
  restriction = self
  enhance_model do
    validate_method = :"validate_#{restriction.property}_assignable"
    define_method validate_method do
      restriction.validate_record(self)
    end
    validate validate_method.to_sym
  end
end
subset?(array1, array2) click to toggle source
# File lib/assignable_values/active_record/restriction/base.rb, line 125
def subset?(array1, array2)
  array1.is_a?(Array) && array2.is_a?(Array) && (array1 - array2).empty?
end