axle_attributes 1.13.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
*/
|