axle_attributes 1.13.2
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/.gitignore +4 -0
- data/.travis.yml +16 -0
- data/Gemfile +15 -0
- data/README.md +10 -0
- data/Rakefile +18 -0
- data/app/models/attribute_customization.rb +7 -0
- data/app/models/attribute_customization/local_cache.rb +28 -0
- data/app/models/attribute_mapping.rb +15 -0
- data/app/models/attribute_mapping/local_cache.rb +40 -0
- data/axle_attributes.gemspec +22 -0
- data/bin/rails +8 -0
- data/db/migrate/20131118042651_create_attribute_customizations.rb +16 -0
- data/db/migrate/20131121013237_create_attribute_mappings.rb +17 -0
- data/db/migrate/20151120160522_add_editable_to_attribute_customizations.rb +7 -0
- data/lib/axle_attributes.rb +30 -0
- data/lib/axle_attributes/child_definition.rb +42 -0
- data/lib/axle_attributes/conversions.rb +36 -0
- data/lib/axle_attributes/definition.rb +118 -0
- data/lib/axle_attributes/definition/callbacks.rb +34 -0
- data/lib/axle_attributes/definition/customization.rb +51 -0
- data/lib/axle_attributes/definition/formatted.rb +57 -0
- data/lib/axle_attributes/definition/indexed.rb +41 -0
- data/lib/axle_attributes/definition/mappings.rb +48 -0
- data/lib/axle_attributes/dumper.rb +54 -0
- data/lib/axle_attributes/engine.rb +4 -0
- data/lib/axle_attributes/format.rb +74 -0
- data/lib/axle_attributes/has_attributes.rb +104 -0
- data/lib/axle_attributes/has_attributes/json_reader.rb +87 -0
- data/lib/axle_attributes/model.rb +34 -0
- data/lib/axle_attributes/null_definition.rb +11 -0
- data/lib/axle_attributes/parent_definition.rb +37 -0
- data/lib/axle_attributes/provided.rb +67 -0
- data/lib/axle_attributes/regex.rb +4 -0
- data/lib/axle_attributes/segmented.rb +30 -0
- data/lib/axle_attributes/serializations.rb +147 -0
- data/lib/axle_attributes/serializations/builder/serialization.rb +83 -0
- data/lib/axle_attributes/serializations/builder/serialize_many.rb +17 -0
- data/lib/axle_attributes/serializations/builder/serialize_one.rb +7 -0
- data/lib/axle_attributes/serializations/reflection.rb +40 -0
- data/lib/axle_attributes/serializations/serialization.rb +88 -0
- data/lib/axle_attributes/serializations/serialize_many.rb +88 -0
- data/lib/axle_attributes/serializations/serialize_one.rb +34 -0
- data/lib/axle_attributes/serialized_child.rb +58 -0
- data/lib/axle_attributes/validations.rb +29 -0
- data/lib/axle_attributes/versioned.rb +21 -0
- data/test/dummy/README.rdoc +6 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +12 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +13 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +80 -0
- data/test/dummy/config/environments/test.rb +38 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +12 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/db/schema.rb +50 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +58 -0
- data/test/dummy/public/422.html +58 -0
- data/test/dummy/public/500.html +57 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/factories/all.rb +14 -0
- data/test/helper.rb +25 -0
- data/test/lib/child_definition_test.rb +43 -0
- data/test/lib/conversions_test.rb +39 -0
- data/test/lib/definition/customization_test.rb +36 -0
- data/test/lib/definition/formatted_test.rb +75 -0
- data/test/lib/definition/indexed_test.rb +57 -0
- data/test/lib/definition/mappings_test.rb +26 -0
- data/test/lib/definition_test.rb +84 -0
- data/test/lib/dumper_test.rb +38 -0
- data/test/lib/format_test.rb +64 -0
- data/test/lib/has_attributes/json_reader_test.rb +46 -0
- data/test/lib/has_attributes_test.rb +69 -0
- data/test/lib/model_test.rb +44 -0
- data/test/lib/null_definition_test.rb +27 -0
- data/test/lib/parent_definition_test.rb +75 -0
- data/test/lib/provided_test.rb +46 -0
- data/test/lib/segmented_test.rb +27 -0
- data/test/lib/serializations/reflection_test.rb +24 -0
- data/test/lib/serializations/serialize_many_test.rb +194 -0
- data/test/lib/serializations/serialize_one_test.rb +122 -0
- data/test/lib/serializations_test.rb +24 -0
- data/test/lib/serialized_child_test.rb +91 -0
- data/test/lib/timestamp_attributes_test.rb +14 -0
- data/test/lib/validations_test.rb +8 -0
- data/test/lib/versioned_test.rb +35 -0
- data/test/models/attribute_customization/local_cache_test.rb +16 -0
- data/test/models/attribute_customization_test.rb +8 -0
- data/test/models/attribute_mapping/local_cache_test.rb +31 -0
- data/test/models/attribute_mapping_test.rb +20 -0
- data/test/support/business.rb +22 -0
- data/test/support/pg.rb +9 -0
- data/test/support/vegetable.rb +8 -0
- metadata +215 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module AxleAttributes::Serializations::Builder
|
|
2
|
+
class SerializeMany < Serialization
|
|
3
|
+
def index_type
|
|
4
|
+
:nested
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def meta_attributes
|
|
8
|
+
if reflection.counter_cache_column
|
|
9
|
+
{
|
|
10
|
+
reflection.counter_cache_column => {type: :integer, index: true, version: false, editable: false}
|
|
11
|
+
}
|
|
12
|
+
else
|
|
13
|
+
{}
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module AxleAttributes
|
|
2
|
+
module Serializations
|
|
3
|
+
class Reflection
|
|
4
|
+
attr_accessor :macro, :name, :options
|
|
5
|
+
def initialize(macro, name, options)
|
|
6
|
+
@macro = macro
|
|
7
|
+
@name = name.to_sym
|
|
8
|
+
@options = options
|
|
9
|
+
@collection = macro == :serialize_many
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def serialization_class
|
|
13
|
+
case macro
|
|
14
|
+
when :serialize_many
|
|
15
|
+
Serializations::SerializeMany
|
|
16
|
+
when :serialize_one
|
|
17
|
+
Serializations::SerializeOne
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def storage_column
|
|
22
|
+
name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def counter_cache_column
|
|
26
|
+
unless options[:counter_cache] == false
|
|
27
|
+
"#{name}_count"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def klass
|
|
32
|
+
@klass ||= (options[:class_name] || name.to_s.classify).constantize
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def collection?
|
|
36
|
+
@collection
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module AxleAttributes
|
|
2
|
+
module Serializations
|
|
3
|
+
class Serialization
|
|
4
|
+
attr_accessor :owner, :reflection
|
|
5
|
+
def initialize(owner, reflection)
|
|
6
|
+
@owner = owner
|
|
7
|
+
@reflection = reflection
|
|
8
|
+
@collection = nil
|
|
9
|
+
@loaded = false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
reflection.name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def writer(value)
|
|
17
|
+
raise "writer must be implemented"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reader
|
|
21
|
+
target
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reader_was
|
|
25
|
+
if owner.changed_attributes.include?(name.to_s)
|
|
26
|
+
instantiate_target_with_self owner.changed_attributes[name.to_s]
|
|
27
|
+
else
|
|
28
|
+
target
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def loaded?
|
|
33
|
+
@loaded
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
def target=(value)
|
|
39
|
+
@target = value
|
|
40
|
+
target_with_serialization_instance(@target)
|
|
41
|
+
loaded!
|
|
42
|
+
owner[reflection.storage_column] = mutate_json_times!(value.as_json)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def mutate_json_times!(json)
|
|
46
|
+
Array.wrap(json).each do |attrs|
|
|
47
|
+
attrs.each do |k, v|
|
|
48
|
+
if v.acts_like?(:time) || v.acts_like?(:date)
|
|
49
|
+
attrs[k] = v.as_json
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
json
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def target
|
|
58
|
+
unless loaded?
|
|
59
|
+
@target = load_target
|
|
60
|
+
loaded!
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
@target
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def loaded!
|
|
67
|
+
@loaded = true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_target
|
|
71
|
+
instantiate_target_with_self owner[reflection.storage_column]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def instantiate_target_with_self(json)
|
|
75
|
+
target_with_serialization_instance instantiate_target(json)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def target_with_serialization_instance(target)
|
|
79
|
+
Array.wrap(target).each { |record| record.serialization_instance = self }
|
|
80
|
+
target
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def instantiate_target(json)
|
|
84
|
+
raise "instantiate_target must be implemented"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module AxleAttributes
|
|
2
|
+
module Serializations
|
|
3
|
+
class SerializeMany < Serialization
|
|
4
|
+
def writer(records)
|
|
5
|
+
return if records.nil? && owner[reflection.storage_column].nil?
|
|
6
|
+
|
|
7
|
+
records ||= []
|
|
8
|
+
records = records.map { |record| record.is_a?(Hash) ? reflection.klass.new(record) : record }
|
|
9
|
+
|
|
10
|
+
replace(records)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def replace(records)
|
|
14
|
+
delete(target - records)
|
|
15
|
+
concat(records)
|
|
16
|
+
|
|
17
|
+
target_changed!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def delete(records)
|
|
21
|
+
records.each { |record| delete_from_collection(record) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def concat(records)
|
|
25
|
+
records.each { |record| add_to_collection(record) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def attributes_writer(attributes_collection)
|
|
29
|
+
return if attributes_collection.empty?
|
|
30
|
+
|
|
31
|
+
if attributes_collection.is_a? Hash
|
|
32
|
+
attributes_collection = attributes_collection.values
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attributes_collection.each do |attributes|
|
|
36
|
+
attributes = attributes.with_indifferent_access
|
|
37
|
+
|
|
38
|
+
if attributes['_destroy'] == '1'
|
|
39
|
+
target.delete_if { |record| record.id == attributes['id'] }
|
|
40
|
+
else
|
|
41
|
+
if existing = target.detect { |record| record.id == attributes['id'] }
|
|
42
|
+
existing.attributes = attributes
|
|
43
|
+
else
|
|
44
|
+
target << reflection.klass.new(attributes)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
target_changed!
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def target_changed!
|
|
55
|
+
self.target = target
|
|
56
|
+
|
|
57
|
+
if reflection.counter_cache_column
|
|
58
|
+
owner.send("#{reflection.counter_cache_column}=", target.size)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def instantiate_target(json)
|
|
63
|
+
json.nil? ? [] : json.map { |entry| reflection.klass.instantiate(entry['id'], entry) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def delete_from_collection(record)
|
|
67
|
+
target.delete(record)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def add_to_collection(record)
|
|
71
|
+
if index = target.index(record)
|
|
72
|
+
current = target[index]
|
|
73
|
+
new_attributes = record.attributes.update('id' => current.id, 'created_at' => current.created_at)
|
|
74
|
+
|
|
75
|
+
current.attributes.each_key do |key|
|
|
76
|
+
unless new_attributes.key?(key)
|
|
77
|
+
current.send("#{key}=", reflection.klass.attributes[key].default)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
current.attributes = new_attributes
|
|
82
|
+
else
|
|
83
|
+
target << record
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module AxleAttributes
|
|
2
|
+
module Serializations
|
|
3
|
+
class SerializeOne < Serialization
|
|
4
|
+
def writer(record_or_hash)
|
|
5
|
+
if record_or_hash.is_a?(Hash)
|
|
6
|
+
if target
|
|
7
|
+
target.attributes = record_or_hash
|
|
8
|
+
record = target
|
|
9
|
+
else
|
|
10
|
+
record = reflection.klass.new(record_or_hash)
|
|
11
|
+
end
|
|
12
|
+
else
|
|
13
|
+
record = record_or_hash
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
self.target = record
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def attributes_writer(attributes)
|
|
20
|
+
attributes = attributes.with_indifferent_access
|
|
21
|
+
writer(attributes['_destroy'] == '1' ? nil : attributes)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def instantiate_target(json)
|
|
27
|
+
if json
|
|
28
|
+
reflection.klass.instantiate json['id'], json
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
|
|
3
|
+
module AxleAttributes
|
|
4
|
+
module SerializedChild
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
include SuperstoreOverrides
|
|
9
|
+
alias :eql? :==
|
|
10
|
+
|
|
11
|
+
include AxleAttributes::Versioned
|
|
12
|
+
include AxleAttributes::HasAttributes
|
|
13
|
+
self.has_attribute_definition_class = '::AxleAttributes::ChildDefinition'
|
|
14
|
+
has_attribute :id, type: :string, index: true, editable: false, version: true
|
|
15
|
+
has_attribute :created_at, type: :time, index: true, editable: false
|
|
16
|
+
|
|
17
|
+
attr_accessor :serialization_instance
|
|
18
|
+
|
|
19
|
+
include AxleAttributes::Provided
|
|
20
|
+
include AxleAttributes::Validations
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module SuperstoreOverrides
|
|
24
|
+
def initialize(*args)
|
|
25
|
+
super
|
|
26
|
+
@attributes['created_at'] ||= Time.current
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def attributes=(params)
|
|
30
|
+
params.each do |attr, value|
|
|
31
|
+
self.public_send("#{attr}=", value) if respond_to?("#{attr}=")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def ==(other)
|
|
36
|
+
super || hash == other.hash
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def attributes
|
|
40
|
+
result = super
|
|
41
|
+
result.select! { |attr, value| !value.nil? && self.class.attributes[attr] }
|
|
42
|
+
result
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def hash
|
|
47
|
+
id.hash
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def versioned_attributes
|
|
51
|
+
attributes.keep_if { |key, value| self.class.attributes[key].versioned? }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def as_search
|
|
55
|
+
attributes.keep_if { |key, value| self.class.attributes[key].indexed? }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module AxleAttributes
|
|
2
|
+
module Validations
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
validate :validate_formats
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def validate_formats
|
|
12
|
+
each_provided_definition do |definition|
|
|
13
|
+
value = send(definition.name)
|
|
14
|
+
if value.present? && error = definition.validate(value)
|
|
15
|
+
errors.add(definition.name, error)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def each_provided_definition
|
|
21
|
+
provided_set.each do |attr|
|
|
22
|
+
if definition = self.class.attributes[attr]
|
|
23
|
+
yield definition
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module AxleAttributes
|
|
2
|
+
module Versioned
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
class_attribute :versioned_attributes, instance_accessor: false
|
|
7
|
+
self.versioned_attributes = Set.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def inherited(child)
|
|
12
|
+
child.versioned_attributes = versioned_attributes.dup
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_versioned_attribute(*attributes)
|
|
17
|
+
attributes.each { |attribute| versioned_attributes << attribute.to_s }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/test/dummy/Rakefile
ADDED
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
|
2
|
+
// listed below.
|
|
3
|
+
//
|
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
|
6
|
+
//
|
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
|
8
|
+
// compiled file.
|
|
9
|
+
//
|
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
|
11
|
+
// about supported directives.
|
|
12
|
+
//
|
|
13
|
+
//= require_tree .
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
|
10
|
+
*
|
|
11
|
+
*= require_self
|
|
12
|
+
*= require_tree .
|
|
13
|
+
*/
|