has_sparse_attributes 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b2787e2a6883d3d5453cdad9b12137910475140
4
+ data.tar.gz: 72a17b2ca0542d12cd31d2d8197ecc0a7f104a68
5
+ SHA512:
6
+ metadata.gz: 79f92cf8585ce27d4ac04f23825472d52cd588a907c675815cf60d46a1ea79149e2cc90fe50e8069351fddf7beef0153bfdda2e56c490f8f35fde701e5fdbb06
7
+ data.tar.gz: d5de310d95fc804ff09019f4694b5618dda8a0cc5f048c8be55540140f2419b98e6edd716a201d6d23a9f2c1f6a96e30b3a11b8c50174cec48eca16c8052e80b
@@ -0,0 +1,135 @@
1
+ require 'active_record/has/sparse_attributes/storage'
2
+ require 'active_record/has/sparse_attributes/column_storage'
3
+ require 'active_record/has/sparse_attributes/table_storage'
4
+
5
+ module ActiveRecord #:nodoc:
6
+ module Has #:nodoc:
7
+ module SparseAttributes #:nodoc:
8
+ def self.included(base) #:nodoc:
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods #:nodoc:
13
+
14
+ def has_sparse_attributes(attribute_names, *options)
15
+ options = options.extract_options!
16
+ options[:storage] ||= :column
17
+ options[:serialize] ||= false
18
+ storage = options[:storage].to_sym
19
+
20
+ # First-time initialization
21
+ if not self.methods.include?('sparse_attributes')
22
+ class_eval <<-EOV
23
+ cattr_accessor :sparse_attributes, :instance_accessor => false
24
+ @@sparse_attributes = {}
25
+
26
+ cattr_accessor :sparse_attribute_storage_configs
27
+ @@sparse_attribute_storage_configs = []
28
+
29
+ before_update :sparse_attributes_before_update
30
+ before_save :sparse_attributes_before_save
31
+ after_save :sparse_attributes_after_save
32
+
33
+ def self.has_sparse_attribute?(name)
34
+ attribute = name.to_s
35
+ attribute = attribute[0..-2] if attribute.ends_with?('=')
36
+ return @@sparse_attributes.has_key?(attribute.to_sym)
37
+ end
38
+
39
+ include ActiveRecord::Has::SparseAttributes::InstanceMethods
40
+ EOV
41
+ end
42
+
43
+ # Add getters and setters for each sparse attribute
44
+ attribute_names.each do |name|
45
+ class_eval <<-EOV
46
+ def #{name}()
47
+ get_sparse_attribute(:#{name})
48
+ end
49
+
50
+ def #{name}=(v)
51
+ set_sparse_attribute(:#{name}, v)
52
+ end
53
+ EOV
54
+ end
55
+
56
+ if storage == :column
57
+ storage_config = ColumnStorageConfig.new(self, options)
58
+ elsif storage == :table
59
+ storage_config = TableStorageConfig.new(self, options)
60
+ else
61
+ raise StandardError.new("Invalid storage option for has_sparse_attributes")
62
+ end
63
+
64
+ self.sparse_attribute_storage_configs << storage_config
65
+ storage_id = self.sparse_attribute_storage_configs.length - 1
66
+
67
+ attribute_names.each do |name|
68
+ self.sparse_attributes[name.to_sym] = storage_id
69
+ end
70
+
71
+ end
72
+ end
73
+
74
+ module InstanceMethods
75
+
76
+ def get_sparse_attributes()
77
+ r = {}
78
+ self.class.sparse_attributes.each_key do |k|
79
+ v = self.get_sparse_attribute(k)
80
+ r[k] = v unless v.nil?
81
+ end
82
+ return r
83
+ end
84
+
85
+ def get_sparse_attribute(name)
86
+ self.get_sparse_attribute_storage(name).get(name)
87
+ end
88
+
89
+ def set_sparse_attribute(name, value)
90
+ self.get_sparse_attribute_storage(name).set(name, value)
91
+ end
92
+
93
+ protected
94
+
95
+ attr_accessor :sparse_attribute_storage
96
+
97
+ def get_sparse_attribute_storage(name)
98
+ create_sparse_attribute_storage()
99
+ self.sparse_attribute_storage[self.class.sparse_attributes[name.to_sym]]
100
+ end
101
+
102
+ def create_sparse_attribute_storage()
103
+ if self.sparse_attribute_storage.nil?
104
+ self.sparse_attribute_storage = []
105
+ self.class.sparse_attribute_storage_configs.each do |config|
106
+ self.sparse_attribute_storage << config.instance(self)
107
+ end
108
+ end
109
+ end
110
+
111
+ def sparse_attributes_before_update(*args)
112
+ create_sparse_attribute_storage()
113
+ self.sparse_attribute_storage.each do |store|
114
+ store.before_update(*args)
115
+ end
116
+ end
117
+
118
+ def sparse_attributes_before_save(*args)
119
+ create_sparse_attribute_storage()
120
+ self.sparse_attribute_storage.each do |store|
121
+ store.before_save(*args)
122
+ end
123
+ end
124
+
125
+ def sparse_attributes_after_save(*args)
126
+ create_sparse_attribute_storage()
127
+ self.sparse_attribute_storage.each do |store|
128
+ store.after_save(*args)
129
+ end
130
+ end
131
+
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,84 @@
1
+ module ActiveRecord #:nodoc:
2
+ module Has #:nodoc:
3
+ module SparseAttributes #:nodoc:
4
+
5
+ class ColumnStorageConfig < StorageConfig
6
+ attr_accessor :column_name
7
+
8
+ def initialize(klass, options = {})
9
+ super
10
+ @column_name = options[:column_name] || 'sparse_attributes'
11
+ serialize_class = options[:serialize_class]
12
+ if serialize_class != false
13
+ @model_class.class_eval "serialize '#{@column_name}'" +
14
+ (serialize_class ? ", #{serialize_class.to_s}" : '')
15
+ end
16
+ end
17
+
18
+ def instance(record)
19
+ ColumnStorage.new(record, self)
20
+ end
21
+ end
22
+
23
+ class ColumnStorage < Storage
24
+ attr_accessor :updated_attributes
25
+
26
+ def get(name)
27
+ col = @config.column_name
28
+ return nil if @record[col].nil?
29
+ return @record[col][name.to_s]
30
+ end
31
+
32
+ def set(name, value)
33
+ name = name.to_s
34
+ col = @config.column_name
35
+ @updated_attributes = {} if @updated_attributes.nil?
36
+ if @record[col].nil?
37
+ @record[col] = {}
38
+ end
39
+ a = @record[col]
40
+ if value.nil?
41
+ a.delete(name)
42
+ else
43
+ value = value.to_s if !@config.serialize_values
44
+ a[name] = value
45
+ end
46
+ @updated_attributes[name] = true
47
+ @record[col] = a
48
+ end
49
+
50
+ def before_update(*args)
51
+ merge_sparse_attributes()
52
+ end
53
+
54
+ def after_save(*args)
55
+ clear_updated_sparse_attributes()
56
+ end
57
+
58
+ protected
59
+
60
+ def merge_sparse_attributes()
61
+ return if @updated_attributes.nil?
62
+ col = @config.column_name
63
+ obj = @config.model_class.select(col.to_s).find(@record.id)
64
+ current = obj[col]
65
+ if current.nil? or current.empty?
66
+ return
67
+ end
68
+ @updated_attributes.each do |key, value|
69
+ if @record[col].has_key?(key)
70
+ current[key] = @record[col][key]
71
+ else
72
+ current.delete(key)
73
+ end
74
+ end
75
+ @record[col] = current
76
+ end
77
+
78
+ def clear_updated_sparse_attributes
79
+ @updated_attributes = {}
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveRecord #:nodoc:
2
+ module Has #:nodoc:
3
+ module SparseAttributes #:nodoc:
4
+ class StorageConfig
5
+
6
+ attr_accessor :model_class
7
+ attr_accessor :serialize_values
8
+
9
+ def initialize(klass, options = {})
10
+ @model_class = klass
11
+ @serialize_values = options[:serialize] || false
12
+ end
13
+
14
+ end
15
+
16
+ class Storage
17
+
18
+ attr_accessor :config
19
+ attr_accessor :record
20
+
21
+ def initialize(record, config)
22
+ @record = record
23
+ @config = config
24
+ end
25
+
26
+ def get(name)
27
+ end
28
+
29
+ def set(name, value)
30
+ end
31
+
32
+ def before_save(*args)
33
+ end
34
+
35
+ def before_update(*args)
36
+ end
37
+
38
+ def after_save(*args)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,153 @@
1
+ module ActiveRecord #:nodoc:
2
+ module Has #:nodoc:
3
+ module SparseAttributes #:nodoc:
4
+
5
+ class TableStorageConfig < StorageConfig
6
+ attr_accessor :attribute_model
7
+ attr_accessor :id_column
8
+
9
+ def initialize(klass, options = {})
10
+ super
11
+ options[:attribute_class_name] ||= 'SparseAttribute'
12
+ options[:id_column] ||= @model_class.model_name.singular.to_s + '_id'
13
+ if !options[:table_name].blank?
14
+ if ActiveRecord::Base.methods.include?(:set_table_name)
15
+ table_name = ('set_table_name "' + options[:table_name] + '";') || ''
16
+ else
17
+ table_name = 'self.table_name = "' + options[:table_name] + '";'
18
+ end
19
+ else
20
+ table_name = ''
21
+ end
22
+ @id_column = options[:id_column]
23
+ klass.class_eval "class #{options[:attribute_class_name]} < ActiveRecord::Base; #{table_name} validates_presence_of :#{@id_column}, :name; end"
24
+ @attribute_model = eval("klass::#{options[:attribute_class_name]}")
25
+ end
26
+
27
+ def instance(record)
28
+ TableStorage.new(record, self)
29
+ end
30
+ end
31
+
32
+ class TableStorage < Storage
33
+ attr_accessor :sparse_attributes
34
+ attr_accessor :sparse_attribute_values
35
+ attr_accessor :updated_sparse_attributes
36
+
37
+ def after_save(*args)
38
+ save()
39
+ end
40
+
41
+ def load()
42
+ @sparse_attributes = {}
43
+ @sparse_attribute_values = {}
44
+ @updated_sparse_attributes = []
45
+
46
+ # The item has not been saved - nothing to load
47
+ if @record.id.nil?
48
+ return
49
+ end
50
+
51
+ unserialize = @config.serialize_values
52
+ attributes = @config.attribute_model.where(@config.id_column => @record.id)
53
+ attributes.each do |attr|
54
+ @sparse_attributes[attr.name] = attr
55
+ @sparse_attribute_values[attr.name] = unserialize ? YAML::load(attr.value) : attr.value
56
+ end
57
+ end
58
+
59
+ def save()
60
+ return 0 if @updated_sparse_attributes.nil?
61
+ num_updates = 0
62
+ klass = @config.attribute_model
63
+ klass_id_column = @config.id_column
64
+ serialize = @config.serialize_values
65
+ @updated_sparse_attributes.each do |name|
66
+ value = @sparse_attribute_values[name]
67
+ have_attribute = @sparse_attributes.has_key?(name)
68
+
69
+ # If the value is nil we will delete the attribute row
70
+ if value.nil?
71
+ num_updates += delete_row(name)
72
+ else
73
+ value = value.to_yaml if serialize
74
+ num_updates += 1 if set_row(name, value)
75
+ end
76
+ end
77
+
78
+ @updated_sparse_attributes = []
79
+ return num_updates
80
+ end
81
+
82
+ def get(name)
83
+ load() if @sparse_attributes.nil?
84
+ return @sparse_attribute_values[name.to_s]
85
+ end
86
+
87
+ def set(name, value)
88
+ load() if @sparse_attributes.nil?
89
+
90
+ name = name.to_s
91
+ if value.nil?
92
+ @sparse_attribute_values[name] = nil
93
+ else
94
+ value = value.to_s unless @config.serialize_values
95
+ @sparse_attribute_values[name] = value
96
+ end
97
+ @updated_sparse_attributes << name
98
+ end
99
+
100
+ protected
101
+
102
+ def delete_row(name)
103
+ klass = @config.attribute_model
104
+ have_attribute = @sparse_attributes.has_key?(name)
105
+
106
+ # If we have the attribute ActiveRecord instance
107
+ # we may be able to just delete the row
108
+ deleted = klass.delete(@sparse_attributes[name].id) if have_attribute
109
+
110
+ # If the attribute couldn't be deleted by id
111
+ # we just scan for the record
112
+ if !have_attribute || deleted == 0
113
+ deleted = klass.where(["#{@config.id_column} = ? AND name = ?", @record.id, name]).delete_all
114
+ end
115
+ return deleted
116
+ end
117
+
118
+ def set_row(name, value)
119
+ klass = @config.attribute_model
120
+ klass_id_column = @config.id_column
121
+ have_attribute = @sparse_attributes.has_key?(name)
122
+
123
+ if have_attribute
124
+ # We already have the attribute so we should be able to just update
125
+ # the row based on the id, unless it has been deleted
126
+ attribute = @sparse_attributes[name]
127
+ attribute.value = value
128
+ updated = attribute.save
129
+ end
130
+
131
+ if !have_attribute || updated == false
132
+ if klass.respond_to?(:find_or_create_by)
133
+ attribute = klass.create_with(:value => value).find_or_create_by(klass_id_column => @record.id, :name => name)
134
+ else
135
+ method_name = ('find_or_create_by_' + klass_id_column.to_s + '_and_name').to_sym
136
+ attribute = klass.send(method_name, klass_id_column => @record.id, :name => name, :value => value)
137
+ end
138
+ if attribute.value != value
139
+ attribute.value = value
140
+ updated = attribute.save
141
+ else
142
+ updated = 1
143
+ end
144
+
145
+ @sparse_attributes[name] = attribute
146
+ end
147
+ return updated
148
+ end
149
+
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_record/has/sparse_attributes'
2
+ ActiveRecord::Base.class_eval { include ActiveRecord::Has::SparseAttributes }
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_sparse_attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Edward Potocko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-12-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: epotocko@equallevel.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/active_record/has/sparse_attributes.rb
20
+ - lib/active_record/has/sparse_attributes/column_storage.rb
21
+ - lib/active_record/has/sparse_attributes/storage.rb
22
+ - lib/active_record/has/sparse_attributes/table_storage.rb
23
+ - lib/has_sparse_attributes.rb
24
+ homepage:
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options:
30
+ - "--charset=UTF-8"
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.6.14.1
46
+ signing_key:
47
+ specification_version: 3
48
+ summary: Extension for ActiveRecord to allow sparse attributes
49
+ test_files: []