lib/materialist/materializer.rb in materialist-3.0.0 vs lib/materialist/materializer.rb in materialist-3.1.0
- old
+ new
@@ -1,8 +1,6 @@
-require 'active_support/inflector'
-require 'routemaster/api_client'
-require_relative './event_worker'
+require_relative './materializer/internals'
module Materialist
module Materializer
def self.included(base)
@@ -13,253 +11,8 @@
base.instance_variable_set(:@__materialist_options, {
mapping: root_mapping,
links_to_materialize: {}
})
base.instance_variable_set(:@__materialist_dsl_mapping_stack, [root_mapping])
- end
-
- module Internals
- class FieldMapping
- def initialize(key:, as:)
- @key = key
- @as = as
- end
-
- attr_reader :key, :as
- end
-
- class LinkMapping
- def initialize(key:)
- @key = key
- @mapping = []
- end
-
- attr_reader :key, :mapping
- end
-
- class LinkHrefMapping
- def initialize(key:, as:)
- @key = key
- @as = as
- end
-
- attr_reader :key, :as
- end
-
- module ClassMethods
- attr_reader :__materialist_options, :__materialist_dsl_mapping_stack
-
- def perform(url, action)
- materializer = Materializer.new(url, self)
- action == :delete ? materializer.destroy : materializer.upsert
- end
- end
-
- module DSL
-
- def materialize_link(key, topic: key)
- __materialist_options[:links_to_materialize][key] = { topic: topic }
- end
-
- def capture(key, as: key)
- __materialist_dsl_mapping_stack.last << FieldMapping.new(key: key, as: as)
- end
-
- def capture_link_href(key, as:)
- __materialist_dsl_mapping_stack.last << LinkHrefMapping.new(key: key, as: as)
- end
-
- def link(key)
- link_mapping = LinkMapping.new(key: key)
- __materialist_dsl_mapping_stack.last << link_mapping
- __materialist_dsl_mapping_stack << link_mapping.mapping
- yield
- __materialist_dsl_mapping_stack.pop
- end
-
- def persist_to(klass)
- __materialist_options[:model_class] = klass
- end
-
- def source_key(key, &url_parser_block)
- __materialist_options[:source_key] = key
- __materialist_options[:url_parser] = url_parser_block
- end
-
- def before_upsert(*method_array)
- __materialist_options[:before_upsert] = method_array
- end
-
- def after_upsert(*method_array)
- __materialist_options[:after_upsert] = method_array
- end
-
- def after_destroy(*method_array)
- __materialist_options[:after_destroy] = method_array
- end
-
- def before_destroy(*method_array)
- __materialist_options[:before_destroy] = method_array
- end
- end
-
- class Materializer
-
- def initialize(url, klass)
- @url = url
- @instance = klass.new
- @options = klass.__materialist_options
- end
-
- def upsert(retry_on_race_condition: true)
- return unless root_resource
-
- if materialize_self?
- upsert_record.tap do |entity|
- send_messages(after_upsert, entity) unless after_upsert.nil?
- end
- end
-
- materialize_links
- rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
- # when there is a race condition and uniqueness of :source_url
- # is enforced by database index, this error is raised
- # so we simply try upsert again
- # if error is due to another type of uniqueness constraint
- # second call will also fail and error would bubble up
- retry_on_race_condition ?
- upsert(retry_on_race_condition: false) :
- raise
- end
-
- def destroy
- return unless materialize_self?
- model_class.find_by(source_lookup(url)).tap do |entity|
- send_messages(before_destroy, entity) unless before_destroy.nil?
- entity.destroy!.tap do |entity|
- send_messages(after_destroy, entity) unless after_destroy.nil?
- end if entity
- end
- end
-
- private
-
- attr_reader :url, :instance, :options
-
- def materialize_self?
- options.include? :model_class
- end
-
- def upsert_record
- model_class.find_or_initialize_by(source_lookup(url)).tap do |entity|
- send_messages(before_upsert, entity) unless before_upsert.nil?
- entity.update_attributes! attributes
- end
- end
-
- def materialize_links
- (options[:links_to_materialize] || [])
- .each { |key, opts| materialize_link(key, opts) }
- end
-
- def materialize_link(key, opts)
- return unless root_resource.body._links.include?(key)
-
- # this can't happen asynchronously
- # because the handler options are unavailable in this context
- # :(
- ::Materialist::EventWorker.new.perform({
- 'topic' => opts[:topic],
- 'url' => root_resource.body._links[key].href,
- 'type' => 'noop'
- })
- end
-
- def mapping
- options.fetch :mapping
- end
-
- def before_upsert
- options[:before_upsert]
- end
-
- def after_upsert
- options[:after_upsert]
- end
-
- def before_destroy
- options[:before_destroy]
- end
-
- def after_destroy
- options[:after_destroy]
- end
-
- def model_class
- options.fetch(:model_class).to_s.camelize.constantize
- end
-
- def source_key
- options.fetch(:source_key, :source_url)
- end
-
- def url_parser
- options[:url_parser] || ->url { url }
- end
-
- def source_lookup(url)
- @_source_lookup ||= { source_key => url_parser.call(url) }
- end
-
- def attributes
- build_attributes root_resource, mapping
- end
-
- def root_resource
- @_root_resource ||= resource_at(url)
- end
-
- def build_attributes(resource, mapping)
- return {} unless resource
-
- mapping.inject({}) do |result, m|
- case m
- when FieldMapping
- result.tap { |r| r[m.as] = resource.body[m.key] }
- when LinkHrefMapping
- result.tap do |r|
- if resource.body._links.include?(m.key)
- r[m.as] = resource.body._links[m.key].href
- end
- end
- when LinkMapping
- resource.body._links.include?(m.key) ?
- result.merge(build_attributes(resource_at(resource.send(m.key).url), m.mapping || [])) :
- result
- else
- result
- end
- end
- end
-
- def resource_at(url)
- api_client.get(url, options: { enable_caching: false })
- rescue Routemaster::Errors::ResourceNotFound
- # this is due to a race condition between an upsert event
- # and a :delete event
- # when this happens we should silently ignore the case
- nil
- end
-
- def api_client
- @_api_client ||= Routemaster::APIClient.new(
- response_class: Routemaster::Responses::HateoasResponse
- )
- end
-
- def send_messages(messages, arguments)
- messages.each { |message| instance.send(message, arguments) }
- end
- end
end
end
end