lib/hcloud/entry_loader.rb in hcloud-0.1.2 vs lib/hcloud/entry_loader.rb in hcloud-1.0.0

- old
+ new

@@ -1,36 +1,197 @@ +# frozen_string_literal: true + require 'active_support/concern' +require 'active_model' + module Hcloud - module EntryLoader + module EntryLoader # rubocop:disable Metrics/ModuleLength extend ActiveSupport::Concern + module Collection + attr_accessor :response + end + included do |klass| - klass.send(:attr_reader, :parent, :client) - klass.const_get(:Attributes).each do |attribute, _value| - klass.send(:attr_accessor, attribute) - end + klass.send(:attr_writer, :client) + klass.include(ActiveModel::Dirty) end - def initialize(resource, parent, client) - @parent = parent - @client = client - self.class.const_get(:Attributes).each do |attribute, value| - case value - when nil - send("#{attribute}=", resource[attribute.to_s]) - when :time - unless resource[attribute.to_s].nil? - send("#{attribute}=", Time.parse(resource[attribute.to_s])) + class_methods do # rubocop:disable Metrics/BlockLength + attr_accessor :resource_url + + def _callbacks + @_callbacks ||= { before: [], after: [] } + end + + %i[before after].each do |method| + define_method(method) { |&block| _callbacks[method] << block } + end + + def schema(**kwargs) + @schema ||= {}.with_indifferent_access + return @schema if kwargs.empty? + + @schema.merge!(kwargs) + end + + def updatable(*args) + define_attribute_methods(*args) + args.each do |updateable_attribute| + define_method(updateable_attribute) { _attributes[updateable_attribute] } + define_method("#{updateable_attribute}=") do |value| + if _attributes[updateable_attribute] != value + public_send("#{updateable_attribute}_will_change!") + end + _update_attribute(updateable_attribute, value) end - else - if value.is_a?(Class) && value.include?(EntryLoader) - send("#{attribute}=", value.new(resource[attribute.to_s], self, client)) + end + end + + def destructible + define_method(:destroy) { prepare_request(method: :delete) } + end + + def protectable(*args) + define_method(:change_protection) do |**kwargs| + kwargs.keys.each do |key| + next if args.map(&:to_s).include? key.to_s + + raise ArgumentError, "#{key} not an allowed protection mode (allowed: #{args})" end + prepare_request('actions/change_protection', j: kwargs) end end + + def has_actions # rubocop:disable Naming/PredicateName + define_method(:actions) do + ActionResource.new(client: client, base_path: resource_url) + end + end + + def resource_class + ancestors.reverse.find { |const| const.include?(Hcloud::EntryLoader) } + end + + def from_response(response, autoload_action: nil) + attributes = response.resource_attributes + action = response.parsed_json[:action] if autoload_action + client = response.context.client + if attributes.is_a?(Array) + results = attributes.map { |item| new(item).tap { |entity| entity.response = response } } + results.tap { |ary| ary.extend(Collection) }.response = response + return results + end + + return Action.new(client, action) if attributes.nil? && action + return new(attributes).tap { |entity| entity.response = response } if action.nil? + + [ + Action.new(client, action), + new(attributes).tap { |entity| entity.response = response } + ] + end end - def request(*args) - client.request(*args) + attr_accessor :response + + def initialize(client = nil, **kwargs) + @client = client + _load(kwargs) + end + + def inspect + "#<#{self.class.name}:0x#{__id__.to_s(16)} #{_attributes.inspect}>" + end + + def client + @client || response&.context&.client + end + + def resource_url + if block = self.class.resource_url + return instance_exec(&block) + end + + [self.class.resource_class.name.demodulize.tableize, id].compact.join('/') + end + + def resource_path + self.class.resource_class.name.demodulize.underscore + end + + def prepare_request(url_suffix = nil, **kwargs, &block) + kwargs[:resource_path] ||= resource_path + kwargs[:resource_class] ||= self.class + kwargs[:autoload_action] = true unless kwargs.key?(:autoload_action) + + client.prepare_request( + [resource_url, url_suffix].compact.join('/'), + **kwargs, + &block + ) + end + + def _attributes + @_attributes ||= {}.with_indifferent_access + end + + def method_missing(method, *args, &block) + _attributes.key?(method) ? _attributes[method] : super + end + + def respond_to_missing?(method, *args, &block) + _attributes.key?(method) || super + end + + def update(**kwargs) + context = self + _run_callbacks(:before) + prepare_request(j: kwargs, method: :put) do |response| + response.resource_class.from_response( + response, + autoload_action: response.autoload_action + ).tap do |*_args| + _run_callbacks(:after) + context.send(:changes_applied) + end + end + end + + def save + update(changes.map { |key, _value| [key.to_sym, _attributes[key]] }.to_h) + end + + def rollback + restore_attributes + end + + def _run_callbacks(order) + self.class._callbacks[order].each { |block| instance_exec(&block) } + end + + def _update_attribute(key, value) + _attributes[key] = value + instance_variable_set("@#{key}", value) + end + + def _load(resource) + @_attributes = {}.with_indifferent_access + + resource.each do |key, value| + definition = self.class.schema[key] + + if definition == :time + _update_attribute(key, value ? Time.parse(value) : nil) + next + end + + if definition.is_a?(Class) && definition.include?(EntryLoader) + _update_attribute(key, value ? definition.new(client, value) : nil) + next + end + + _update_attribute(key, value.is_a?(Hash) ? value.with_indifferent_access : value) + end end end end