all_futures 1.0.3 → 2.0.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 +4 -4
- data/Gemfile +4 -1
- data/Gemfile.lock +272 -61
- data/LICENSE.txt +1 -1
- data/README.md +59 -79
- data/Rakefile +13 -1
- data/SUMMARY.md +22 -2
- data/bin/console +8 -0
- data/bin/loc +0 -0
- data/bin/setup +0 -0
- data/bin/standardize +0 -0
- data/lib/all_futures/association.rb +42 -0
- data/lib/all_futures/association_index.rb +69 -0
- data/lib/all_futures/attributes.rb +28 -0
- data/lib/all_futures/base.rb +66 -0
- data/lib/all_futures/callbacks.rb +71 -0
- data/lib/all_futures/configuration.rb +9 -0
- data/lib/all_futures/dirty.rb +116 -0
- data/lib/all_futures/embeds.rb +40 -0
- data/lib/all_futures/errors.rb +37 -0
- data/lib/all_futures/finder.rb +214 -0
- data/lib/all_futures/locale/en.yml +49 -0
- data/lib/all_futures/persistence.rb +595 -0
- data/lib/all_futures/presenter.rb +35 -0
- data/lib/all_futures/railtie.rb +26 -0
- data/lib/all_futures/translation.rb +22 -0
- data/lib/all_futures/validations.rb +10 -0
- data/lib/all_futures/version.rb +3 -1
- data/lib/all_futures/versions.rb +80 -0
- data/lib/all_futures.rb +24 -1
- data/lib/vendor/ACTIVE_ENTITY_MIT-LICENSE +42 -0
- data/lib/vendor/README.md +8 -0
- data/lib/vendor/active_entity/aggregations.rb +277 -0
- data/lib/vendor/active_entity/associations/embeds/association.rb +134 -0
- data/lib/vendor/active_entity/associations/embeds/builder/association.rb +105 -0
- data/lib/vendor/active_entity/associations/embeds/builder/collection_association.rb +71 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embedded_in.rb +38 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embeds_many.rb +13 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embeds_one.rb +16 -0
- data/lib/vendor/active_entity/associations/embeds/builder/singular_association.rb +28 -0
- data/lib/vendor/active_entity/associations/embeds/collection_association.rb +209 -0
- data/lib/vendor/active_entity/associations/embeds/collection_proxy.rb +310 -0
- data/lib/vendor/active_entity/associations/embeds/embedded_in_association.rb +31 -0
- data/lib/vendor/active_entity/associations/embeds/embeds_many_association.rb +15 -0
- data/lib/vendor/active_entity/associations/embeds/embeds_one_association.rb +26 -0
- data/lib/vendor/active_entity/associations/embeds/singular_association.rb +35 -0
- data/lib/vendor/active_entity/associations.rb +168 -0
- data/lib/vendor/active_entity/attribute_assignment.rb +87 -0
- data/lib/vendor/active_entity/attribute_decorators.rb +90 -0
- data/lib/vendor/active_entity/attribute_methods/before_type_cast.rb +78 -0
- data/lib/vendor/active_entity/attribute_methods/dirty.rb +180 -0
- data/lib/vendor/active_entity/attribute_methods/primary_key.rb +96 -0
- data/lib/vendor/active_entity/attribute_methods/query.rb +38 -0
- data/lib/vendor/active_entity/attribute_methods/read.rb +44 -0
- data/lib/vendor/active_entity/attribute_methods/serialization.rb +90 -0
- data/lib/vendor/active_entity/attribute_methods/time_zone_conversion.rb +93 -0
- data/lib/vendor/active_entity/attribute_methods/write.rb +54 -0
- data/lib/vendor/active_entity/attribute_methods.rb +833 -0
- data/lib/vendor/active_entity/attributes.rb +240 -0
- data/lib/vendor/active_entity/base.rb +293 -0
- data/lib/vendor/active_entity/callbacks.rb +21 -0
- data/lib/vendor/active_entity/coders/json.rb +15 -0
- data/lib/vendor/active_entity/coders/yaml_column.rb +50 -0
- data/lib/vendor/active_entity/core.rb +348 -0
- data/lib/vendor/active_entity/enum.rb +260 -0
- data/lib/vendor/active_entity/errors.rb +69 -0
- data/lib/vendor/active_entity/gem_version.rb +17 -0
- data/lib/vendor/active_entity/inheritance.rb +176 -0
- data/lib/vendor/active_entity/integration.rb +78 -0
- data/lib/vendor/active_entity/locale/en.yml +45 -0
- data/lib/vendor/active_entity/model_schema.rb +118 -0
- data/lib/vendor/active_entity/nested_attributes.rb +554 -0
- data/lib/vendor/active_entity/persistence.rb +20 -0
- data/lib/vendor/active_entity/railtie.rb +76 -0
- data/lib/vendor/active_entity/readonly_attributes.rb +55 -0
- data/lib/vendor/active_entity/reflection.rb +447 -0
- data/lib/vendor/active_entity/serialization.rb +28 -0
- data/lib/vendor/active_entity/store.rb +291 -0
- data/lib/vendor/active_entity/translation.rb +24 -0
- data/lib/vendor/active_entity/type/date.rb +9 -0
- data/lib/vendor/active_entity/type/date_time.rb +9 -0
- data/lib/vendor/active_entity/type/internal/timezone.rb +17 -0
- data/lib/vendor/active_entity/type/json.rb +30 -0
- data/lib/vendor/active_entity/type/modifiers/array.rb +72 -0
- data/lib/vendor/active_entity/type/modifiers/array_without_blank.rb +19 -0
- data/lib/vendor/active_entity/type/registry.rb +112 -0
- data/lib/vendor/active_entity/type/serialized.rb +71 -0
- data/lib/vendor/active_entity/type/text.rb +11 -0
- data/lib/vendor/active_entity/type/time.rb +21 -0
- data/lib/vendor/active_entity/type/unsigned_integer.rb +17 -0
- data/lib/vendor/active_entity/type.rb +75 -0
- data/lib/vendor/active_entity/validate_embeds_association.rb +300 -0
- data/lib/vendor/active_entity/validations/absence.rb +25 -0
- data/lib/vendor/active_entity/validations/associated.rb +60 -0
- data/lib/vendor/active_entity/validations/length.rb +26 -0
- data/lib/vendor/active_entity/validations/presence.rb +68 -0
- data/lib/vendor/active_entity/validations/subset.rb +76 -0
- data/lib/vendor/active_entity/validations/uniqueness_in_embeds.rb +91 -0
- data/lib/vendor/active_entity/validations/uniqueness_on_active_record.rb +234 -0
- data/lib/vendor/active_entity/validations.rb +47 -0
- data/lib/vendor/active_entity/version.rb +10 -0
- data/lib/vendor/active_entity.rb +99 -0
- data/lib/vendor/core_ext/array_without_blank.rb +48 -0
- metadata +175 -21
- data/lib/all_futures/all_futures.rb +0 -28
- data/lib/all_futures/config.rb +0 -14
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveEntity
|
|
4
|
+
module Associations
|
|
5
|
+
module Embeds
|
|
6
|
+
# = Active Entity Belongs To Association
|
|
7
|
+
class EmbeddedInAssociation < SingularAssociation #:nodoc:
|
|
8
|
+
def default(&block)
|
|
9
|
+
writer(owner.instance_exec(&block)) if reader.nil?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def replace(record)
|
|
15
|
+
if record
|
|
16
|
+
raise_on_type_mismatch!(record)
|
|
17
|
+
set_inverse_instance(record)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
self.target = record
|
|
21
|
+
end
|
|
22
|
+
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
|
23
|
+
# has_one associations.
|
|
24
|
+
def invertible_for?(record)
|
|
25
|
+
inverse = inverse_reflection_for(record)
|
|
26
|
+
inverse&.embeds_one?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveEntity
|
|
4
|
+
module Associations
|
|
5
|
+
module Embeds
|
|
6
|
+
# = Active Entity Has Many Association
|
|
7
|
+
# This is the proxy that handles a has many association.
|
|
8
|
+
#
|
|
9
|
+
# If the association has a <tt>:through</tt> option further specialization
|
|
10
|
+
# is provided by its child HasManyThroughAssociation.
|
|
11
|
+
class EmbedsManyAssociation < CollectionAssociation #:nodoc:
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveEntity
|
|
4
|
+
module Associations
|
|
5
|
+
module Embeds
|
|
6
|
+
# = Active Entity Has One Association
|
|
7
|
+
class EmbedsOneAssociation < SingularAssociation #:nodoc:
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def replace(record)
|
|
11
|
+
self.target =
|
|
12
|
+
if record.is_a? reflection.klass
|
|
13
|
+
record
|
|
14
|
+
elsif record.nil?
|
|
15
|
+
nil
|
|
16
|
+
elsif record.respond_to?(:to_h)
|
|
17
|
+
build_record(record.to_h)
|
|
18
|
+
end
|
|
19
|
+
rescue => ex
|
|
20
|
+
raise_on_type_mismatch!(record)
|
|
21
|
+
raise ex
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveEntity
|
|
4
|
+
module Associations
|
|
5
|
+
module Embeds
|
|
6
|
+
class SingularAssociation < Association #:nodoc:
|
|
7
|
+
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
|
8
|
+
def reader
|
|
9
|
+
target
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
|
|
13
|
+
def writer(record)
|
|
14
|
+
replace(record)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build(attributes = {}, &block)
|
|
18
|
+
record = build_record(attributes, &block)
|
|
19
|
+
set_new_record(record)
|
|
20
|
+
record
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def replace(_record)
|
|
26
|
+
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def set_new_record(record)
|
|
30
|
+
replace(record)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
|
4
|
+
require "active_support/core_ext/string/conversions"
|
|
5
|
+
|
|
6
|
+
module ActiveEntity
|
|
7
|
+
class AssociationNotFoundError < ConfigurationError #:nodoc:
|
|
8
|
+
def initialize(record = nil, association_name = nil)
|
|
9
|
+
if record && association_name
|
|
10
|
+
super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
|
|
11
|
+
else
|
|
12
|
+
super("Association was not found.")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class InverseOfAssociationNotFoundError < ActiveEntityError #:nodoc:
|
|
18
|
+
def initialize(reflection = nil, associated_class = nil)
|
|
19
|
+
if reflection
|
|
20
|
+
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
|
|
21
|
+
else
|
|
22
|
+
super("Could not find the inverse association.")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# See ActiveEntity::Associations::ClassMethods for documentation.
|
|
28
|
+
module Associations # :nodoc:
|
|
29
|
+
extend ActiveSupport::Autoload
|
|
30
|
+
extend ActiveSupport::Concern
|
|
31
|
+
# These classes will be loaded when associations are created.
|
|
32
|
+
# So there is no need to eager load them.
|
|
33
|
+
module Embeds
|
|
34
|
+
extend ActiveSupport::Autoload
|
|
35
|
+
|
|
36
|
+
autoload :Association, "active_entity/associations/embeds/association"
|
|
37
|
+
autoload :SingularAssociation, "active_entity/associations/embeds/singular_association"
|
|
38
|
+
autoload :CollectionAssociation, "active_entity/associations/embeds/collection_association"
|
|
39
|
+
autoload :CollectionProxy, "active_entity/associations/embeds/collection_proxy"
|
|
40
|
+
|
|
41
|
+
module Builder #:nodoc:
|
|
42
|
+
autoload :Association, "active_entity/associations/embeds/builder/association"
|
|
43
|
+
autoload :SingularAssociation, "active_entity/associations/embeds/builder/singular_association"
|
|
44
|
+
autoload :CollectionAssociation, "active_entity/associations/embeds/builder/collection_association"
|
|
45
|
+
|
|
46
|
+
autoload :EmbeddedIn, "active_entity/associations/embeds/builder/embedded_in"
|
|
47
|
+
autoload :EmbedsOne, "active_entity/associations/embeds/builder/embeds_one"
|
|
48
|
+
autoload :EmbedsMany, "active_entity/associations/embeds/builder/embeds_many"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
eager_autoload do
|
|
52
|
+
autoload :EmbeddedInAssociation
|
|
53
|
+
autoload :EmbedsOneAssociation
|
|
54
|
+
autoload :EmbedsManyAssociation
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.eager_load!
|
|
59
|
+
super
|
|
60
|
+
Embeds.eager_load!
|
|
61
|
+
end
|
|
62
|
+
# Returns the association instance for the given name, instantiating it if it doesn't already exist
|
|
63
|
+
def association(name) #:nodoc:
|
|
64
|
+
association = association_instance_get(name)
|
|
65
|
+
|
|
66
|
+
if association.nil?
|
|
67
|
+
unless reflection = self.class._reflect_on_association(name)
|
|
68
|
+
raise AssociationNotFoundError.new(self, name)
|
|
69
|
+
end
|
|
70
|
+
association = reflection.association_class.new(self, reflection)
|
|
71
|
+
association_instance_set(name, association)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
association
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def association_cached?(name) # :nodoc:
|
|
78
|
+
@association_cache.key?(name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def initialize_dup(*) # :nodoc:
|
|
82
|
+
@association_cache = {}
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def init_internals
|
|
89
|
+
@association_cache = {}
|
|
90
|
+
super
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns the specified association instance if it exists, +nil+ otherwise.
|
|
94
|
+
def association_instance_get(name)
|
|
95
|
+
@association_cache[name]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Set the specified association instance.
|
|
99
|
+
def association_instance_set(name, association)
|
|
100
|
+
@association_cache[name] = association
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# \Associations are a set of macro-like class methods for tying objects together through
|
|
104
|
+
# foreign keys. They express relationships like "Project has one Project Manager"
|
|
105
|
+
# or "Project belongs to a Portfolio". Each macro adds a number of methods to the
|
|
106
|
+
# class which are specialized according to the collection or association symbol and the
|
|
107
|
+
# options hash. It works much the same way as Ruby's own <tt>attr*</tt>
|
|
108
|
+
# methods.
|
|
109
|
+
#
|
|
110
|
+
# class Project < ActiveEntity::Base
|
|
111
|
+
# belongs_to :portfolio
|
|
112
|
+
# has_one :project_manager
|
|
113
|
+
# has_many :milestones
|
|
114
|
+
# has_and_belongs_to_many :categories
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
117
|
+
# The project class now has the following methods (and more) to ease the traversal and
|
|
118
|
+
# manipulation of its relationships:
|
|
119
|
+
# * <tt>Project#portfolio</tt>, <tt>Project#portfolio=(portfolio)</tt>, <tt>Project#reload_portfolio</tt>
|
|
120
|
+
# * <tt>Project#project_manager</tt>, <tt>Project#project_manager=(project_manager)</tt>, <tt>Project#reload_project_manager</tt>
|
|
121
|
+
# * <tt>Project#milestones.empty?</tt>, <tt>Project#milestones.size</tt>, <tt>Project#milestones</tt>, <tt>Project#milestones<<(milestone)</tt>,
|
|
122
|
+
# <tt>Project#milestones.delete(milestone)</tt>, <tt>Project#milestones.destroy(milestone)</tt>, <tt>Project#milestones.find(milestone_id)</tt>,
|
|
123
|
+
# <tt>Project#milestones.build</tt>, <tt>Project#milestones.create</tt>
|
|
124
|
+
# * <tt>Project#categories.empty?</tt>, <tt>Project#categories.size</tt>, <tt>Project#categories</tt>, <tt>Project#categories<<(category1)</tt>,
|
|
125
|
+
# <tt>Project#categories.delete(category1)</tt>, <tt>Project#categories.destroy(category1)</tt>
|
|
126
|
+
#
|
|
127
|
+
# === A word of warning
|
|
128
|
+
#
|
|
129
|
+
# Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveEntity::Core] of
|
|
130
|
+
# <tt>ActiveEntity::Base</tt>. Since the association adds a method with that name to
|
|
131
|
+
# its model, using an association with the same name as one provided by <tt>ActiveEntity::Base</tt> will override the method inherited through <tt>ActiveEntity::Base</tt> and will break things.
|
|
132
|
+
# For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveEntity::Base</tt> instance methods.
|
|
133
|
+
module ClassMethods
|
|
134
|
+
def embedded_in(name, **options)
|
|
135
|
+
reflection = Embeds::Builder::EmbeddedIn.build(self, name, options)
|
|
136
|
+
Reflection.add_reflection self, name, reflection
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def embeds_one(name, **options)
|
|
140
|
+
reflection = Embeds::Builder::EmbedsOne.build(self, name, options)
|
|
141
|
+
Reflection.add_reflection self, name, reflection
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def embeds_many(name, **options)
|
|
145
|
+
reflection = Embeds::Builder::EmbedsMany.build(self, name, options)
|
|
146
|
+
Reflection.add_reflection self, name, reflection
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def association_names
|
|
150
|
+
@association_names ||=
|
|
151
|
+
if !abstract_class?
|
|
152
|
+
reflections.keys.map(&:to_sym)
|
|
153
|
+
else
|
|
154
|
+
[]
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def embeds_association_names
|
|
159
|
+
@association_names ||=
|
|
160
|
+
if !abstract_class?
|
|
161
|
+
reflections.select { |_, r| r.embedded? }.keys.map(&:to_sym)
|
|
162
|
+
else
|
|
163
|
+
[]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_model/forbidden_attributes_protection"
|
|
4
|
+
|
|
5
|
+
module ActiveEntity
|
|
6
|
+
module AttributeAssignment
|
|
7
|
+
include ActiveModel::AttributeAssignment
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def _assign_attributes(attributes)
|
|
12
|
+
multi_parameter_attributes = nested_parameter_attributes = nil
|
|
13
|
+
|
|
14
|
+
attributes.each do |k, v|
|
|
15
|
+
key = k.to_s
|
|
16
|
+
|
|
17
|
+
if key.include?("(")
|
|
18
|
+
(multi_parameter_attributes ||= {})[key] = v
|
|
19
|
+
elsif v.is_a?(Hash)
|
|
20
|
+
(nested_parameter_attributes ||= {})[key] = v
|
|
21
|
+
else
|
|
22
|
+
_assign_attribute(key, v)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
|
|
27
|
+
assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
|
31
|
+
def assign_nested_parameter_attributes(pairs)
|
|
32
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
|
36
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
|
37
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
|
38
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
|
39
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
|
40
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
|
41
|
+
def assign_multiparameter_attributes(pairs)
|
|
42
|
+
execute_callstack_for_multiparameter_attributes(
|
|
43
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
|
48
|
+
errors = []
|
|
49
|
+
callstack.each do |name, values_with_empty_parameters|
|
|
50
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
|
51
|
+
values = nil
|
|
52
|
+
else
|
|
53
|
+
values = values_with_empty_parameters
|
|
54
|
+
end
|
|
55
|
+
send("#{name}=", values)
|
|
56
|
+
rescue => ex
|
|
57
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
|
58
|
+
end
|
|
59
|
+
unless errors.empty?
|
|
60
|
+
error_descriptions = errors.map(&:message).join(",")
|
|
61
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
|
66
|
+
attributes = {}
|
|
67
|
+
|
|
68
|
+
pairs.each do |(multiparameter_name, value)|
|
|
69
|
+
attribute_name = multiparameter_name.split("(").first
|
|
70
|
+
attributes[attribute_name] ||= {}
|
|
71
|
+
|
|
72
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
|
73
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
attributes
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
|
80
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_parameter_position(multiparameter_name)
|
|
84
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveEntity
|
|
4
|
+
module AttributeDecorators # :nodoc:
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
class_attribute :attribute_type_decorations, instance_accessor: false, default: TypeDecorator.new # :internal:
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods # :nodoc:
|
|
12
|
+
# This method is an internal API used to create class macros such as
|
|
13
|
+
# +serialize+, and features like time zone aware attributes.
|
|
14
|
+
#
|
|
15
|
+
# Used to wrap the type of an attribute in a new type.
|
|
16
|
+
# When the schema for a model is loaded, attributes with the same name as
|
|
17
|
+
# +column_name+ will have their type yielded to the given block. The
|
|
18
|
+
# return value of that block will be used instead.
|
|
19
|
+
#
|
|
20
|
+
# Subsequent calls where +column_name+ and +decorator_name+ are the same
|
|
21
|
+
# will override the previous decorator, not decorate twice. This can be
|
|
22
|
+
# used to create idempotent class macros like +serialize+
|
|
23
|
+
def decorate_attribute_type(column_name, decorator_name, &block)
|
|
24
|
+
matcher = ->(name, _) { name == column_name.to_s }
|
|
25
|
+
key = "_#{column_name}_#{decorator_name}"
|
|
26
|
+
decorate_matching_attribute_types(matcher, key, &block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# This method is an internal API used to create higher level features like
|
|
30
|
+
# time zone aware attributes.
|
|
31
|
+
#
|
|
32
|
+
# When the schema for a model is loaded, +matcher+ will be called for each
|
|
33
|
+
# attribute with its name and type. If the matcher returns a truthy value,
|
|
34
|
+
# the type will then be yielded to the given block, and the return value
|
|
35
|
+
# of that block will replace the type.
|
|
36
|
+
#
|
|
37
|
+
# Subsequent calls to this method with the same value for +decorator_name+
|
|
38
|
+
# will replace the previous decorator, not decorate twice. This can be
|
|
39
|
+
# used to ensure that class macros are idempotent.
|
|
40
|
+
def decorate_matching_attribute_types(matcher, decorator_name, &block)
|
|
41
|
+
reload_schema_from_cache
|
|
42
|
+
decorator_name = decorator_name.to_s
|
|
43
|
+
|
|
44
|
+
# Create new hashes so we don't modify parent classes
|
|
45
|
+
self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def load_schema!
|
|
51
|
+
super
|
|
52
|
+
attribute_types.each do |name, type|
|
|
53
|
+
decorated_type = attribute_type_decorations.apply(name, type)
|
|
54
|
+
define_attribute(name, decorated_type)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class TypeDecorator # :nodoc:
|
|
60
|
+
delegate :clear, to: :@decorations
|
|
61
|
+
|
|
62
|
+
def initialize(decorations = {})
|
|
63
|
+
@decorations = decorations
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def merge(*args)
|
|
67
|
+
TypeDecorator.new(@decorations.merge(*args))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def apply(name, type)
|
|
71
|
+
decorations = decorators_for(name, type)
|
|
72
|
+
decorations.inject(type) do |new_type, block|
|
|
73
|
+
block.call(new_type)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def decorators_for(name, type)
|
|
80
|
+
matching(name, type).map(&:last)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def matching(name, type)
|
|
84
|
+
@decorations.values.select do |(matcher, _)|
|
|
85
|
+
matcher.call(name, type)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveEntity
|
|
4
|
+
module AttributeMethods
|
|
5
|
+
# = Active Entity Attribute Methods Before Type Cast
|
|
6
|
+
#
|
|
7
|
+
# ActiveEntity::AttributeMethods::BeforeTypeCast provides a way to
|
|
8
|
+
# read the value of the attributes before typecasting and deserialization.
|
|
9
|
+
#
|
|
10
|
+
# class Task < ActiveEntity::Base
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# task = Task.new(id: '1', completed_on: '2012-10-21')
|
|
14
|
+
# task.id # => 1
|
|
15
|
+
# task.completed_on # => Sun, 21 Oct 2012
|
|
16
|
+
#
|
|
17
|
+
# task.attributes_before_type_cast
|
|
18
|
+
# # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
|
|
19
|
+
# task.read_attribute_before_type_cast('id') # => "1"
|
|
20
|
+
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
|
21
|
+
#
|
|
22
|
+
# In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
|
|
23
|
+
# it declares a method for all attributes with the <tt>*_before_type_cast</tt>
|
|
24
|
+
# suffix.
|
|
25
|
+
#
|
|
26
|
+
# task.id_before_type_cast # => "1"
|
|
27
|
+
# task.completed_on_before_type_cast # => "2012-10-21"
|
|
28
|
+
module BeforeTypeCast
|
|
29
|
+
extend ActiveSupport::Concern
|
|
30
|
+
|
|
31
|
+
included do
|
|
32
|
+
attribute_method_suffix "_before_type_cast"
|
|
33
|
+
attribute_method_suffix "_came_from_user?"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the value of the attribute identified by +attr_name+ before
|
|
37
|
+
# typecasting and deserialization.
|
|
38
|
+
#
|
|
39
|
+
# class Task < ActiveEntity::Base
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# task = Task.new(id: '1', completed_on: '2012-10-21')
|
|
43
|
+
# task.read_attribute('id') # => 1
|
|
44
|
+
# task.read_attribute_before_type_cast('id') # => '1'
|
|
45
|
+
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
|
|
46
|
+
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
|
47
|
+
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
|
|
48
|
+
def read_attribute_before_type_cast(attr_name)
|
|
49
|
+
attribute_before_type_cast(attr_name.to_s)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns a hash of attributes before typecasting and deserialization.
|
|
53
|
+
#
|
|
54
|
+
# class Task < ActiveEntity::Base
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
|
|
58
|
+
# task.attributes
|
|
59
|
+
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
|
|
60
|
+
# task.attributes_before_type_cast
|
|
61
|
+
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
|
|
62
|
+
def attributes_before_type_cast
|
|
63
|
+
@attributes.values_before_type_cast
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
|
|
69
|
+
def attribute_before_type_cast(attr_name)
|
|
70
|
+
@attributes[attr_name].value_before_type_cast
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def attribute_came_from_user?(attr_name)
|
|
74
|
+
@attributes[attr_name].came_from_user?
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|