cirandas.net

ref: master

plugins/orders/lib/serialized_synced_data.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
module SerializedSyncedData

  def self.prepare_data _hash
    return {} unless _hash
    hash = {}; _hash.each do |key, value|
      next if value.blank?
      hash[key.to_sym] = value
    end
    hash
  end

  module ClassMethods

    def sync_serialized_field field, &block
      class_attribute :serialized_synced_fields unless self.respond_to? :serialized_synced_fields
      self.serialized_synced_fields ||= []
      self.serialized_synced_fields << field

      attribute  = "#{field}_id"
      field_data = "#{field}_data".to_sym
      field_data_without_sync = "#{field_data}_without_sync".to_sym

      serialize field_data
      before_save "fill_#{field_data}"

      # Rails doesn't define getters/setter for attributes
      if not self.method_defined? field_data and field_data.to_s.in? self.column_names
        define_method field_data do
          self[field_data] || {}
        end
      else
        define_method "#{field_data}_with_sync_default" do
          self.send("#{field_data}_without_sync_default") || {}
        end
        alias_method_chain field_data, :sync_default
      end
      if not self.method_defined? "#{field_data}=" and field_data.to_s.in? self.column_names
        define_method "#{field_data}=" do |value|
          self[field_data] = SerializedSyncedData.prepare_data value
        end
      else
        define_method "#{field_data}_with_prepare=" do |value|
          self.send "#{field_data}_without_prepare=", SerializedSyncedData.prepare_data(value)
        end
        alias_method_chain "#{field_data}=", :prepare
      end

      # return data from foreign registry if any data was synced yet
      define_method "#{field_data}_with_sync" do
        current_data = self.send field_data_without_sync
        if current_data.present? then current_data else self.send "#{field}_synced_data" end
      end
      alias_method_chain field_data, :sync

      # get the data to sync as defined
      define_method "#{field}_synced_data" do
        source = self.send field
        if block_given?
          data = SerializedSyncedData.prepare_data instance_exec(&block)
        elsif source.is_a? ApplicationRecord
          data = SerializedSyncedData.prepare_data source.attributes
        elsif source.is_a? Array
          data = source.map{ |s| SerializedSyncedData.prepare_data s.attributes }
        end || {}
      end

      define_method "sync_#{field_data}" do
        value     = self.send "#{field}_synced_data"
        current   = self.send field_data
        # leave old fields unchanged
        new_value = current.deep_merge value
        self.send "#{field_data}=", new_value
      end

      # fill and update data
      define_method "fill_#{field_data}" do
        return unless self.send(field_data_without_sync).blank? or self.send("#{attribute}_changed?")
        self.send "sync_#{field_data}"
      end

      include InstanceMethods
    end

  end

  module InstanceMethods

    def sync_serialized_data
      self.class.serialized_synced_fields.each do |field|
        self.send "sync_#{field}_data"
      end
    end

  end

end