cirandas.net

ref: master

plugins/suppliers/lib/default_delegate.rb


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
module DefaultDelegate

  module ClassMethods

    def default_delegate_setting field, options, &block
      extend ActsAsHavingSettings::ClassMethods

      prefix = options[:prefix] || :default
      default_setting = "#{prefix}_#{field}"
      settings_items default_setting, default: options[:default], type: :boolean

      options[:default_setting] = default_setting
      default_delegate field, options
    end

    # TODO: add some documentation about the methods being added
    def default_delegate field, options = {}
      class_attribute :default_delegate_enable unless respond_to? :default_delegate_enable
      self.default_delegate_enable = true if self.default_delegate_enable.nil?

      # rake db:migrate run?
      return unless self.table_exists?

      # Rails doesn't define getters for attributes
      define_method field do
        self[field]
      end if field.to_s.in? self.column_names and not self.method_defined? field
      define_method "#{field}=" do |value|
        self[field] = value
      end if field.to_s.in? self.column_names and not self.method_defined? "#{field}="

      original_field_method = "original_own_#{field}".freeze
      alias_method original_field_method, field
      own_field = "own_#{field}".freeze
      define_method own_field do
        # we prefer the value from dabatase here, and the getter may give a default value
        # e.g. Product#name defaults to Product#product_category.name
        if field.to_s.in? self.class.column_names then self[field] else self.send original_field_method end
      end
      alias_method "#{own_field}=", "#{field}="

      delegated_field = "delegated_#{field}".freeze
      to = options[:to].freeze
      define_method delegated_field do
        case to
        when Symbol
          object = self.send to
          object.send field if object and object.respond_to? field
        when Proc then instance_exec &to
        end
      end
      alias_method "original_#{field}", delegated_field

      own_field_blank = "own_#{field}_blank?".freeze
      define_method own_field_blank do
        own = self.send own_field
        # blank? also covers false, use nil? and empty? instead
        own.nil? or (own.respond_to? :empty? and own.empty?)
      end
      own_field_present = "own_#{field}_present?".freeze
      define_method own_field_present do
        not self.send own_field_blank
      end
      default_if = options[:default_if].freeze
      own_field_is_default = "own_#{field}_default?".freeze
      define_method own_field_is_default do
        default = self.send own_field_blank
        default ||= case default_if
        when Proc then instance_exec &default_if
        when :equal?
          self.send(own_field).equal? self.send(delegated_field)
        when Symbol then self.send default_if
        else false
        end
      end

      default_setting = options[:default_setting] || "#{options[:prefix] || :default}_#{field}"
      # as a field may use other field's default_setting, check for definition
      default_setting_with_presence = "#{default_setting}_with_presence".freeze
      unless self.method_defined? default_setting_with_presence
        define_method default_setting_with_presence do
          original_setting = self.send "#{default_setting}_without_presence"
          # if the setting is false, see if it should be true; if it is true, respect it.
          original_setting = self.send own_field_is_default unless original_setting
          original_setting
        end
        define_method "#{default_setting_with_presence}=" do |value|
          # this ensures latter the getter won't get a different
          self.send "#{own_field}=", nil if value
          self.send "#{default_setting}_without_presence=", value
        end
        alias_method_chain default_setting, :presence
        alias_method_chain "#{default_setting}=", :presence
      end

      define_method "#{field}_with_default" do
        return self.send own_field unless self.default_delegate_enable

        if self.send default_setting
          # delegated_field may return nil, so use own instead
          # this is the case with some associations (e.g. Product#product_qualifiers)
          # FIXME: this shouldn't be necessary, it seems to happens only in certain cases
          # (product creation, product global search, etc)
          self.send(delegated_field) || self.send(own_field)
        else self.send(own_field)
        end
      end
      define_method "#{field}_with_default=" do |*args|
        return self.send "#{own_field}=", *args unless self.default_delegate_enable

        own = self.send "#{own_field}=", *args
        # break/set the default setting automatically, used for interfaces
        # that don't have the default setting (e.g. manage_products)
        self.send "#{default_setting}=", self.send(own_field_is_default)
        own
      end
      alias_method_chain field, :default
      alias_method_chain "#{field}=", :default

      include DefaultDelegate::InstanceMethods
    end
  end

  module InstanceMethods
  end

end

ApplicationRecord.extend DefaultDelegate::ClassMethods