lib/ecoportal/api/common/content/double_model.rb in ecoportal-api-v2-0.9.7 vs lib/ecoportal/api/common/content/double_model.rb in ecoportal-api-v2-1.0.1

- old
+ new

@@ -16,10 +16,13 @@ msg += " key: #{key}." if key super(msg) end end + class NoKeyMethod < StandardError + end + class << self attr_reader :key def key? !!key @@ -169,11 +172,11 @@ klass.uniq = uniq end define_method method do return instance_variable_get(var) if instance_variable_defined?(var) - new_obj = dim_class.new(parent: self, key: method) + new_obj = dim_class.new(parent: self, key: method, read_only: self._read_only) variable_set(var, new_obj) end end end @@ -192,11 +195,14 @@ # @param method [Symbol] the method that exposes the embeded object # @param key [Symbol] the `key` that embeds it to the underlying `Hash` model # @param order_matters [Boolean] to state if the order will matter # @param klass [Class, String] the class of the individual elements it embeds # @param enum_class [Class, String] the class of the collection that will hold the individual elements - def embeds_many(method, key: method, order_matters: false, order_key: nil, klass: nil, enum_class: nil) + # @param read_only [Boolean] whether or not should try to **work around** items `klass` missing a `key` + # - If set to `true` this is meant only for read purposes (won't be able to successufully insert) + def embeds_many(method, key: method, klass: nil, enum_class: nil, + order_matters: false, order_key: nil, read_only: false) if enum_class eclass = enum_class elsif klass eclass = new_class("#{method}::#{klass}", inherits: Common::Content::CollectionModel) do |dim_class| dim_class.klass = klass @@ -204,21 +210,21 @@ dim_class.order_key = order_key end else raise "You should either specify the 'klass' of the elements or the 'enum_class'" end - embed(method, key: key, multiple: true, klass: eclass) do |instance_with_called_method| + embed(method, key: key, multiple: true, klass: eclass, read_only: read_only) do |instance_with_called_method| # keep reference to the original class to resolve the `klass` dependency # See stackoverflow: https://stackoverflow.com/a/73709529/4352306 referrer_class = instance_with_called_method.class eclass.klass = {referrer_class => klass} if klass end end private - def embed(method, key: method, nullable: false, multiple: false, klass:, &block) + def embed(method, key: method, nullable: false, multiple: false, klass:, read_only: false, &block) method = method.to_s.freeze var = instance_variable_name(method).freeze k = key.to_s.freeze # retrieving method (getter) @@ -228,13 +234,26 @@ unless nullable doc[k] ||= multiple ? [] : {} end return variable_set(var, nil) unless doc[k] - self.class.resolve_class(klass).new( - doc[k], parent: self, key: k - ).tap {|obj| variable_set(var, obj)} + embedded_class = self.class.resolve_class(klass) + + if multiple && read_only + if doc[k].is_a?(Array) && embedded_class < Common::Content::CollectionModel + if (item_class = embedded_class.klass) && !item_class.key? + item_class.passkey :id + doc[k].each_with_index do |item_doc, i| + item_doc["id"] = "#{i}" unless item_doc.key?("id") + end + end + end + end + + embedded_class.new(doc[k], parent: self, key: k, read_only: self._read_only || read_only).tap do |obj| + variable_set(var, obj) + end end end # The list of keys that will be forced in the model def model_forced_keys @@ -242,20 +261,21 @@ end end inheritable_class_vars :forced_model_keys, :key - attr_reader :_parent, :_key + attr_reader :_parent, :_key, :_read_only - def initialize(doc = {}, parent: self, key: nil) - @_dim_vars = [] - @_parent = parent || self - @_key = key || self + def initialize(doc = {}, parent: self, key: nil, read_only: false) + @_dim_vars = [] + @_parent = parent || self + @_key = key || self + @_read_only = read_only self.class.enforce!(doc) - if _parent == self + if (_parent == self) || read_only @doc = doc @original_doc = JSON.parse(@doc.to_json) end if key_method? && doc && doc.is_a?(Hash) @@ -269,17 +289,17 @@ _parent.root end # @return [String] the `value` of the `key` method (i.e. `id` value) def key - raise "No key_method defined for #{self.class}" unless key_method? + raise NoKeyMethod.new "No key_method defined for #{self.class}" unless key_method? self.method(key_method).call end # @param [String] the `value` of the `key` method (i.e. `id` value) def key=(value) - raise "No key_method defined for #{self.class}" unless key_method? + raise NoKeyMethod.new "No key_method defined for #{self.class}" unless key_method? method = "#{key_method}=" self.method(method).call(value) end # Offers a method for child classes to transform the key, @@ -294,10 +314,11 @@ end end # @return [nil, Hash] the underlying `Hash` model as is (carrying current changes) def doc + return @doc if doc_var? raise UnlinkedModel.new(from: "#{self.class}#doc", key: _key) unless linked? if is_root? @doc else _parent.doc.dig(*[_doc_key(_key)].flatten) @@ -376,11 +397,15 @@ end end protected + def doc_var? + !!defined?(@doc) + end + def is_root? - _parent == self && !!defined?(@doc) + _parent == self && doc_var? end def linked? is_root? || !!_parent.doc end