trax_core 0.0.84 → 0.0.85

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +2 -2
  4. data/lib/trax/core/definitions.rb +4 -4
  5. data/lib/trax/core/ext/hash.rb +15 -5
  6. data/lib/trax/core/ext/method.rb +95 -0
  7. data/lib/trax/core/ext/object.rb +7 -2
  8. data/lib/trax/core/fields.rb +4 -0
  9. data/lib/trax/core/has_mixins.rb +5 -1
  10. data/lib/trax/core/transformer.rb +244 -0
  11. data/lib/trax/core/types/boolean.rb +1 -0
  12. data/lib/trax/core/types/enum.rb +7 -8
  13. data/lib/trax/core/types/enum_value.rb +4 -10
  14. data/lib/trax/core/types/json.rb +8 -0
  15. data/lib/trax/core/types/struct.rb +45 -4
  16. data/lib/trax/core/types/value_object.rb +7 -1
  17. data/lib/trax/core.rb +13 -0
  18. data/lib/trax_core/version.rb +1 -1
  19. data/spec/support/defs.rb +29 -1
  20. data/spec/support/storefront/product.rb +2 -0
  21. data/spec/trax/array_spec.rb +2 -6
  22. data/spec/trax/core/definitions_spec.rb +76 -2
  23. data/spec/trax/core/eager_autoload_namespace_spec.rb +4 -4
  24. data/spec/trax/core/errors_spec.rb +10 -15
  25. data/spec/trax/core/ext/array_spec.rb +2 -2
  26. data/spec/trax/core/ext/class_spec.rb +3 -3
  27. data/spec/trax/core/ext/hash_spec.rb +10 -0
  28. data/spec/trax/core/ext/method_spec.rb +104 -0
  29. data/spec/trax/core/ext/module_spec.rb +5 -5
  30. data/spec/trax/core/ext/object_spec.rb +5 -5
  31. data/spec/trax/core/transformer_spec.rb +170 -0
  32. data/spec/trax/core/types/array_spec.rb +4 -4
  33. data/spec/trax/core/types/enum_spec.rb +23 -18
  34. data/spec/trax/core/types/struct_spec.rb +50 -7
  35. data/spec/trax/core/types/value_object_spec.rb +6 -0
  36. data/spec/trax/core_spec.rb +1 -3
  37. data/spec/trax/hash_spec.rb +13 -15
  38. data/trax_core.gemspec +7 -6
  39. metadata +97 -5
  40. data/spec/trax/core/inheritance_spec.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc61a40b6e173df7ff14e66550fd7c5fa558ab4c
4
- data.tar.gz: 9b5f6b14a3b4c714b094f8020b66f3d532a79855
3
+ metadata.gz: 607a79a7c4082da1e2a834475a6d4f9cdd0ae5e5
4
+ data.tar.gz: 77f3a354d9981ac41cdbbc16c5786f9788c62ab7
5
5
  SHA512:
6
- metadata.gz: 18b3e5d1d9dc44a315bc88480c76b68ecb81a00d07dd41d14b831040ea3e62c7c696477ba9965c34f449a5f168aa35529f94ef3619f7ef2ebc99af8a5457bab7
7
- data.tar.gz: 1013d42d2b1900af67bd2e46edf65c0e28a59c301c400ae60423d945acf7d06ae2656b260a95d5baec813671323b50adabcfefb4438fd40102be00f349e47a47
6
+ metadata.gz: 10c480758ec396d9175a51272457c24fbd8d1da41a354a93bce0163760e60add4d177213d83e8faf780e9f0cc76177912453f7472d424c664a4bde73bde83743
7
+ data.tar.gz: aa5dadf5ea48099d9b44b7da1f0b54bd2520e2c98b80b3eed7106da6b465f3f3d490dda21bb0c240f2d489459e6645b8421ea562a9f2044a224b3e1475f16a6d
data/.gitignore CHANGED
@@ -8,6 +8,7 @@ InstalledFiles
8
8
  _yardoc
9
9
  coverage
10
10
  doc/
11
+ vendor/
11
12
  lib/bundler/man
12
13
  pkg
13
14
  rdoc
data/.travis.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
  script: bundle exec rspec
3
3
  rvm:
4
- - 2.2
5
- - 2.1
4
+ - 2.3.1
5
+ - 2.2.5
6
6
  notifications:
7
7
  email: false
@@ -6,8 +6,8 @@ module Trax
6
6
  end
7
7
 
8
8
  def enum(klass_name, **options, &block)
9
- attribute_klass = if options.key?(:extend)
10
- _klass_prototype = options[:extend].constantize.clone
9
+ attribute_klass = if options.key?(:extends)
10
+ _klass_prototype = options[:extends].constantize.clone
11
11
  ::Trax::Core::NamedClass.new("#{self.name}::#{klass_name}", _klass_prototype, :parent_definition => self, &block)
12
12
  else
13
13
  ::Trax::Core::NamedClass.new("#{self.name}::#{klass_name}", ::Trax::Core::Types::Enum, :parent_definition => self, &block)
@@ -17,8 +17,8 @@ module Trax
17
17
  end
18
18
 
19
19
  def struct(klass_name, **options, &block)
20
- attribute_klass = if options.key?(:extend)
21
- _klass_prototype = options[:extend].constantize.clone
20
+ attribute_klass = if options.key?(:extends)
21
+ _klass_prototype = options[:extends].constantize.clone
22
22
  ::Trax::Core::NamedClass.new("#{self.name}::#{klass_name}", _klass_prototype, :parent_definition => self, &block)
23
23
  else
24
24
  ::Trax::Core::NamedClass.new("#{self.name}::#{klass_name}", ::Trax::Core::Types::Struct, :parent_definition => self, &block)
@@ -1,21 +1,27 @@
1
- class Hash
1
+ module HashExtensions
2
+ def assert_required_keys(*args)
3
+ missing_args = args.reject{|arg| self.key?(arg) }
4
+ raise ArgumentError.new("Missing keys: #{missing_args.join(', ')}") if missing_args.any?
5
+ self
6
+ end
7
+
2
8
  ## Returns selected keys, named or renamed as specified
3
9
  # myproduct = {:name => "something", :price => "20"}
4
- # liability = myproduct.tap(&{:cost => :price})
10
+ # liability = myproduct.tap(&{:cost => :price}.to_transformer)
5
11
  # liability[:cost] == 20
6
12
  ## Note: Tap only works where source is a hash object, so use as otherwise
7
13
  # (because tap always returns the object you are tapping)
8
14
  # myproduct = ::OpenStruct.new({:name => "something", :price => "20"})
9
- # liability.as!(&{:cost => :price})
15
+ # liability.as!({:cost => :price})
10
16
  # liability[:cost] == 20
11
17
  #
12
18
  # Transforming values:
13
19
  # Pass a hash as the value with the key being the source key/method
14
20
  # myproduct = ::OpenStruct.new({:name => "something", :price => "20"})
15
- # my_sale_product = myproduct.as!(&{:sale_price => {:price => ->(val){ val / 2 } } })
21
+ # my_sale_product = myproduct.as!({:sale_price => {:price => ->(val){ val / 2 } } })
16
22
  # my_sale_product[:sale_price] == 10
17
23
 
18
- def to_proc
24
+ def to_transformer
19
25
  ->(hash_or_object) {
20
26
  new_hash = {}
21
27
 
@@ -48,3 +54,7 @@ class Hash
48
54
  }
49
55
  end
50
56
  end
57
+
58
+ class Hash
59
+ include HashExtensions
60
+ end
@@ -0,0 +1,95 @@
1
+ module MethodExtensions
2
+ # method(method_name).parameters returns an array of the parameters it accepts as well as the signature type
3
+ # :req = required, ordinal argument, i.e. def foo(one)
4
+ # :opt = optional ordinal argument, i.e. def foo(one=nil)
5
+ # :keyreq = required keyword argument i.e. def foo(req:)
6
+ # :key = optional keyword argument, i.e. def foo(one:nil)
7
+ # :rest = optional arguments splat, i.e. def foo(*args)
8
+ # :keyrest = optional keyword arguments splat, i.e. def foo(**args)
9
+
10
+ STRATEGIES_FOR_SEND_WHEN_METHOD = {
11
+ :accepts_nothing? => :strategy_for_method_without_arguments,
12
+ :accepts_arguments_and_keywords? => :strategy_for_method_with_arguments_and_keywords,
13
+ :accepts_arguments? => :strategy_for_method_with_arguments,
14
+ :accepts_keywords? => :strategy_for_method_with_keywords
15
+ }.freeze
16
+
17
+ def accepted_argument_signatures
18
+ @accepted_argument_signatures ||= self.parameters.any? ? self.parameters.map(&:first).uniq : []
19
+ end
20
+
21
+ def accepts_something?
22
+ @accepts_something ||= arity != 0
23
+ end
24
+
25
+ def accepts_nothing?
26
+ !accepts_something?
27
+ end
28
+
29
+ def accepts_arguments?
30
+ @accepts_arguments ||= requires_arguments? || accepts_optional_arguments? || accepts_arguments_splat?
31
+ end
32
+
33
+ def accepts_keywords?
34
+ @accepts_keywords ||= requires_keywords? || accepts_optional_keywords? || accepts_keywords_splat?
35
+ end
36
+
37
+ def accepts_arguments_and_keywords?
38
+ @accepts_arguments_and_keywords ||= accepts_arguments? && accepts_keywords?
39
+ end
40
+
41
+ def accepts_arguments_splat?
42
+ @accepts_arguments_splat ||= accepted_argument_signatures.include?(:rest)
43
+ end
44
+
45
+ def accepts_keywords_splat?
46
+ @accepts_keywords_splat ||= accepted_argument_signatures.include?(:keyrest)
47
+ end
48
+
49
+ def accepts_optional_arguments?
50
+ @accepts_optional_arguments ||= accepted_argument_signatures.include?(:opt)
51
+ end
52
+
53
+ def accepts_optional_keywords?
54
+ @accepts_optional_keywords ||= accepted_argument_signatures.include?(:key)
55
+ end
56
+
57
+ def execute_call_strategy(*args, **options)
58
+ __send__(strategy_for_call)
59
+ end
60
+
61
+ def requires_arguments?
62
+ @requires_arguments ||= accepted_argument_signatures.include?(:req)
63
+ end
64
+
65
+ def requires_keywords?
66
+ @requires_keywords ||= accepted_argument_signatures.include?(:keyreq)
67
+ end
68
+
69
+ def strategy_for_method_without_arguments(*args, **options)
70
+ call()
71
+ end
72
+
73
+ def strategy_for_method_with_keywords(*args, **options)
74
+ call(**options)
75
+ end
76
+
77
+ def strategy_for_method_with_arguments(*args, **options)
78
+ call(*args)
79
+ end
80
+
81
+ def strategy_for_method_with_arguments_and_keywords(*args, **options)
82
+ call(*args, **options)
83
+ end
84
+
85
+ def strategy_for_call
86
+ @strategy_for_call ||= begin
87
+ first_matching_question = STRATEGIES_FOR_SEND_WHEN_METHOD.keys.detect{ |k| send(k) }
88
+ STRATEGIES_FOR_SEND_WHEN_METHOD[first_matching_question]
89
+ end
90
+ end
91
+ end
92
+
93
+ class Method
94
+ include MethodExtensions
95
+ end
@@ -1,7 +1,12 @@
1
1
  require "active_support/core_ext/object/try"
2
2
  class Object
3
- def as!
4
- yield self
3
+ def __smartsend__(method_name, *args, **options)
4
+ target = method(method_name)
5
+ target.execute_call_strategy(*args, **options)
6
+ end
7
+
8
+ def as!(h)
9
+ h.to_transformer.call(self)
5
10
  end
6
11
 
7
12
  # Defines a Configuration Class within a target module namespace, or nested class
@@ -38,6 +38,10 @@ module Trax
38
38
  @enums ||= by_type(:enum)
39
39
  end
40
40
 
41
+ def key?(k)
42
+ all.key?(k)
43
+ end
44
+
41
45
  def structs
42
46
  @structs ||= by_type(:struct)
43
47
  end
@@ -21,7 +21,11 @@ module Trax
21
21
 
22
22
  mixin_module = base.const_set("Mixin", ::Module.new)
23
23
  mixin_module.module_attribute(:mixin_namespace) { base }
24
- mixin_module.extend(::Trax::Core::Mixin)
24
+
25
+ # NOTE: This line causes specs to fail, because it loads before
26
+ # ::Trax::Core::Definitions. It's currently not being used by any other
27
+ # Trax gems, so we'll have to revisit this whenever they start using it
28
+ #mixin_module.extend(::Trax::Core::Mixin)
25
29
 
26
30
  mixin_module.module_eval do
27
31
  def self.extended(base)
@@ -0,0 +1,244 @@
1
+ module Trax
2
+ module Core
3
+ class Transformer < SimpleDelegator
4
+ attr_reader :input, :parent, :output
5
+
6
+ def self.inherited(subklass)
7
+ subklass.class_attribute :properties
8
+ subklass.properties = {}.with_indifferent_access
9
+ subklass.class_attribute :after_initialize_callbacks
10
+ subklass.after_initialize_callbacks = ::Set.new
11
+ subklass.class_attribute :after_transform_callbacks
12
+ subklass.after_transform_callbacks = ::Set.new
13
+ end
14
+
15
+ def self.after_initialize(&block)
16
+ after_initialize_callbacks << block
17
+ end
18
+
19
+ def self.after_transform(&block)
20
+ after_transform_callbacks << block
21
+ end
22
+
23
+ def self.properties_with_default_values
24
+ @properties_with_default_values ||= properties.values.select{ |prop| prop.try(:default) }
25
+ end
26
+
27
+ def self.nested_properties
28
+ @nested_properties ||= properties.values.select{|prop| prop.is_nested? }
29
+ end
30
+
31
+ def self.transformer_properties
32
+ @transformer_properties ||= properties.values.select{|prop| prop.ancestors.include?(::Trax::Core::Transformer) }
33
+ end
34
+
35
+ def self.transformer_properties_with_after_transform_callbacks
36
+ @transformer_properties_with_after_transform_callbacks ||= transformer_properties.select{|prop| prop.after_transform_callbacks.any? }
37
+ end
38
+
39
+ def self.is_nested?
40
+ !!self.try(:parent_definition)
41
+ end
42
+
43
+ def self.from_parent?
44
+ false
45
+ end
46
+
47
+ def self.property(_property_name, **options, &block)
48
+ options[:parent_definition] = self
49
+ options[:property_name] = _property_name
50
+ options[:with] = block if block_given?
51
+ transformer_klass_name = "#{name}::#{_property_name.camelize}"
52
+ transformer_klass = ::Trax::Core::NamedClass.new(transformer_klass_name, Property, **options)
53
+ self.properties[_property_name] = transformer_klass
54
+ end
55
+
56
+ def self.transformer(_property_name, **options, &block)
57
+ options[:parent_definition] = self
58
+ options[:property_name] = _property_name
59
+ options[:default] = ->(){ {}.with_indifferent_access } unless options.key?(:default)
60
+ options[:with] = block if block_given?
61
+ transformer_klass_name = "#{name}::#{_property_name.camelize}"
62
+ transformer_klass = ::Trax::Core::NamedClass.new(transformer_klass_name, Transformer, **options, &block)
63
+ self.properties[_property_name] = transformer_klass
64
+ end
65
+
66
+ def self.nested(*args, **options, &block)
67
+ transformer(*args, **options, &block)
68
+ end
69
+
70
+ def self.fetch_property_from_object(_property, obj)
71
+ if _property.include?('/')
72
+ property_chain = _property.split('/')
73
+ obj.dig(*property_chain)
74
+ else
75
+ obj[_property]
76
+ end
77
+ end
78
+
79
+ def initialize(obj={}, parent=nil)
80
+ @input = obj.dup
81
+ @output = {}.with_indifferent_access
82
+ @parent = parent if parent
83
+
84
+ initialize_output_properties
85
+ initialize_default_values
86
+ run_after_initialize_callbacks if run_after_initialize_callbacks?
87
+ run_after_transform_callbacks if run_after_transform_callbacks?
88
+ end
89
+
90
+ def [](_property)
91
+ if _property.include?('/')
92
+ property_chain = _property.split('/')
93
+ self.dig(*property_chain)
94
+ else
95
+ super(_property)
96
+ end
97
+ end
98
+
99
+ def key?(_property)
100
+ if _property.include?('/')
101
+ property_chain = _property.split('/')
102
+ !!self.dig(*property_chain)
103
+ else
104
+ super(_property)
105
+ end
106
+ end
107
+
108
+ def has_parent?
109
+ !!parent
110
+ end
111
+
112
+ def parent_key?(k)
113
+ return false unless has_parent?
114
+
115
+ parent.key?(k)
116
+ end
117
+
118
+ def __getobj__
119
+ @output
120
+ end
121
+
122
+ def to_hash
123
+ @to_hash ||= begin
124
+ duplicate_hash = self.__getobj__.dup
125
+
126
+ duplicate_hash.each_pair do |k, v|
127
+ if v.is_a?(::Trax::Core::Transformer)
128
+ duplicate_hash[k] = v.__getobj__
129
+ elsif v.is_a?(Property)
130
+ duplicate_hash[k] = v.__getobj__
131
+ end
132
+ end
133
+
134
+ duplicate_hash
135
+ end
136
+ end
137
+
138
+ def to_recursive_hash
139
+ @to_recursive_hash ||= begin
140
+ duplicate_hash = self.__getobj__.dup
141
+
142
+ self.each_pair do |k, v|
143
+ if v.is_a?(::Trax::Core::Transformer)
144
+ duplicate_hash[k] = v.to_hash
145
+ elsif v.is_a?(Property)
146
+ duplicate_hash[k] = v.__getobj__
147
+ end
148
+ end
149
+
150
+ duplicate_hash
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ def initialize_default_values
157
+ self.class.properties_with_default_values.each do |prop|
158
+ unless @output.key?(prop.property_name)
159
+ if prop.default.is_a?(Proc)
160
+ @output[prop.property_name] = prop.default.arity > 0 ? prop.default.call(@output) : prop.default.call
161
+ else
162
+ @output[prop.property_name] = prop.default
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ def initialize_output_properties
169
+ self.class.properties.each_pair do |k,property_klass|
170
+ if @input.key?(property_klass.property_name)
171
+ value = @input[property_klass.property_name]
172
+ @output[property_klass.property_name] = property_klass.new(value, self)
173
+ elsif @input.key?(property_klass.try(:from))
174
+ value = @input[property_klass.from]
175
+ @output[property_klass.property_name] = property_klass.new(value, self)
176
+ elsif property_klass.from_parent?
177
+ value = self.class.fetch_property_from_object(property_klass.from_parent, self.parent.input)
178
+ @output[property_klass.property_name] = property_klass.new(value, self)
179
+ elsif property_klass.ancestors.include?(::Trax::Core::Transformer)
180
+ value = if property_klass.default.is_a?(Proc)
181
+ property_klass.default.arity > 0 ? property_klass.default.call(self) : property_klass.default.call
182
+ else
183
+ property_klass.default
184
+ end
185
+
186
+ @output[property_klass.property_name] = property_klass.new(value, self)
187
+ end
188
+ end
189
+ end
190
+
191
+ #will not transform output based on callback result
192
+ def run_after_initialize_callbacks
193
+ self.class.after_initialize_callbacks.each do |callback|
194
+ @output.instance_eval(&callback)
195
+ end
196
+ end
197
+
198
+ def run_after_initialize_callbacks?
199
+ self.class.after_initialize_callbacks.any?
200
+ end
201
+
202
+ #will transform output with return of each callback
203
+ def run_after_transform_callbacks
204
+ self.class.after_transform_callbacks.each do |callback|
205
+ @output = self.instance_exec(@output, &callback)
206
+ end
207
+ end
208
+
209
+ def run_after_transform_callbacks?
210
+ self.class.after_transform_callbacks.any?
211
+ end
212
+ end
213
+
214
+ class Property < SimpleDelegator
215
+ def self.is_nested?
216
+ @is_nested ||= !!self.try(:parent_definition)
217
+ end
218
+
219
+ def self.is_translated?
220
+ @is_translated ||= !!self.try(:from)
221
+ end
222
+
223
+ def self.from_parent?
224
+ @from_parent ||= !!try(:from_parent)
225
+ end
226
+
227
+ def initialize(value, transformer)
228
+ @value = value
229
+
230
+ if self.class.try(:with)
231
+ @value = self.class.with.arity > 1 ? self.class.with.call(@value, transformer) : self.class.with.call(@value)
232
+ end
233
+ end
234
+
235
+ def nil?
236
+ __getobj__.nil?
237
+ end
238
+
239
+ def __getobj__
240
+ @value
241
+ end
242
+ end
243
+ end
244
+ end
@@ -9,6 +9,7 @@ module Trax
9
9
  def self.to_schema
10
10
  result = super
11
11
  result[:values] = [true, false]
12
+ result
12
13
  end
13
14
  end
14
15
  end
@@ -15,7 +15,7 @@ module Trax
15
15
  class_attribute :allow_nil, :raise_on_invalid
16
16
 
17
17
  ### Class Methods ###
18
- def self.define_enum_value(const_name, val=nil, **attributes)
18
+ def self.define_enum_value(const_name, val=nil, **attributes, &block)
19
19
  name = "#{const_name}".underscore.to_sym
20
20
  const_name = name.to_s.camelize
21
21
  val = (self._values_hash.length + 1) if val.nil?
@@ -23,11 +23,8 @@ module Trax
23
23
  raise ::Trax::Core::Errors::DuplicateEnumValue.new(:klass => self.class.name, :value => const_name) if self === name
24
24
  raise ::Trax::Core::Errors::DuplicateEnumValue.new(:klass => self.class.name, :value => val) if self === val
25
25
 
26
- value_klass = ::Trax::Core::NamedClass.new("#{self.name}::#{const_name}", ::Trax::Core::Types::EnumValue){
27
- self.tag = name
28
- self.value = val
29
- self.attributes = attributes
30
- }
26
+ value_klass_class_attributes = {:tag => name, :value => val, :attributes => attributes}
27
+ value_klass = ::Trax::Core::NamedClass.new("#{self.name}::#{const_name}", ::Trax::Core::Types::EnumValue, **value_klass_class_attributes, &block)
31
28
 
32
29
  self._values_hash[val] = value_klass
33
30
  self._names_hash[name] = value_klass
@@ -134,13 +131,15 @@ module Trax
134
131
  end
135
132
 
136
133
  def self.to_schema
137
- ::Trax::Core::Definition.new(
134
+ result = ::Trax::Core::Definition.new(
138
135
  :name => self.name.demodulize.underscore,
139
136
  :source => self.name,
140
- :type => :enum,
137
+ :type => self.type,
141
138
  :choices => choices.map(&:to_schema),
142
139
  :values => keys
143
140
  )
141
+ result[:default] = self.default if self.respond_to?(:default)
142
+ result
144
143
  end
145
144
 
146
145
  class << self
@@ -2,13 +2,6 @@ module Trax
2
2
  module Core
3
3
  module Types
4
4
  class EnumValue
5
- def self.inherited(subclass)
6
- super(subclass)
7
- self.class_attribute(:tag)
8
- self.class_attribute(:value)
9
- self.class_attribute(:attributes)
10
- end
11
-
12
5
  def self.as_json(options={})
13
6
  tag.to_s
14
7
  end
@@ -42,12 +35,13 @@ module Trax
42
35
  :source => self.name,
43
36
  :name => to_s,
44
37
  :type => :enum_value,
45
- :integer_value => to_i
38
+ :integer_value => to_i,
39
+ :attributes => attributes
46
40
  )
47
41
  end
48
42
 
49
43
  def self.inspect
50
- ":#{tag}"
44
+ tag ? ":#{tag}" : super
51
45
  end
52
46
 
53
47
  def self.include?(val)
@@ -60,7 +54,7 @@ module Trax
60
54
  end
61
55
 
62
56
  def self.===(val)
63
- [tag, to_s, to_i].include?(val)
57
+ [::Trax::Core::Types::Enum, tag, to_s, to_i].include?(val)
64
58
  end
65
59
  end
66
60
  end
@@ -5,6 +5,14 @@ module Trax
5
5
  def self.type
6
6
  :json
7
7
  end
8
+
9
+ def self.to_schema
10
+ ::Trax::Core::Definition.new(
11
+ :name => self.name.demodulize.underscore,
12
+ :source => self.name,
13
+ :type => self.type
14
+ )
15
+ end
8
16
  end
9
17
  end
10
18
  end
@@ -19,11 +19,11 @@ module Trax
19
19
  :array_of => [],
20
20
  :boolean => nil,
21
21
  :enum => nil,
22
- :float => 0.0,
22
+ :float => nil,
23
23
  :integer => nil,
24
24
  :json => {},
25
25
  :set => [],
26
- :string => "",
26
+ :string => nil,
27
27
  :struct => {},
28
28
  :time => nil
29
29
  }.with_indifferent_access.freeze
@@ -134,10 +134,51 @@ module Trax
134
134
  alias :time :time_property
135
135
  end
136
136
 
137
+ def reverse_merge(other_hash)
138
+ self.class.new(other_hash).merge(self)
139
+ end
140
+
141
+ def reverse_merge!(other_hash)
142
+ self.class.new(other_hash).merge!(self)
143
+ end
144
+
145
+ def reverse_merge_present_values_only
146
+ other = other_hash.delete_if{|k,v| v.nil? || self.value_present_for_key?(k) }
147
+ other.keys.each_with_object(self) do |k, result|
148
+ self.delete(k) if !self.value_present_for_key?(k)
149
+ end
150
+
151
+ self.merge(other)
152
+ end
153
+
154
+ def reverse_merge_present_values_only!(other_hash)
155
+ other = other_hash.delete_if{|k,v| v.nil? || self.value_present_for_key?(k) }
156
+ other.keys.each_with_object(self) do |k, result|
157
+ self.delete(k) if !self.value_present_for_key?(k)
158
+ end
159
+
160
+ self.merge!(other)
161
+ end
162
+
137
163
  def value
138
164
  self
139
165
  end
140
166
 
167
+ def value_present_for_key?(k)
168
+ case self.class.fields_module.all[k].type
169
+ when :struct
170
+ !self.__send__(k).empty?
171
+ when :boolean
172
+ !self.__send__(k).nil?
173
+ when :array, :set
174
+ self.__send__(k) && self.__send__(k).length > 0
175
+ when :string
176
+ self.__send__(k).present?
177
+ else
178
+ !self.__send__(k).nil?
179
+ end
180
+ end
181
+
141
182
  private
142
183
 
143
184
  #By default, strings/int/bool wont get cast to value objects
@@ -146,8 +187,8 @@ module Trax
146
187
  name = name.is_a?(::Symbol) ? name.to_s : name
147
188
  klass_name = "#{fields_module.name.underscore}/#{property_name}".camelize
148
189
 
149
- attribute_klass = if options.key?(:extend)
150
- _klass_prototype = options[:extend].is_a?(::String) ? options[:extend].safe_constantize : options[:extend]
190
+ attribute_klass = if options.key?(:extends)
191
+ _klass_prototype = options[:extends].is_a?(::String) ? options[:extends].safe_constantize : options[:extends]
151
192
  _klass = ::Trax::Core::NamedClass.new(klass_name, _klass_prototype, :parent_definition => self, **options, &block)
152
193
  _klass
153
194
  else
@@ -10,6 +10,10 @@ module Trax
10
10
  @value
11
11
  end
12
12
 
13
+ def nil?
14
+ @value.nil?
15
+ end
16
+
13
17
  def self.symbolic_name
14
18
  name.demodulize.underscore.to_sym
15
19
  end
@@ -19,11 +23,13 @@ module Trax
19
23
  end
20
24
 
21
25
  def self.to_schema
22
- ::Trax::Core::Definition.new(
26
+ result = ::Trax::Core::Definition.new(
23
27
  :name => self.name.demodulize.underscore,
24
28
  :source => self.name,
25
29
  :type => self.type
26
30
  )
31
+ result[:default] = self.default if self.respond_to?(:default)
32
+ result
27
33
  end
28
34
  end
29
35
  end