has_sparse_attributes 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []