isomorphic 0.1.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/.gitignore +53 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +32 -0
- data/README.md +228 -0
- data/Rakefile +2 -0
- data/WARRANTY.txt +22 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/isomorphic.gemspec +38 -0
- data/lib/isomorphic.rb +13 -0
- data/lib/isomorphic/errors.rb +20 -0
- data/lib/isomorphic/factory.rb +173 -0
- data/lib/isomorphic/hash_with_indifferent_access.rb +62 -0
- data/lib/isomorphic/inflector.rb +180 -0
- data/lib/isomorphic/lens.rb +433 -0
- data/lib/isomorphic/memoization.rb +135 -0
- data/lib/isomorphic/model.rb +83 -0
- data/lib/isomorphic/node.rb +1211 -0
- data/lib/isomorphic/version.rb +3 -0
- metadata +105 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
require "active_support/core_ext/object/try"
|
3
|
+
require "active_support/hash_with_indifferent_access"
|
4
|
+
|
5
|
+
require "isomorphic/hash_with_indifferent_access"
|
6
|
+
|
7
|
+
module Isomorphic
|
8
|
+
# Included when the base class can memo-cache the results of getters and setters of Isomorphic lenses.
|
9
|
+
module Memoization
|
10
|
+
extend ::ActiveSupport::Concern
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
# @!scope class
|
14
|
+
|
15
|
+
# Defines finder methods and instance variables for the Active Record associations given by name.
|
16
|
+
#
|
17
|
+
# @param inflector [Isomorphic::Inflector::AbstractInflector] the Isomorphic inflector
|
18
|
+
# @param terms [Array<Object>] the inflectable terms
|
19
|
+
# @param association_names [Array<#to_s>] the association names
|
20
|
+
# @param options [Hash<Symbol, Object>] the options
|
21
|
+
# @option options [Array<#to_s>] :xmlattrs ([]) the XML attribute names
|
22
|
+
# @return [void]
|
23
|
+
# @raise [Isomorphic::InflectorError] if an inflectable term is invalid
|
24
|
+
def memo_isomorphism_for(inflector, terms, *association_names, **options)
|
25
|
+
method_name = inflector.isomorphism(terms)
|
26
|
+
|
27
|
+
options[:xmlattrs].try(:each) do |xmlattr_name|
|
28
|
+
association_names.each do |association_name|
|
29
|
+
memo_isomorphism_method_names_by_association_name_for(inflector.base)[association_name] ||= []
|
30
|
+
memo_isomorphism_method_names_by_association_name_for(inflector.base)[association_name] << method_name
|
31
|
+
|
32
|
+
# @!scope instance
|
33
|
+
|
34
|
+
# @!attribute [r] xmlattr_#{xmlattr_name}_for_#{method_name}_by_#{association_name}
|
35
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] the cache of XML attributes
|
36
|
+
attribute_name = :"@xmlattr_#{xmlattr_name}_for_#{method_name}_by_#{association_name}"
|
37
|
+
|
38
|
+
# @!attribute [r] #{method_name}_by_xmlattr_#{xmlattr_name}
|
39
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] the cache of instances
|
40
|
+
isomorphism_attribute_name = :"@#{method_name}_by_xmlattr_#{xmlattr_name}"
|
41
|
+
|
42
|
+
# @!method find_or_create_#{method_name}_for_#{association_name}!(record, *args, &block)
|
43
|
+
# Find or create the instance for the given Active Record record.
|
44
|
+
#
|
45
|
+
# @param record [ActiveRecord::Base] the record
|
46
|
+
# @param args [Array<Object>] the arguments for the isomorphism
|
47
|
+
# @yieldparam [Object] the instance
|
48
|
+
# @yieldreturn [void]
|
49
|
+
# @return [Object] the instance
|
50
|
+
define_method(:"find_or_create_#{method_name}_for_#{association_name}!") do |record, *args, &block|
|
51
|
+
instance = (instance_variable_get(attribute_name) || instance_variable_set(attribute_name, ::ActiveSupport::HashWithIndifferentAccess.new)).try { |hash| hash[record] }.try { |xmlattr_for_record|
|
52
|
+
(instance_variable_get(isomorphism_attribute_name) || instance_variable_set(isomorphism_attribute_name, ::ActiveSupport::HashWithIndifferentAccess.new)).try { |hash| hash[xmlattr_for_record] }
|
53
|
+
}
|
54
|
+
|
55
|
+
if instance.nil?
|
56
|
+
instance = record.send(:"to_#{method_name}", *args)
|
57
|
+
|
58
|
+
unless instance.nil?
|
59
|
+
if instance.respond_to?(:"xmlattr_#{xmlattr_name}")
|
60
|
+
xmlattr_for_record = instance.send(:"xmlattr_#{xmlattr_name}")
|
61
|
+
|
62
|
+
(instance_variable_get(attribute_name) || instance_variable_set(attribute_name, ::ActiveSupport::HashWithIndifferentAccess.new)).try { |hash|
|
63
|
+
hash[record] = xmlattr_for_record
|
64
|
+
}
|
65
|
+
|
66
|
+
(instance_variable_get(isomorphism_attribute_name) || instance_variable_set(isomorphism_attribute_name, ::ActiveSupport::HashWithIndifferentAccess.new)).try { |hash|
|
67
|
+
hash[xmlattr_for_record] = instance
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
unless block.nil?
|
72
|
+
case block.arity
|
73
|
+
when 1 then block.call(instance)
|
74
|
+
else instance.instance_eval(&block)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
instance
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the memo-cahe.
|
89
|
+
#
|
90
|
+
# @param base [Module] the base module
|
91
|
+
# @return [Hash<Module, ActiveSupport::HashWithIndifferentAccess>] the memo-cache
|
92
|
+
def memo_isomorphism_method_names_by_association_name_for(base)
|
93
|
+
unless class_variable_defined?(:"@@memo_isomorphism_method_names_by_association_name_for")
|
94
|
+
class_variable_set(:"@@memo_isomorphism_method_names_by_association_name_for", {})
|
95
|
+
end
|
96
|
+
|
97
|
+
class_variable_get(:"@@memo_isomorphism_method_names_by_association_name_for")[base] ||= ::ActiveSupport::HashWithIndifferentAccess.new
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# @!scope instance
|
102
|
+
|
103
|
+
# Find all memoized instances for the given Active Record record by XML attribute name.
|
104
|
+
#
|
105
|
+
# @param inflector [Isomorphic::Inflector::AbstractInflector] the Isomorphic inflector
|
106
|
+
# @param record [ActiveRecord::Base] the Active Record record
|
107
|
+
# @param options [Hash<Symbol, Object>] the options
|
108
|
+
# @option options [Array<#to_s>] :xmlattrs ([]) the XML attribute names
|
109
|
+
# @return [ActiveSupport::HashWithIndifferentAccess] the memoized instances by XML attribute name
|
110
|
+
# @raise [Isomorphic::InflectorError] if an inflectable term is invalid
|
111
|
+
def find_all_with_memo_isomorphism_for(inflector, record, **options)
|
112
|
+
association_name = record.class.name.underscore.gsub("/", "_").to_sym
|
113
|
+
|
114
|
+
options[:xmlattrs].try(:inject, ::ActiveSupport::HashWithIndifferentAccess.new) { |xmlattr_acc, xmlattr_name|
|
115
|
+
xmlattr_acc[xmlattr_name] = self.class.memo_isomorphism_method_names_by_association_name_for(inflector.base)[association_name].try(:inject, inflector.convert_hash({})) { |acc, method_name|
|
116
|
+
attribute_name = :"@xmlattr_#{xmlattr_name}_for_#{method_name}_by_#{association_name}"
|
117
|
+
|
118
|
+
isomorphism_attribute_name = :"@#{method_name}_by_xmlattr_#{xmlattr_name}"
|
119
|
+
|
120
|
+
acc[method_name] ||= instance_variable_get(attribute_name).try { |xmlattr_for_method_name_by_association_name|
|
121
|
+
xmlattr_for_method_name_by_association_name[record].try { |xmlattr_for_method_name|
|
122
|
+
instance_variable_get(isomorphism_attribute_name).try { |method_name_by_xmlattr|
|
123
|
+
method_name_by_xmlattr[xmlattr_for_method_name]
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
acc
|
129
|
+
}
|
130
|
+
|
131
|
+
xmlattr_acc
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
require "active_support/hash_with_indifferent_access"
|
3
|
+
|
4
|
+
require "isomorphic/node"
|
5
|
+
|
6
|
+
module Isomorphic
|
7
|
+
# Included when the base class can define isomorphisms.
|
8
|
+
module Model
|
9
|
+
extend ::ActiveSupport::Concern
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
# @!scope class
|
13
|
+
|
14
|
+
# Define an isomorphism for the given class and optional alias name.
|
15
|
+
#
|
16
|
+
# @param factory [Isomorphic::Factory::AbstractFactory] the Isomorphic factory
|
17
|
+
# @param inflector [Isomorphic::Inflector::AbstractInflector] the Isomorphic inflector
|
18
|
+
# @param isomorphism_class [Class] the class for the isomorphism
|
19
|
+
# @param method_suffix [#to_s] the optional alias name for the isomorphism
|
20
|
+
# @param options [Hash<Symbol, Object>] the options
|
21
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the root node should always return a non-+nil+ target
|
22
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
23
|
+
# @option options [Boolean] :collection (false) +true+ if the target is a collection
|
24
|
+
# @yieldparam node [Isomorphic::Node::Root] the root node
|
25
|
+
# @yieldreturn [void]
|
26
|
+
# @return [void]
|
27
|
+
# @raise [Isomorphic::InflectorError] if an inflectable term is invalid
|
28
|
+
def isomorphism_for(factory, inflector, isomorphism_class, method_suffix = nil, **options, &block)
|
29
|
+
isomorphism_method_name = inflector.isomorphism([[isomorphism_class, method_suffix]])
|
30
|
+
|
31
|
+
# @!scope instance
|
32
|
+
|
33
|
+
# @!method has_#{isomorphism_method_name}?
|
34
|
+
# Does the base class have an Isomorphic node for this isomorphism?
|
35
|
+
#
|
36
|
+
# @return [Boolean] +true+
|
37
|
+
define_singleton_method(:"has_#{isomorphism_method_name}?") do
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
klass = options[:collection] ? Isomorphic::Node::RootCollection : Isomorphic::Node::RootMember
|
42
|
+
|
43
|
+
# @!method build_isomorphic_node_for_#{isomorphism_method_name}(*args)
|
44
|
+
# Builds an Isomorphic node for this isomorphism.
|
45
|
+
#
|
46
|
+
# @param args [Array<Object>] the arguments for the isomorphism
|
47
|
+
# @return [Isomorphic::Node::AbstractNode] the Isomorphic node
|
48
|
+
define_singleton_method(:"build_isomorphic_node_for_#{isomorphism_method_name}") do |*args|
|
49
|
+
klass.new(factory, inflector, self, isomorphism_class, *args, **options, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!method from_#{isomorphism_method_name}(isomorphism_instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new, *args)
|
53
|
+
# Converts the given instance to an Active Record record.
|
54
|
+
#
|
55
|
+
# @param isomorphism_instance [Object] the instance
|
56
|
+
# @param record [ActiveRecord::Base] the record (optional; when not provided, a new instance of the Active Record class is constructed)
|
57
|
+
# @param xmlattr_acc [ActiveSupport::HashWithIndifferentAccess] the accumulator for XML attributes
|
58
|
+
# @param args [Array<Object>] the arguments for the isomorphism
|
59
|
+
# @return [ActiveRecord::Base] the record
|
60
|
+
define_singleton_method(:"from_#{isomorphism_method_name}") do |isomorphism_instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new, *args|
|
61
|
+
node = send(:"build_isomorphic_node_for_#{isomorphism_method_name}", *args)
|
62
|
+
|
63
|
+
node.from_isomorphism(isomorphism_instance, record, xmlattr_acc)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @!method to_#{isomorphism_method_name}(isomorphism_instance = nil, *args)
|
67
|
+
# Converts the given Active Record record to an instance.
|
68
|
+
#
|
69
|
+
# @param isomorphism_instance [Object] the instance (optional; when not provided, a new instance is constructed)
|
70
|
+
# @return [Object] the instance
|
71
|
+
define_method(:"to_#{isomorphism_method_name}") do |isomorphism_instance = nil, *args|
|
72
|
+
node = self.class.send(:"build_isomorphic_node_for_#{isomorphism_method_name}", *args)
|
73
|
+
|
74
|
+
node.to_isomorphism(self, isomorphism_instance)
|
75
|
+
end
|
76
|
+
|
77
|
+
return
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!scope instance
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,1211 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
require "active_support/core_ext/array/extract_options"
|
3
|
+
require "active_support/core_ext/object/try"
|
4
|
+
require "active_support/hash_with_indifferent_access"
|
5
|
+
|
6
|
+
require "isomorphic/errors"
|
7
|
+
|
8
|
+
require "isomorphic/lens"
|
9
|
+
|
10
|
+
module Isomorphic
|
11
|
+
# Generic base class for Isomorphic node errors.
|
12
|
+
#
|
13
|
+
# @abstract
|
14
|
+
class NodeError < Isomorphic::IsomorphicError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when the +:default+ option is not given in the constructor for a {Isomorphic::Node::Guard} node.
|
18
|
+
class DefaultOptionNotFound < Isomorphic::NodeError
|
19
|
+
end
|
20
|
+
|
21
|
+
# Raised when the +:default+ option is not included in the array of values for a {Isomorphic::Node::Guard} node.
|
22
|
+
class DefaultOptionNotIncluded < Isomorphic::NodeError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Raised when an Isomorphic node cannot find a class.
|
26
|
+
class InvalidNodeClass < Isomorphic::NodeError
|
27
|
+
# @!attribute [r] klass
|
28
|
+
# @return [Class] the class
|
29
|
+
attr_reader :klass
|
30
|
+
|
31
|
+
# Default constructor.
|
32
|
+
#
|
33
|
+
# @param message [#to_s] the message
|
34
|
+
# @param klass [Clsas] the class
|
35
|
+
def initialize(message = nil, klass)
|
36
|
+
super(message)
|
37
|
+
|
38
|
+
@klass = klass
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Raised when an object is not an instance of a class.
|
43
|
+
class InvalidNodeObject < Isomorphic::NodeError
|
44
|
+
# @!attribute [r] klass
|
45
|
+
# @return [Class] the class
|
46
|
+
# @!attribute [r] object
|
47
|
+
# @return [Object] the object
|
48
|
+
attr_reader :klass, :object
|
49
|
+
|
50
|
+
# Default constructor.
|
51
|
+
#
|
52
|
+
# @param message [#to_s] the message
|
53
|
+
# @param klass [Clsas] the class
|
54
|
+
# @param object [Object] the object
|
55
|
+
def initialize(message = nil, klass, object)
|
56
|
+
super(message)
|
57
|
+
|
58
|
+
@klass, @object = klass, object
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module Node
|
63
|
+
module Internal
|
64
|
+
# Included when the base class needs to perform deep equality testing.
|
65
|
+
module DeepEquals
|
66
|
+
extend ::ActiveSupport::Concern
|
67
|
+
|
68
|
+
# @!scope instance
|
69
|
+
|
70
|
+
# Are all attributes deep equal for the given object?
|
71
|
+
#
|
72
|
+
# @param base [Module] the base module
|
73
|
+
# @param object [Object] the object
|
74
|
+
# @param attributes [ActiveSupport::HashWithIndifferentAccess] the attributes
|
75
|
+
# @return [Boolean] +true+ if all attribues are deep equal; otherwise, +false+
|
76
|
+
def all_attributes_eql?(base, object, attributes = ::ActiveSupport::HashWithIndifferentAccess.new)
|
77
|
+
attributes.try(:each_pair) do |pair|
|
78
|
+
attribute_name, expected_value = *pair
|
79
|
+
|
80
|
+
return false unless deep_eql?(base, expected_value, object.send(attribute_name))
|
81
|
+
end
|
82
|
+
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def deep_eql?(base, value, other_value)
|
89
|
+
if value.class.parents[-2] == base
|
90
|
+
if value.is_a?(::Array)
|
91
|
+
return false unless other_value.is_a?(::Array) && (value.all? { |instance_for_value|
|
92
|
+
other_value.any? { |instance_for_other_value|
|
93
|
+
deep_eql?(base, instance_for_value, instance_for_other_value)
|
94
|
+
}
|
95
|
+
})
|
96
|
+
elsif value.is_a?(::String)
|
97
|
+
return false unless (value.to_s == other_value.to_s)
|
98
|
+
else
|
99
|
+
attributes = value.class.instance_methods(false).reject { |method_name|
|
100
|
+
method_name.to_s.ends_with?("=")
|
101
|
+
}.inject(::ActiveSupport::HashWithIndifferentAccess.new) { |acc, method_name|
|
102
|
+
value.send(method_name).try { |expected_value|
|
103
|
+
acc[method_name] = expected_value
|
104
|
+
}
|
105
|
+
|
106
|
+
acc
|
107
|
+
}
|
108
|
+
|
109
|
+
return false unless (value.class == other_value.class) && all_attributes_eql?(base, other_value, attributes)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
return false unless (value == other_value)
|
113
|
+
end
|
114
|
+
|
115
|
+
true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Included when the base class is a collection.
|
120
|
+
module InstanceMethodsForCollection
|
121
|
+
extend ::ActiveSupport::Concern
|
122
|
+
|
123
|
+
included do
|
124
|
+
include Isomorphic::Lens::Internal::InstanceMethodsForLens
|
125
|
+
end
|
126
|
+
|
127
|
+
# @!scope instance
|
128
|
+
|
129
|
+
# Build an Isomorphic node for an Active Record association.
|
130
|
+
#
|
131
|
+
# @param options [Hash<Symbol, Object>] the options
|
132
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
133
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
134
|
+
# @yieldparam node [Isomorphic::Node::Association] the Isomorphic node (member)
|
135
|
+
# @yieldreturn [void]
|
136
|
+
# @return [self]
|
137
|
+
def association(**options, &block)
|
138
|
+
node = Isomorphic::Node::Association.new(self, **options, &block)
|
139
|
+
@__child_nodes << node
|
140
|
+
node
|
141
|
+
end
|
142
|
+
|
143
|
+
# Build an Isomorphic node for an Active Record association (as an attribute) using an Isomorphic lens.
|
144
|
+
#
|
145
|
+
# @param lens_or_association [Isomorphic::Lens::AbstractLens, #to_s] the Isomorphic lens or the name of the Active Record association
|
146
|
+
# @param options [Hash<Symbol, Object>] the options
|
147
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
148
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
149
|
+
# @yieldparam node [Isomorphic::Node::Association] the Isomorphic node (member)
|
150
|
+
# @yieldreturn [void]
|
151
|
+
# @return [self]
|
152
|
+
def association_for(lens_or_association, **options, &block)
|
153
|
+
lens = lens_or_association.is_a?(Isomorphic::Lens::AbstractLens) ? lens_or_association : reflect_on_attribute(lens_or_association)
|
154
|
+
|
155
|
+
association(**options.merge({
|
156
|
+
get: ::Proc.new { |record| lens.get(record) },
|
157
|
+
set: ::Proc.new { |record, value, xmlattr_acc| lens.set(record, value, xmlattr_acc) },
|
158
|
+
}), &block)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Build an Isomorphic node for zero or more members of a collection.
|
162
|
+
#
|
163
|
+
# @param isomorphism_class [Class] the class
|
164
|
+
# @param options [Hash<Symbol, Object>] the options
|
165
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
166
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
167
|
+
# @yieldparam node [Isomorphic::Node::Element] the Isomorphic node (member)
|
168
|
+
# @yieldreturn [void]
|
169
|
+
# @return [self]
|
170
|
+
def element(isomorphism_class, **options, &block)
|
171
|
+
node = Isomorphic::Node::Element.new(self, isomorphism_class, **options, &block)
|
172
|
+
@__child_nodes << node
|
173
|
+
node
|
174
|
+
end
|
175
|
+
|
176
|
+
# Build an Isomorphic node for a "guard" statement for an Active Record association using an Isomorphic lens.
|
177
|
+
#
|
178
|
+
# @param lens_or_attribute_name [Isomorphic::Lens::AbstractLens, #to_s] the Isomorphic lens or the name of the Active Record association
|
179
|
+
# @param values [Array<Object>] the included values
|
180
|
+
# @param options [Hash<Symbol, Object>] the options
|
181
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
182
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
183
|
+
# @option options [Object] :default (nil) the default value, where more than one included value is given
|
184
|
+
# @yieldparam node [Isomorphic::Node::GuardCollection] the Isomorphic node (collection)
|
185
|
+
# @yieldreturn [void]
|
186
|
+
# @return [self]
|
187
|
+
def guard_association_for(lens_or_attribute_name, *values, **options, &block)
|
188
|
+
lens = lens_or_attribute_name.is_a?(Isomorphic::Lens::AbstractLens) ? lens_or_attribute_name : reflect_on_attribute(lens_or_attribute_name)
|
189
|
+
|
190
|
+
node = Isomorphic::Node::GuardCollection.new(self, lens, *values, **options, &block)
|
191
|
+
@__child_nodes << node
|
192
|
+
node
|
193
|
+
end
|
194
|
+
|
195
|
+
# Build an Isomorphic node for a value of a "namespace" statement that is within scope.
|
196
|
+
#
|
197
|
+
# @param namespace [#to_s] the namespace name
|
198
|
+
# @param terms [Array<Object>] the inflectable terms
|
199
|
+
# @param options [Hash<Symbol, Object>] the options
|
200
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
201
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
202
|
+
# @yieldparam node [Isomorphic::Node::NamespaceAssociation] the Isomorphic node (member)
|
203
|
+
# @yieldreturn [void]
|
204
|
+
# @return [self]
|
205
|
+
# @raise [Isomorphic::InflectorError] if an inflectable term is invalid
|
206
|
+
def namespace_association_for(namespace, terms, **options, &block)
|
207
|
+
key = inflector.isomorphism(terms)
|
208
|
+
|
209
|
+
node = Isomorphic::Node::NamespaceAssociation.new(self, namespace, key, **options, &block)
|
210
|
+
@__child_nodes << node
|
211
|
+
node
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Included when the base class is a member.
|
216
|
+
module InstanceMethodsForMember
|
217
|
+
extend ::ActiveSupport::Concern
|
218
|
+
|
219
|
+
included do
|
220
|
+
include Isomorphic::Lens::Internal::InstanceMethodsForLens
|
221
|
+
end
|
222
|
+
|
223
|
+
# @!scope instance
|
224
|
+
|
225
|
+
# Build an Isomorphic node for an attribute.
|
226
|
+
#
|
227
|
+
# @param method_name [#to_s] the method name for the target
|
228
|
+
# @param isomorphism_xmlattr_class [Class] the class for the target
|
229
|
+
# @param options [Hash<Symbol, Object>] the options
|
230
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
231
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
232
|
+
# @yieldparam node [Isomorphic::Node::Attribute] the Isomorphic node (member)
|
233
|
+
# @yieldreturn [void]
|
234
|
+
# @return [self]
|
235
|
+
def attribute(method_name, isomorphism_xmlattr_class = nil, **options, &block)
|
236
|
+
node = Isomorphic::Node::Attribute.new(self, method_name, isomorphism_xmlattr_class, **options, &block)
|
237
|
+
@__child_nodes << node
|
238
|
+
node
|
239
|
+
end
|
240
|
+
|
241
|
+
# Build an Isomorphic node for an attribute using the given lens.
|
242
|
+
#
|
243
|
+
# @param lens_or_attribute_name [Isomorphic::Lens::AbstractLens, #to_s] the Isomorphic lens or the name of the attribute
|
244
|
+
# @param method_name [#to_s] the method name for the target
|
245
|
+
# @param isomorphism_xmlattr_class [Class] the class for the target
|
246
|
+
# @param options [Hash<Symbol, Object>] the options
|
247
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
248
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
249
|
+
# @yieldparam node [Isomorphic::Node::Attribute] the Isomorphic node (member)
|
250
|
+
# @yieldreturn [void]
|
251
|
+
# @return [self]
|
252
|
+
def attribute_for(lens_or_attribute_name, method_name, isomorphism_xmlattr_class = nil, **options, &block)
|
253
|
+
lens = lens_or_attribute_name.is_a?(Isomorphic::Lens::AbstractLens) ? lens_or_attribute_name : reflect_on_attribute(lens_or_attribute_name)
|
254
|
+
|
255
|
+
attribute(method_name, isomorphism_xmlattr_class, **options.merge({
|
256
|
+
get: ::Proc.new { |record| lens.get(record) },
|
257
|
+
set: ::Proc.new { |record, value, xmlattr_acc| lens.set(record, value, xmlattr_acc) },
|
258
|
+
}), &block)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Build an Isomorphic node for a collection.
|
262
|
+
#
|
263
|
+
# @param method_name [#to_s] the method name for the target
|
264
|
+
# @param isomorphism_class [Class] the class for the target
|
265
|
+
# @param options [Hash<Symbol, Object>] the options
|
266
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
267
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
268
|
+
# @yieldparam node [Isomorphic::Node::Attribute] the Isomorphic node (collection)
|
269
|
+
# @yieldreturn [void]
|
270
|
+
# @return [self]
|
271
|
+
def collection(method_name, isomorphism_class, **options, &block)
|
272
|
+
node = Isomorphic::Node::Collection.new(self, method_name, isomorphism_class, **options, &block)
|
273
|
+
@__child_nodes << node
|
274
|
+
node
|
275
|
+
end
|
276
|
+
|
277
|
+
# Build an Isomorphic node for a +Proc+ that is called in the reverse direction only.
|
278
|
+
#
|
279
|
+
# @param options [Hash<Symbol, Object>] the options
|
280
|
+
# @yieldparam instance [Object] the instance
|
281
|
+
# @yieldparam record [ActiveRecord::Base] the record
|
282
|
+
# @yieldparam xmlattr_acc [ActiveSupport::HashWithIndifferentAccess] the accumulator for XML attributes
|
283
|
+
# @yieldparam scope [Isomorphic::Node::Internal::Scope] the scope
|
284
|
+
# @yieldreturn [ActiveRecord::Record] the record
|
285
|
+
# @return [self]
|
286
|
+
def from(**options, &block)
|
287
|
+
node = Isomorphic::Node::ProcFrom.new(self, **options, &block)
|
288
|
+
@__child_nodes << node
|
289
|
+
node
|
290
|
+
end
|
291
|
+
|
292
|
+
# Build an Isomorphic node for a "guard" statement for an attribute using an Isomorphic lens.
|
293
|
+
#
|
294
|
+
# @param lens_or_attribute_name [Isomorphic::Lens::AbstractLens, #to_s] the Isomorphic lens or the name of the attribute
|
295
|
+
# @param values [Array<Object>] the included values
|
296
|
+
# @param options [Hash<Symbol, Object>] the options
|
297
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
298
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
299
|
+
# @option options [Object] :default (nil) the default value, where more than one included value is given
|
300
|
+
# @yieldparam node [Isomorphic::Node::GuardCollection] the Isomorphic node (collection)
|
301
|
+
# @yieldreturn [void]
|
302
|
+
# @return [self]
|
303
|
+
def guard_attribute_for(lens_or_attribute_name, *values, **options, &block)
|
304
|
+
lens = lens_or_attribute_name.is_a?(Isomorphic::Lens::AbstractLens) ? lens_or_attribute_name : reflect_on_attribute(lens_or_attribute_name)
|
305
|
+
|
306
|
+
node = Isomorphic::Node::GuardMember.new(self, lens, *values, **options, &block)
|
307
|
+
@__child_nodes << node
|
308
|
+
node
|
309
|
+
end
|
310
|
+
|
311
|
+
# Build an Isomorphic node for a member.
|
312
|
+
#
|
313
|
+
# @param method_name [#to_s] the method name for the target
|
314
|
+
# @param isomorphism_class [Class] the class for the target
|
315
|
+
# @param options [Hash<Symbol, Object>] the options
|
316
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
317
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
318
|
+
# @yieldparam node [Isomorphic::Node::Attribute] the Isomorphic node (collection)
|
319
|
+
# @yieldreturn [void]
|
320
|
+
# @return [self]
|
321
|
+
def member(method_name, isomorphism_class, **options, &block)
|
322
|
+
node = Isomorphic::Node::Member.new(self, method_name, isomorphism_class, **options, &block)
|
323
|
+
@__child_nodes << node
|
324
|
+
node
|
325
|
+
end
|
326
|
+
|
327
|
+
# Build an Isomorphic node for a "namespace" statement using an Isomorphic lens.
|
328
|
+
#
|
329
|
+
# @param namespace [#to_s] the namespace name
|
330
|
+
# @param lens_or_attribute_name [Isomorphic::Lens::AbstractLens, #to_s] the Isomorphic lens or the name of the attribute
|
331
|
+
# @param options [Hash<Symbol, Object>] the options
|
332
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
333
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
334
|
+
# @yieldparam node [Isomorphic::Node::NamespaceAssociation] the Isomorphic node (member)
|
335
|
+
# @yieldreturn [void]
|
336
|
+
# @return [self]
|
337
|
+
def namespace(namespace, lens_or_attribute_name, **options, &block)
|
338
|
+
lens = lens_or_attribute_name.is_a?(Isomorphic::Lens::AbstractLens) ? lens_or_attribute_name : reflect_on_attribute(lens_or_attribute_name)
|
339
|
+
|
340
|
+
node = Isomorphic::Node::Namespace.new(self, namespace, lens, **options, &block)
|
341
|
+
@__child_nodes << node
|
342
|
+
node
|
343
|
+
end
|
344
|
+
|
345
|
+
# Build an Isomorphic node for a value of a "namespace" statement that is within scope.
|
346
|
+
#
|
347
|
+
# @param namespace [#to_s] the namespace name
|
348
|
+
# @param terms [Array<Object>] the inflectable terms
|
349
|
+
# @param method_name [#to_s] the method name for the target
|
350
|
+
# @param isomorphism_xmlattr_class [Class] the class for the target
|
351
|
+
# @param options [Hash<Symbol, Object>] the options
|
352
|
+
# @option options [Boolean] :allow_blank (false) +true+ if the new node should always return a non-+nil+ target
|
353
|
+
# @option options [Hash<Symbol, Object>] :attributes ({}) default attributes for the target
|
354
|
+
# @yieldparam node [Isomorphic::Node::NamespaceAssociation] the Isomorphic node (member)
|
355
|
+
# @yieldreturn [void]
|
356
|
+
# @return [self]
|
357
|
+
# @raise [Isomorphic::InflectorError] if an inflectable term is invalid
|
358
|
+
def namespace_attribute_for(namespace, terms, method_name, isomorphism_xmlattr_class = nil, **options, &block)
|
359
|
+
key = inflector.isomorphism(terms)
|
360
|
+
|
361
|
+
node = Isomorphic::Node::NamespaceAttribute.new(self, namespace, key, method_name, isomorphism_xmlattr_class, **options, &block)
|
362
|
+
@__child_nodes << node
|
363
|
+
node
|
364
|
+
end
|
365
|
+
|
366
|
+
# Build an Isomorphic node for a +Proc+ that is called in the forward direction only.
|
367
|
+
#
|
368
|
+
# @param options [Hash<Symbol, Object>] the options
|
369
|
+
# @yieldparam record [ActiveRecord::Base] the record
|
370
|
+
# @yieldparam instance [Object] the instance
|
371
|
+
# @yieldparam scope [Isomorphic::Node::Internal::Scope] the scope
|
372
|
+
# @yieldreturn [Object] the instance
|
373
|
+
# @return [self]
|
374
|
+
def to(**options, &block)
|
375
|
+
node = Isomorphic::Node::ProcTo.new(self, **options, &block)
|
376
|
+
@__child_nodes << node
|
377
|
+
node
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Implements a tree of hashes, where retrieval is from the bottom-up.
|
382
|
+
class Scope
|
383
|
+
# @!attribute [r] parent
|
384
|
+
# @return [Isomorphic::Node::Internal::Scope] the parent scope
|
385
|
+
attr_reader :parent
|
386
|
+
|
387
|
+
# Default constructor.
|
388
|
+
#
|
389
|
+
# @param parent [Isomorphic::Node::Internal::Scope] the parent scope
|
390
|
+
# @param inflector [Isomorphic::Inflector::AbstractInflector] the Isomorphic inflector
|
391
|
+
def initialize(parent, inflector = nil)
|
392
|
+
super()
|
393
|
+
|
394
|
+
@parent, @inflector = parent, inflector
|
395
|
+
|
396
|
+
@value_by_namespace_and_key = ActiveSupport::HashWithIndifferentAccess.new
|
397
|
+
end
|
398
|
+
|
399
|
+
# @!attribute [r] inflector
|
400
|
+
# @return [Isomorphic::Inflector::AbstractInflector] the inflector
|
401
|
+
%i(inflector).each do |method_name|
|
402
|
+
define_method(method_name) do
|
403
|
+
if parent.is_a?(Isomorphic::Node::Internal::Scope)
|
404
|
+
parent.send(method_name)
|
405
|
+
else
|
406
|
+
instance_variable_get(:"@#{method_name}")
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
# Build a new child scope for the given namespace.
|
412
|
+
#
|
413
|
+
# @param namespace [#to_s] the namespace name
|
414
|
+
# @return [Isomorphic::Node::Internal::Scope] the new child scope
|
415
|
+
def child(namespace)
|
416
|
+
scope = self.class.new(self)
|
417
|
+
|
418
|
+
scope.instance_variable_get(:@value_by_namespace_and_key).send(:[]=, namespace, inflector.convert_hash({}))
|
419
|
+
|
420
|
+
scope
|
421
|
+
end
|
422
|
+
|
423
|
+
# Get the scoped value of the given key in the context of the given namespace.
|
424
|
+
#
|
425
|
+
# @param namespace [#to_s] the namespace name
|
426
|
+
# @param key [#to_s] the key
|
427
|
+
# @return [Object, nil] the value or +nil+ if the namespace or key are not defined
|
428
|
+
def get(namespace, key)
|
429
|
+
if @value_by_namespace_and_key.key?(namespace) && @value_by_namespace_and_key[namespace].key?(key)
|
430
|
+
@value_by_namespace_and_key[namespace][key]
|
431
|
+
elsif @parent.is_a?(self.class)
|
432
|
+
@parent.get(namespace, key)
|
433
|
+
else
|
434
|
+
nil
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
# The keys for the given namespace.
|
439
|
+
#
|
440
|
+
# @param namespace [#to_s] the namespace name
|
441
|
+
# @return [Array<String>] the keys or +nil+ if the namespace is not defined
|
442
|
+
def keys(namespace)
|
443
|
+
if @value_by_namespace_and_key.key?(namespace)
|
444
|
+
@value_by_namespace_and_key[namespace].keys
|
445
|
+
else
|
446
|
+
nil
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
# The namespaces.
|
451
|
+
#
|
452
|
+
# @return [Array<String>] the namespace names
|
453
|
+
def namespaces
|
454
|
+
@value_by_namespace_and_key.keys
|
455
|
+
end
|
456
|
+
|
457
|
+
# Set the scoped value of the given key in the context of the given namespace.
|
458
|
+
#
|
459
|
+
# @param namespace [#to_s] the namespace name
|
460
|
+
# @param key [#to_s] the key
|
461
|
+
# @param value [Object] the value
|
462
|
+
# @return [Object, nil] the value or +nil+ if the namespace is not defined
|
463
|
+
def set(namespace, key, value)
|
464
|
+
if @value_by_namespace_and_key.key?(namespace)
|
465
|
+
@value_by_namespace_and_key[namespace][key] = value
|
466
|
+
elsif @parent.is_a?(self.class)
|
467
|
+
@parent.set(namespace, key, value)
|
468
|
+
else
|
469
|
+
nil
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# The values for the given namespace.
|
474
|
+
#
|
475
|
+
# @param namespace [#to_s] the namespace name
|
476
|
+
# @return [Array<Object>, nil] the values or +nil+ if the namespace is not defined
|
477
|
+
def values(namespace)
|
478
|
+
keys(namespace).try(:inject, inflector.convert_hash({})) { |acc, key|
|
479
|
+
acc[key] = get(namespace, key)
|
480
|
+
acc
|
481
|
+
}
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
# Generic base class for Isomorphic nodes.
|
487
|
+
#
|
488
|
+
# @abstract Subclass and override {#from_isomorphism} and {#to_isomorphism} to implement a custom class.
|
489
|
+
class AbstractNode
|
490
|
+
include Isomorphic::Node::Internal::DeepEquals
|
491
|
+
|
492
|
+
# @!attribute [r] parent
|
493
|
+
# @return [Isomorphic::Node::AbstractNode] the parent node
|
494
|
+
attr_reader :parent
|
495
|
+
|
496
|
+
# @!attribute [r] options
|
497
|
+
# @return [Hash<Symbol, Object>] the options
|
498
|
+
attr_reader :options
|
499
|
+
|
500
|
+
# Default constructor.
|
501
|
+
#
|
502
|
+
# @param parent [Isomorphic::Node::AbstractNode] the parent node
|
503
|
+
# @param args [Array<Object>] the arguments
|
504
|
+
# @yieldparam [self]
|
505
|
+
# @yieldreturn [void]
|
506
|
+
def initialize(parent, *args, &block)
|
507
|
+
super()
|
508
|
+
|
509
|
+
@__cache_for_to_isomorphism_for = {}
|
510
|
+
@__child_nodes = []
|
511
|
+
|
512
|
+
@parent = parent
|
513
|
+
|
514
|
+
@options = args.extract_options!
|
515
|
+
|
516
|
+
unless block.nil?
|
517
|
+
self.instance_exec(*args, &block)
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
# @!attribute [r] factory
|
522
|
+
# @return [Isomorphic::Factory::AbstractFactory] the Isomorphic factory
|
523
|
+
# @!attribute [r] inflector
|
524
|
+
# @return [Isomorphic::Inflector::AbstractInflector] the Isomorphic inflector
|
525
|
+
%i(factory inflector).each do |method_name|
|
526
|
+
define_method(method_name) do
|
527
|
+
if parent.is_a?(Isomorphic::Node::AbstractNode)
|
528
|
+
parent.send(method_name)
|
529
|
+
else
|
530
|
+
instance_variable_get(:"@#{method_name}")
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
# Converts the given instance to an Active Record record.
|
536
|
+
#
|
537
|
+
# @param scope [Isomorphic::Node::Internal::Scope] the scope
|
538
|
+
# @param isomorphism_instance [Object] the instance
|
539
|
+
# @param record [ActiveRecord::Base] the record (optional; when not provided, a new instance of the Active Record class is constructed)
|
540
|
+
# @param xmlattr_acc [ActiveSupport::HashWithIndifferentAccess] the accumulator for XML attributes
|
541
|
+
# @return [ActiveRecord::Base] the record
|
542
|
+
def from_isomorphism(scope, isomorphism_instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
543
|
+
raise ::NotImplementedError
|
544
|
+
end
|
545
|
+
|
546
|
+
# Converts the given Active Record record to an instance.
|
547
|
+
#
|
548
|
+
# @param scope [Isomorphic::Node::Internal::Scope] the scope
|
549
|
+
# @param record [ActiveRecord::Base] the record
|
550
|
+
# @param isomorphism_instance [Object] the instance (optional; when not provided, a new instance is constructed)
|
551
|
+
# @return [Object] the instance
|
552
|
+
def to_isomorphism(scope, record, isomorphism_instance = nil)
|
553
|
+
raise ::NotImplementedError
|
554
|
+
end
|
555
|
+
|
556
|
+
# Returns the cached instance for the given Active Record record.
|
557
|
+
#
|
558
|
+
# @param record [ActiveRecord::Base] the record
|
559
|
+
# @param args [Array<#to_s>] the method names
|
560
|
+
# @return [Object] the cached instance
|
561
|
+
def to_isomorphism_for(record, *args)
|
562
|
+
args.inject(@__cache_for_to_isomorphism_for[record]) { |acc, arg|
|
563
|
+
acc.try(:[], arg)
|
564
|
+
}
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
class Association < Isomorphic::Node::AbstractNode
|
569
|
+
include Isomorphic::Node::Internal::InstanceMethodsForMember
|
570
|
+
|
571
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
572
|
+
is_present = false
|
573
|
+
|
574
|
+
instance.each do |instance_for_node|
|
575
|
+
_set(scope, record, instance_for_node, xmlattr_acc).try { |record_for_node|
|
576
|
+
is_present ||= true
|
577
|
+
|
578
|
+
@__child_nodes.inject(false) { |acc, child_node| child_node.from_isomorphism(scope, instance_for_node, record_for_node, xmlattr_acc).nil? ? acc : true }
|
579
|
+
}
|
580
|
+
end
|
581
|
+
|
582
|
+
if is_present
|
583
|
+
record
|
584
|
+
else
|
585
|
+
nil
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
def to_isomorphism(scope, record, instance = nil)
|
590
|
+
instance_for_node_or_array = _get(scope, record).try { |instance_for_node_or_hash|
|
591
|
+
@__cache_for_to_isomorphism_for[record] ||= instance_for_node_or_hash
|
592
|
+
|
593
|
+
if instance_for_node_or_hash.is_a?(::Hash)
|
594
|
+
instance_for_node_or_hash.values
|
595
|
+
else
|
596
|
+
instance_for_node_or_hash
|
597
|
+
end
|
598
|
+
}
|
599
|
+
|
600
|
+
unless instance_for_node_or_array.nil?
|
601
|
+
array = (instance_for_node_or_array.instance_of?(::Array) ? instance_for_node_or_array : [instance_for_node_or_array]).reject(&:nil?)
|
602
|
+
|
603
|
+
if array.any? || options[:allow_blank]
|
604
|
+
array.each do |instance_for_node|
|
605
|
+
@__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(scope, record, instance_for_node).nil? ? acc : true }
|
606
|
+
|
607
|
+
instance.send(:<<, instance_for_node)
|
608
|
+
end
|
609
|
+
|
610
|
+
instance_for_node_or_array
|
611
|
+
else
|
612
|
+
nil
|
613
|
+
end
|
614
|
+
else
|
615
|
+
nil
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
protected
|
620
|
+
|
621
|
+
def _get(scope, record)
|
622
|
+
options[:get].try { |block|
|
623
|
+
case block.arity
|
624
|
+
when 1 then block.call(record)
|
625
|
+
else record.instance_eval(&block)
|
626
|
+
end
|
627
|
+
}
|
628
|
+
end
|
629
|
+
|
630
|
+
def _set(scope, record, instance_for_node, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
631
|
+
options[:set].try { |block|
|
632
|
+
case block.arity
|
633
|
+
when 3 then block.call(record, instance_for_node, xmlattr_acc)
|
634
|
+
else record.instance_exec(instance_for_node, xmlattr_acc, &block)
|
635
|
+
end
|
636
|
+
}
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
class Attribute < AbstractNode
|
641
|
+
include Isomorphic::Node::Internal::InstanceMethodsForMember
|
642
|
+
|
643
|
+
attr_reader :method_name
|
644
|
+
|
645
|
+
attr_reader :xmlattr_class
|
646
|
+
|
647
|
+
def initialize(parent, method_name, xmlattr_class = nil, **options, &block)
|
648
|
+
super(parent, **options, &block)
|
649
|
+
|
650
|
+
unless xmlattr_class.nil? || (xmlattr_class.is_a?(::Class) && (xmlattr_class.parents[-2] == parent.inflector.base))
|
651
|
+
raise Isomorphic::InvalidNodeClass.new(nil, xmlattr_class)
|
652
|
+
end
|
653
|
+
|
654
|
+
@method_name = method_name
|
655
|
+
|
656
|
+
@xmlattr_class = xmlattr_class
|
657
|
+
end
|
658
|
+
|
659
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
660
|
+
is_present = false
|
661
|
+
|
662
|
+
instance_for_node = instance.send(method_name)
|
663
|
+
|
664
|
+
is_valid = (xmlattr_class.nil? || instance_for_node.instance_of?(xmlattr_class)) && all_attributes_eql?(inflector.base, instance_for_node, options[:attributes].try(:each_pair).try(:inject, ::ActiveSupport::HashWithIndifferentAccess.new) { |acc, pair|
|
665
|
+
xmlattr_name, value = *pair
|
666
|
+
|
667
|
+
acc[:"xmlattr_#{xmlattr_name}"] = value
|
668
|
+
acc
|
669
|
+
})
|
670
|
+
|
671
|
+
if is_valid && (!instance_for_node.nil? || options[:allow_nil])
|
672
|
+
_set(scope, record, instance_for_node, xmlattr_acc).try { |record_for_node|
|
673
|
+
is_present ||= true
|
674
|
+
|
675
|
+
@__child_nodes.inject(false) { |acc, child_node| child_node.from_isomorphism(scope, instance_for_node, record_for_node, xmlattr_acc).nil? ? acc : true }
|
676
|
+
}
|
677
|
+
end
|
678
|
+
|
679
|
+
if is_present
|
680
|
+
record
|
681
|
+
else
|
682
|
+
nil
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
def to_isomorphism(scope, record, instance = nil)
|
687
|
+
instance_for_node = _get(scope, record).try { |retval_or_hash|
|
688
|
+
@__cache_for_to_isomorphism_for[record] ||= retval_or_hash
|
689
|
+
|
690
|
+
if retval_or_hash.is_a?(::Hash)
|
691
|
+
retval_or_hash.values
|
692
|
+
else
|
693
|
+
retval_or_hash
|
694
|
+
end
|
695
|
+
}.try { |retval|
|
696
|
+
if retval.class.parents[-2] == inflector.base
|
697
|
+
@__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(scope, record, retval).nil? ? acc : true }
|
698
|
+
|
699
|
+
retval
|
700
|
+
else
|
701
|
+
retval.try(:to_s).try { |s|
|
702
|
+
if xmlattr_class.nil?
|
703
|
+
s
|
704
|
+
else
|
705
|
+
instance_for_node = xmlattr_class.new(s)
|
706
|
+
|
707
|
+
options[:attributes].try(:each_pair).try(:inject, {}) { |acc, pair|
|
708
|
+
xmlattr_name, value = *pair
|
709
|
+
|
710
|
+
acc[:"xmlattr_#{xmlattr_name}"] = value
|
711
|
+
acc
|
712
|
+
}.try { |attributes|
|
713
|
+
factory.update_attributes(instance_for_node, attributes)
|
714
|
+
}
|
715
|
+
|
716
|
+
@__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(scope, record, instance_for_node).nil? ? acc : true }
|
717
|
+
|
718
|
+
instance_for_node
|
719
|
+
end
|
720
|
+
}
|
721
|
+
end
|
722
|
+
}
|
723
|
+
|
724
|
+
if !instance_for_node.nil? || options[:allow_nil]
|
725
|
+
instance.send(:"#{method_name}=", instance_for_node)
|
726
|
+
else
|
727
|
+
nil
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
protected
|
732
|
+
|
733
|
+
def _get(scope, record)
|
734
|
+
options[:get].try { |block|
|
735
|
+
case block.arity
|
736
|
+
when 1 then block.call(record)
|
737
|
+
else record.instance_eval(&block)
|
738
|
+
end
|
739
|
+
}
|
740
|
+
end
|
741
|
+
|
742
|
+
def _set(scope, record, instance_for_node, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
743
|
+
options[:set].try { |block|
|
744
|
+
case block.arity
|
745
|
+
when 3 then block.call(record, instance_for_node, xmlattr_acc)
|
746
|
+
else record.instance_exec(instance_for_node, xmlattr_acc, &block)
|
747
|
+
end
|
748
|
+
}
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
class Collection < Isomorphic::Node::AbstractNode
|
753
|
+
include Isomorphic::Node::Internal::InstanceMethodsForCollection
|
754
|
+
|
755
|
+
attr_reader :method_name
|
756
|
+
|
757
|
+
attr_reader :klass
|
758
|
+
|
759
|
+
def initialize(parent, method_name, klass, **options, &block)
|
760
|
+
super(parent, **options, &block)
|
761
|
+
|
762
|
+
unless klass.is_a?(::Class) && (klass.parents[-2] == parent.inflector.base)
|
763
|
+
raise Isomorphic::InvalidNodeClass.new(nil, klass)
|
764
|
+
end
|
765
|
+
|
766
|
+
@method_name = method_name
|
767
|
+
@klass = klass
|
768
|
+
end
|
769
|
+
|
770
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
771
|
+
is_present = false
|
772
|
+
|
773
|
+
instance.send(method_name).try { |instance_for_node|
|
774
|
+
is_valid = instance_for_node.instance_of?(klass) && all_attributes_eql?(inflector.base, instance_for_node, options[:attributes])
|
775
|
+
|
776
|
+
if is_valid
|
777
|
+
is_present ||= @__child_nodes.inject(false) { |acc, child_node| child_node.from_isomorphism(scope, instance_for_node, record, xmlattr_acc).nil? ? acc : true }
|
778
|
+
end
|
779
|
+
}
|
780
|
+
|
781
|
+
if is_present
|
782
|
+
record
|
783
|
+
else
|
784
|
+
nil
|
785
|
+
end
|
786
|
+
end
|
787
|
+
|
788
|
+
def to_isomorphism(scope, record, instance = nil)
|
789
|
+
is_present = false
|
790
|
+
|
791
|
+
instance_for_node = instance.send(method_name) || factory.for(klass)
|
792
|
+
|
793
|
+
options[:attributes].try { |attributes|
|
794
|
+
factory.update_attributes(instance_for_node, attributes)
|
795
|
+
}
|
796
|
+
|
797
|
+
is_present ||= @__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(scope, record, instance_for_node).nil? ? acc : true }
|
798
|
+
|
799
|
+
if is_present || options[:allow_blank]
|
800
|
+
instance.send(:"#{method_name}=", instance_for_node)
|
801
|
+
|
802
|
+
instance_for_node
|
803
|
+
else
|
804
|
+
nil
|
805
|
+
end
|
806
|
+
end
|
807
|
+
end
|
808
|
+
|
809
|
+
class Element < Isomorphic::Node::AbstractNode
|
810
|
+
include Isomorphic::Node::Internal::InstanceMethodsForMember
|
811
|
+
|
812
|
+
attr_reader :klass
|
813
|
+
|
814
|
+
def initialize(parent, klass, **options, &block)
|
815
|
+
super(parent, **options, &block)
|
816
|
+
|
817
|
+
unless klass.is_a?(::Class) && (klass.parents[-2] == parent.inflector.base)
|
818
|
+
raise Isomorphic::InvalidNodeClass.new(nil, klass)
|
819
|
+
end
|
820
|
+
|
821
|
+
@klass = klass
|
822
|
+
end
|
823
|
+
|
824
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
825
|
+
is_present = false
|
826
|
+
|
827
|
+
instance.try(:each) { |instance_for_node|
|
828
|
+
is_valid = instance_for_node.instance_of?(klass) && all_attributes_eql?(inflector.base, instance_for_node, options[:attributes])
|
829
|
+
|
830
|
+
if is_valid
|
831
|
+
is_present ||= @__child_nodes.inject(options[:allow_blank]) { |acc, child_node| child_node.from_isomorphism(scope, instance_for_node, record, xmlattr_acc).nil? ? acc : true }
|
832
|
+
end
|
833
|
+
}
|
834
|
+
|
835
|
+
if is_present
|
836
|
+
record
|
837
|
+
else
|
838
|
+
nil
|
839
|
+
end
|
840
|
+
end
|
841
|
+
|
842
|
+
def to_isomorphism(scope, record, instance = nil)
|
843
|
+
is_present = false
|
844
|
+
|
845
|
+
instance_for_node = factory.for(klass)
|
846
|
+
|
847
|
+
options[:attributes].try { |attributes|
|
848
|
+
factory.update_attributes(instance_for_node, attributes)
|
849
|
+
}
|
850
|
+
|
851
|
+
is_present ||= @__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(scope, record, instance_for_node).nil? ? acc : true }
|
852
|
+
|
853
|
+
if is_present || options[:allow_blank]
|
854
|
+
instance.send(:<<, instance_for_node)
|
855
|
+
|
856
|
+
instance_for_node
|
857
|
+
else
|
858
|
+
nil
|
859
|
+
end
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
class Guard < Isomorphic::Node::AbstractNode
|
864
|
+
attr_reader :lens
|
865
|
+
|
866
|
+
attr_reader :values
|
867
|
+
|
868
|
+
def initialize(parent, lens, *values, **options, &block)
|
869
|
+
super(parent, **options, &block)
|
870
|
+
|
871
|
+
unless (lens.is_a?(Isomorphic::Lens::Association) && lens.last_lens.is_a?(Isomorphic::Lens::Attribute)) || lens.is_a?(Isomorphic::Lens::Attribute)
|
872
|
+
raise Isomorphic::LensInvalid.new(nil, lens)
|
873
|
+
end
|
874
|
+
|
875
|
+
if (values.size > 1) && !options.key?(:default)
|
876
|
+
raise Isomorphic::DefaultOptionNotFound.new(nil)
|
877
|
+
elsif options.key?(:default) && !values.include?(options[:default])
|
878
|
+
raise Isomorphic::DefaultOptionNotIncluded.new(nil)
|
879
|
+
end
|
880
|
+
|
881
|
+
@lens = lens
|
882
|
+
@values = values
|
883
|
+
end
|
884
|
+
|
885
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
886
|
+
is_present = @__child_nodes.inject(false) { |acc, child_node| child_node.from_isomorphism(scope, instance, record, xmlattr_acc).nil? ? acc : true }
|
887
|
+
|
888
|
+
if is_present
|
889
|
+
value = \
|
890
|
+
case values.size
|
891
|
+
when 1 then values[0]
|
892
|
+
else options[:default]
|
893
|
+
end
|
894
|
+
|
895
|
+
lens.set(record, value, xmlattr_acc)
|
896
|
+
|
897
|
+
record
|
898
|
+
else
|
899
|
+
nil
|
900
|
+
end
|
901
|
+
end
|
902
|
+
|
903
|
+
def to_isomorphism(scope, record, instance = nil)
|
904
|
+
lens.get(record).try { |value_or_hash|
|
905
|
+
is_valid = \
|
906
|
+
if value_or_hash.is_a?(::Hash)
|
907
|
+
value_or_hash.values.all? { |value| values.include?(value) }
|
908
|
+
else
|
909
|
+
values.include?(value_or_hash)
|
910
|
+
end
|
911
|
+
|
912
|
+
if is_valid
|
913
|
+
is_present = @__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(scope, record, instance).nil? ? acc : true }
|
914
|
+
|
915
|
+
if is_present || options[:allow_blank]
|
916
|
+
instance
|
917
|
+
else
|
918
|
+
nil
|
919
|
+
end
|
920
|
+
else
|
921
|
+
nil
|
922
|
+
end
|
923
|
+
}
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
class GuardCollection < Isomorphic::Node::Guard
|
928
|
+
include Isomorphic::Node::Internal::InstanceMethodsForCollection
|
929
|
+
end
|
930
|
+
|
931
|
+
class GuardMember < Isomorphic::Node::Guard
|
932
|
+
include Isomorphic::Node::Internal::InstanceMethodsForMember
|
933
|
+
end
|
934
|
+
|
935
|
+
class Member < Isomorphic::Node::AbstractNode
|
936
|
+
include Isomorphic::Node::Internal::InstanceMethodsForMember
|
937
|
+
|
938
|
+
attr_reader :method_name
|
939
|
+
|
940
|
+
attr_reader :klass
|
941
|
+
|
942
|
+
def initialize(parent, method_name, klass, **options, &block)
|
943
|
+
super(parent, **options, &block)
|
944
|
+
|
945
|
+
unless klass.is_a?(::Class) && (klass.parents[-2] == parent.inflector.base)
|
946
|
+
raise Isomorphic::InvalidNodeClass.new(nil, klass)
|
947
|
+
end
|
948
|
+
|
949
|
+
@method_name = method_name
|
950
|
+
@klass = klass
|
951
|
+
end
|
952
|
+
|
953
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
954
|
+
is_present = false
|
955
|
+
|
956
|
+
instance.send(method_name).try { |instance_for_node|
|
957
|
+
is_valid = instance_for_node.instance_of?(klass) && all_attributes_eql?(inflector.base, instance_for_node, options[:attributes])
|
958
|
+
|
959
|
+
if is_valid
|
960
|
+
is_present ||= @__child_nodes.inject(options[:allow_blank]) { |acc, child_node| child_node.from_isomorphism(scope, instance_for_node, record, xmlattr_acc).nil? ? acc : true }
|
961
|
+
end
|
962
|
+
}
|
963
|
+
|
964
|
+
if is_present
|
965
|
+
record
|
966
|
+
else
|
967
|
+
nil
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
def to_isomorphism(scope, record, instance = nil)
|
972
|
+
is_present = false
|
973
|
+
|
974
|
+
instance_for_node = instance.send(method_name) || factory.for(klass)
|
975
|
+
|
976
|
+
options[:attributes].try { |attributes|
|
977
|
+
factory.update_attributes(instance_for_node, attributes)
|
978
|
+
}
|
979
|
+
|
980
|
+
is_present ||= @__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(scope, record, instance_for_node).nil? ? acc : true }
|
981
|
+
|
982
|
+
if is_present || options[:allow_blank]
|
983
|
+
instance.send(:"#{method_name}=", instance_for_node)
|
984
|
+
|
985
|
+
instance_for_node
|
986
|
+
else
|
987
|
+
nil
|
988
|
+
end
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
class Namespace < Isomorphic::Node::AbstractNode
|
993
|
+
include Isomorphic::Node::Internal::InstanceMethodsForMember
|
994
|
+
|
995
|
+
attr_reader :namespace_name
|
996
|
+
|
997
|
+
attr_reader :lens
|
998
|
+
|
999
|
+
def initialize(parent, namespace_name, lens, **options, &block)
|
1000
|
+
super(parent, **options, &block)
|
1001
|
+
|
1002
|
+
unless (lens.is_a?(Isomorphic::Lens::Association) && lens.last_lens.is_a?(Isomorphic::Lens::Isomorphism)) || lens.is_a?(Isomorphic::Lens::Isomorphism)
|
1003
|
+
raise Isomorphic::LensInvalid.new(nil, lens)
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
@namespace_name = namespace_name
|
1007
|
+
|
1008
|
+
@lens = lens
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
1012
|
+
new_scope = scope.child(namespace_name)
|
1013
|
+
|
1014
|
+
is_present = @__child_nodes.inject(false) { |acc, child_node| child_node.from_isomorphism(new_scope, instance, record, xmlattr_acc).nil? ? acc : true }
|
1015
|
+
|
1016
|
+
lens.set(record, new_scope.values(namespace_name), xmlattr_acc)
|
1017
|
+
|
1018
|
+
if is_present
|
1019
|
+
record
|
1020
|
+
else
|
1021
|
+
nil
|
1022
|
+
end
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
def to_isomorphism(scope, record, instance = nil)
|
1026
|
+
new_scope = scope.child(namespace_name)
|
1027
|
+
|
1028
|
+
lens.get(record).try { |hash|
|
1029
|
+
hash.each do |key, value|
|
1030
|
+
new_scope.set(namespace_name, key, value)
|
1031
|
+
end
|
1032
|
+
}
|
1033
|
+
|
1034
|
+
is_present = @__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(new_scope, record, instance).nil? ? acc : true }
|
1035
|
+
|
1036
|
+
if is_present || options[:allow_blank]
|
1037
|
+
instance
|
1038
|
+
else
|
1039
|
+
nil
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
class NamespaceAssociation < Isomorphic::Node::Association
|
1045
|
+
attr_reader :namespace_name, :key
|
1046
|
+
|
1047
|
+
def initialize(parent, namespace_name, key, **options, &block)
|
1048
|
+
super(parent, **options, &block)
|
1049
|
+
|
1050
|
+
@namespace_name = namespace_name
|
1051
|
+
@key = key
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
protected
|
1055
|
+
|
1056
|
+
def _get(scope, record)
|
1057
|
+
scope.get(namespace_name, key)
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
def _set(scope, record, instance_for_node, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
1061
|
+
scope.set(namespace_name, key, instance_for_node)
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
class NamespaceAttribute < Isomorphic::Node::Attribute
|
1066
|
+
attr_reader :namespace_name, :key
|
1067
|
+
|
1068
|
+
def initialize(parent, namespace_name, key, method_name, xmlattr_class = nil, **options, &block)
|
1069
|
+
super(parent, method_name, xmlattr_class, **options, &block)
|
1070
|
+
|
1071
|
+
@namespace_name = namespace_name
|
1072
|
+
@key = key
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
protected
|
1076
|
+
|
1077
|
+
def _get(scope, record)
|
1078
|
+
scope.get(namespace_name, key)
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
def _set(scope, record, instance_for_node, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
1082
|
+
scope.set(namespace_name, key, instance_for_node)
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
class ProcFrom < Isomorphic::Node::AbstractNode
|
1087
|
+
attr_reader :block
|
1088
|
+
|
1089
|
+
def initialize(parent, **options, &block)
|
1090
|
+
# super(parent, **options)
|
1091
|
+
|
1092
|
+
@block = block
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
1096
|
+
if block.nil?
|
1097
|
+
record
|
1098
|
+
else
|
1099
|
+
case block.arity
|
1100
|
+
when 1 then block.call(instance)
|
1101
|
+
when 2 then block.call(instance, record)
|
1102
|
+
when 3 then block.call(instance, record, xmlattr_acc)
|
1103
|
+
when 4 then block.call(instance, record, xmlattr_acc, scope)
|
1104
|
+
else instance.instance_exec(record, xmlattr_acc, scope, &block)
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def to_isomorphism(scope, record, instance = nil)
|
1110
|
+
instance
|
1111
|
+
end
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
class ProcTo < Isomorphic::Node::AbstractNode
|
1115
|
+
attr_reader :block
|
1116
|
+
|
1117
|
+
def initialize(parent, **options, &block)
|
1118
|
+
# super(parent, **options)
|
1119
|
+
|
1120
|
+
@block = block
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
def from_isomorphism(scope, instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
1124
|
+
record
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
def to_isomorphism(scope, record, instance = nil)
|
1128
|
+
if block.nil?
|
1129
|
+
instance
|
1130
|
+
else
|
1131
|
+
case block.arity
|
1132
|
+
when 1 then block.call(record)
|
1133
|
+
when 2 then block.call(record, instance)
|
1134
|
+
when 3 then block.call(record, instance, scope)
|
1135
|
+
else record.instance_exec(instance, scope, &block)
|
1136
|
+
end
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
|
1141
|
+
class Root < Isomorphic::Node::AbstractNode
|
1142
|
+
attr_reader :record_class
|
1143
|
+
|
1144
|
+
attr_reader :klass
|
1145
|
+
|
1146
|
+
attr_reader :args
|
1147
|
+
|
1148
|
+
def initialize(factory, inflector, record_class, klass, *args, &block)
|
1149
|
+
@factory = factory
|
1150
|
+
@inflector = inflector
|
1151
|
+
|
1152
|
+
@record_class = record_class
|
1153
|
+
@klass = klass
|
1154
|
+
|
1155
|
+
super(nil, *args, &block)
|
1156
|
+
|
1157
|
+
unless klass.is_a?(::Class) && (klass.parents[-2] == inflector.base)
|
1158
|
+
raise Isomorphic::InvalidNodeClass.new(nil, klass)
|
1159
|
+
end
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
def from_isomorphism(instance, record = nil, xmlattr_acc = ::ActiveSupport::HashWithIndifferentAccess.new)
|
1163
|
+
unless instance.instance_of?(klass)
|
1164
|
+
raise Isomorphic::InvalidNodeObject.new(nil, klass, instance)
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
scope = Isomorphic::Node::Internal::Scope.new(nil, inflector)
|
1168
|
+
|
1169
|
+
is_valid = all_attributes_eql?(inflector.base, instance, options[:attributes])
|
1170
|
+
|
1171
|
+
if is_valid
|
1172
|
+
record_for_node = record || record_class.new
|
1173
|
+
|
1174
|
+
@__child_nodes.each do |child_node|
|
1175
|
+
child_node.from_isomorphism(scope, instance, record_for_node, xmlattr_acc)
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
record_for_node
|
1179
|
+
else
|
1180
|
+
record
|
1181
|
+
end
|
1182
|
+
end
|
1183
|
+
|
1184
|
+
def to_isomorphism(record, instance = nil)
|
1185
|
+
scope = Isomorphic::Node::Internal::Scope.new(nil, inflector)
|
1186
|
+
|
1187
|
+
instance_for_node = instance || factory.for(klass)
|
1188
|
+
|
1189
|
+
options[:attributes].try { |attributes|
|
1190
|
+
factory.update_attributes(instance_for_node, attributes)
|
1191
|
+
}
|
1192
|
+
|
1193
|
+
is_present = @__child_nodes.inject(false) { |acc, child_node| child_node.to_isomorphism(scope, record, instance_for_node).nil? ? acc : true }
|
1194
|
+
|
1195
|
+
if is_present || options[:allow_blank]
|
1196
|
+
instance_for_node
|
1197
|
+
else
|
1198
|
+
nil
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
class RootCollection < Isomorphic::Node::Root
|
1204
|
+
include Isomorphic::Node::Internal::InstanceMethodsForCollection
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
class RootMember < Isomorphic::Node::Root
|
1208
|
+
include Isomorphic::Node::Internal::InstanceMethodsForMember
|
1209
|
+
end
|
1210
|
+
end
|
1211
|
+
end
|