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 +7 -0
- data/lib/active_record/has/sparse_attributes.rb +135 -0
- data/lib/active_record/has/sparse_attributes/column_storage.rb +84 -0
- data/lib/active_record/has/sparse_attributes/storage.rb +43 -0
- data/lib/active_record/has/sparse_attributes/table_storage.rb +153 -0
- data/lib/has_sparse_attributes.rb +2 -0
- metadata +49 -0
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
|
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: []
|