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