materialist 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ad0c95193ee887dc776cdb28f54fa1fd7a00212a
4
+ data.tar.gz: 6e3dfb760278e54a275acdc667f5b32fe1809374
5
+ SHA512:
6
+ metadata.gz: 54a84928a4d988d22741f038d02bc47757b331ff45f45cef201829691b204a239745a89abc9b67fac706f267fda054b0e65094ef1d62ac9836282e7ec608d448
7
+ data.tar.gz: 8758db7954c895a28ac969f4f166b570451f338a1a11d449ee9d9f8bae3e1dcc66cb57d1867af24bb9e3dcee1497d4c557411924a616171fbc2fd6580f2afb16
data/.env.test ADDED
@@ -0,0 +1,2 @@
1
+ ROUTEMASTER_DRAIN_REDIS=redis://localhost/drain-rider-api
2
+ ROUTEMASTER_CACHE_REDIS=redis://localhost/cache-rider-api
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ .bundle/
2
+ doc/
3
+ .yardoc/
4
+ pkg/
5
+ tmp
6
+ *.orig
7
+ tags
8
+ *.swp
9
+ *.swo
10
+ coverage/
11
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ -I.
2
+ --color
3
+ --format progress
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.0
data/Appraisals ADDED
@@ -0,0 +1,3 @@
1
+ appraise "rails-5" do
2
+ gem "rails", "~> 5.0"
3
+ end
data/Gemfile ADDED
@@ -0,0 +1,26 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in routemaster_client.gemspec
4
+ gemspec
5
+
6
+ gem 'activesupport'
7
+ gem 'sidekiq'
8
+ gem 'routemaster-drain', '~> 3.0'
9
+
10
+ # Just here to avoid a safety warning
11
+ gem 'psych', require: false
12
+
13
+ # Used in builds and tests
14
+ gem 'bundler', require: false
15
+ gem 'dotenv', require: false
16
+ gem 'simplecov', require: false
17
+ gem 'codecov', require: false
18
+ gem 'webmock', require: false
19
+
20
+ gem 'guard-rspec', require: false
21
+ gem 'pry', require: false
22
+ gem 'byebug', require: false
23
+ gem 'rspec', require: false
24
+ gem 'appraisal', require: false
25
+ gem 'dogstatsd', require: false
26
+ gem 'fork_break', require: false
data/Gemfile.lock ADDED
@@ -0,0 +1,165 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ materialist (0.0.1)
5
+ sidekiq
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (5.1.3)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (~> 0.7)
13
+ minitest (~> 5.1)
14
+ tzinfo (~> 1.1)
15
+ addressable (2.5.0)
16
+ public_suffix (~> 2.0, >= 2.0.2)
17
+ appraisal (2.2.0)
18
+ bundler
19
+ rake
20
+ thor (>= 0.14.0)
21
+ byebug (9.1.0)
22
+ codecov (0.1.10)
23
+ json
24
+ simplecov
25
+ url
26
+ coderay (1.1.1)
27
+ concurrent-ruby (1.0.5)
28
+ connection_pool (2.2.1)
29
+ crack (0.4.3)
30
+ safe_yaml (~> 1.0.0)
31
+ diff-lcs (1.3)
32
+ docile (1.1.5)
33
+ dogstatsd (2.0.0)
34
+ dotenv (2.2.1)
35
+ ethon (0.10.1)
36
+ ffi (>= 1.3.0)
37
+ faraday (0.13.1)
38
+ multipart-post (>= 1.2, < 3)
39
+ faraday_middleware (0.12.2)
40
+ faraday (>= 0.7.4, < 1.0)
41
+ ffi (1.9.18)
42
+ fork (1.0.1)
43
+ fork_break (0.1.4)
44
+ fork (= 1.0.1)
45
+ formatador (0.2.5)
46
+ guard (2.14.1)
47
+ formatador (>= 0.2.4)
48
+ listen (>= 2.7, < 4.0)
49
+ lumberjack (~> 1.0)
50
+ nenv (~> 0.1)
51
+ notiffany (~> 0.0)
52
+ pry (>= 0.9.12)
53
+ shellany (~> 0.0)
54
+ thor (>= 0.18.1)
55
+ guard-compat (1.2.1)
56
+ guard-rspec (4.7.3)
57
+ guard (~> 2.1)
58
+ guard-compat (~> 1.1)
59
+ rspec (>= 2.99.0, < 4.0)
60
+ hashdiff (0.3.2)
61
+ hashie (3.5.6)
62
+ i18n (0.8.6)
63
+ json (2.1.0)
64
+ listen (3.1.5)
65
+ rb-fsevent (~> 0.9, >= 0.9.4)
66
+ rb-inotify (~> 0.9, >= 0.9.7)
67
+ ruby_dep (~> 1.2)
68
+ lumberjack (1.0.12)
69
+ method_source (0.8.2)
70
+ minitest (5.10.3)
71
+ multipart-post (2.0.0)
72
+ nenv (0.3.0)
73
+ notiffany (0.1.1)
74
+ nenv (~> 0.1)
75
+ shellany (~> 0.0)
76
+ pry (0.10.4)
77
+ coderay (~> 1.1.0)
78
+ method_source (~> 0.8.1)
79
+ slop (~> 3.4)
80
+ psych (2.2.4)
81
+ public_suffix (2.0.5)
82
+ rack (2.0.3)
83
+ rack-protection (2.0.0)
84
+ rack
85
+ rake (12.0.0)
86
+ rb-fsevent (0.10.2)
87
+ rb-inotify (0.9.10)
88
+ ffi (>= 0.5.0, < 2)
89
+ redis (3.3.3)
90
+ redis-namespace (1.5.3)
91
+ redis (~> 3.0, >= 3.0.4)
92
+ routemaster-drain (3.0.1)
93
+ addressable
94
+ concurrent-ruby
95
+ faraday (>= 0.9.0)
96
+ faraday_middleware
97
+ hashie
98
+ rack (>= 1.4.5)
99
+ redis-namespace
100
+ typhoeus (~> 1.1)
101
+ wisper (~> 1.6.1)
102
+ rspec (3.6.0)
103
+ rspec-core (~> 3.6.0)
104
+ rspec-expectations (~> 3.6.0)
105
+ rspec-mocks (~> 3.6.0)
106
+ rspec-core (3.6.0)
107
+ rspec-support (~> 3.6.0)
108
+ rspec-expectations (3.6.0)
109
+ diff-lcs (>= 1.2.0, < 2.0)
110
+ rspec-support (~> 3.6.0)
111
+ rspec-mocks (3.6.0)
112
+ diff-lcs (>= 1.2.0, < 2.0)
113
+ rspec-support (~> 3.6.0)
114
+ rspec-support (3.6.0)
115
+ ruby_dep (1.5.0)
116
+ safe_yaml (1.0.4)
117
+ shellany (0.0.1)
118
+ sidekiq (5.0.4)
119
+ concurrent-ruby (~> 1.0)
120
+ connection_pool (~> 2.2, >= 2.2.0)
121
+ rack-protection (>= 1.5.0)
122
+ redis (~> 3.3, >= 3.3.3)
123
+ simplecov (0.15.0)
124
+ docile (~> 1.1.0)
125
+ json (>= 1.8, < 3)
126
+ simplecov-html (~> 0.10.0)
127
+ simplecov-html (0.10.2)
128
+ slop (3.6.0)
129
+ thor (0.20.0)
130
+ thread_safe (0.3.6)
131
+ typhoeus (1.3.0)
132
+ ethon (>= 0.9.0)
133
+ tzinfo (1.2.3)
134
+ thread_safe (~> 0.1)
135
+ url (0.3.2)
136
+ webmock (2.3.2)
137
+ addressable (>= 2.3.6)
138
+ crack (>= 0.3.2)
139
+ hashdiff
140
+ wisper (1.6.1)
141
+
142
+ PLATFORMS
143
+ ruby
144
+
145
+ DEPENDENCIES
146
+ activesupport
147
+ appraisal
148
+ bundler
149
+ byebug
150
+ codecov
151
+ dogstatsd
152
+ dotenv
153
+ fork_break
154
+ guard-rspec
155
+ materialist!
156
+ pry
157
+ psych
158
+ routemaster-drain (~> 3.0)
159
+ rspec
160
+ sidekiq
161
+ simplecov
162
+ webmock
163
+
164
+ BUNDLED WITH
165
+ 1.15.4
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: 'bundle exec rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
9
+ end
10
+
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2014-2016 HouseTrip Ltd.
2
+ Copyright (c) 2016 Deliveroo Ltd.
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ ## Materialist
2
+
3
+ > _adjective_ `philosophy`: relating to the theory that nothing exists except matter and its movements and modifications.
4
+
5
+ A "materializer" is a ruby class that is responsible for receiving an event and
6
+ materializing the remote resource (described by the event) in database.
7
+
8
+ This library is a set of utilities that provide both the wiring and the DSL to
9
+ painlessly do so.
10
+
11
+ ### Configuration
12
+
13
+ First you need an "event handler":
14
+
15
+ ```ruby
16
+ handler = Materialist::EventHandler.new({ ...options })
17
+ ```
18
+
19
+ Where options could be:
20
+
21
+ - `topics` (only when using in `.subscribe`): An array of topics to be used.
22
+ If not provided nothing would be materialized.
23
+ - `queue`: name of the queue to be used by sidekiq worker
24
+
25
+ Then there are two ways to configure materialist in routemaster:
26
+
27
+ 1. **If you DON'T need resources to be cached in redis:** use `handler` as siphon:
28
+
29
+ ```ruby
30
+ handler = Materialist::EventHandler.new
31
+ siphon_events = {
32
+ zones: handler,
33
+ rider_domain_riders: handler
34
+ }
35
+
36
+ app = Routemaster::Drain::Caching.new(siphon_events: siphon_events)
37
+ # ...
38
+
39
+ map '/events' do
40
+ run app
41
+ end
42
+ ```
43
+
44
+ 2. **You DO need resources cached in redis:** In this case you need to use `handler` to subscribe to the caching pipeline:
45
+
46
+ ```ruby
47
+ TOPICS = %w(
48
+ zones
49
+ rider_domain_riders
50
+ )
51
+
52
+ handler = Materialist::EventHandler.new({ topics: TOPICS })
53
+ app = Routemaster::Drain::Caching.new # or ::Basic.new
54
+ app.subscribe(handler, prefix: true)
55
+ # ...
56
+
57
+ map '/events' do
58
+ run app
59
+ end
60
+ ```
61
+
62
+ ### DSL
63
+
64
+ Next you would need to define a materializer for each of the topic. The name of
65
+ the materializer class should match the topic name (in singular)
66
+
67
+ These materializers would live in a first-class directory (`/materializers`) in your rails app.
68
+
69
+ ```ruby
70
+ require 'materialist/materializer'
71
+
72
+ class ZoneMaterializer
73
+ include Materialist::Materializer
74
+
75
+ use_model :zone
76
+
77
+ materialize :id, as: :orderweb_id
78
+ materialize :code
79
+ materialize :name
80
+
81
+ link :city do
82
+ materialize :tz_name, as: :timezone
83
+
84
+ link :country do
85
+ materialize :name, as: :country_name
86
+ materialize :iso_alpha2_code, as: :country_iso_alpha2_code
87
+ end
88
+ end
89
+ end
90
+ ```
91
+
92
+ Here is what each part of the DSL mean:
93
+
94
+ #### `use_model <model_name>`
95
+ describes the name of the active record model to be used.
96
+
97
+ #### `materialize <key>, as: <column> (default: key)`
98
+ describes mapping a resource key to database column.
99
+
100
+ #### `link <key>`
101
+ describes materializing from a relation of the resource. This can be nested to any depth as shown above.
102
+
103
+ When inside the block of a `link` any other part of DSL can be used and will be evaluated in the context of the relation resource.
104
+
105
+ #### `after_upsert <method>`
106
+ describes the name of the instance method to be invoked after a record was materialized.
107
+
108
+ ```ruby
109
+ class ZoneMaterializer
110
+ include Materialist::Materializer
111
+
112
+ after_upsert :my_method
113
+
114
+ def my_method(record)
115
+ end
116
+ end
117
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/appraise ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Run all appraisals, with all specified rubies
4
+ #
5
+ require 'yaml'
6
+
7
+
8
+ RUBIES = YAML.load_file('.travis.yml')['rvm']
9
+ APPRAISALS = `appraisal list`.strip.split(/\s+/)
10
+
11
+ # setup
12
+ RUBIES.each do |ruby|
13
+ ENV['RBENV_VERSION'] = ruby
14
+ system 'rbenv version'
15
+ system 'rbenv exec ruby -v'
16
+ system 'rbenv exec bundle check || rbenv exec bundle install'
17
+ system "rbenv exec appraisal install"
18
+ end
19
+
20
+ # tests
21
+ RUBIES.each do |ruby|
22
+ ENV['RBENV_VERSION'] = ruby
23
+ system 'rbenv exec ruby -v'
24
+ APPRAISALS.each do |variant|
25
+ puts "*** Ruby #{ruby} / variant #{variant}"
26
+ system "rbenv exec appraisal #{variant} rspec"
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Materialist
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'active_support/inflector'
2
+ require_relative './event_worker'
3
+
4
+ module Materialist
5
+ class EventHandler
6
+
7
+ def initialize(options={})
8
+ @options = options
9
+ end
10
+
11
+ def on_events_received(batch)
12
+ batch.each { |event| call(event) if topics.include?(event['topic'].to_s) }
13
+ end
14
+
15
+ def call(event)
16
+ worker.perform_async(event)
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :options
22
+
23
+ def topics
24
+ @_topics ||= options.fetch(:topics, []).map(&:to_s)
25
+ end
26
+
27
+ def worker
28
+ @_worker ||= Materialist::EventWorker.set(options.slice(:queue))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ require 'sidekiq'
2
+ require 'active_support/inflector'
3
+
4
+ module Materialist
5
+ class EventWorker
6
+ include Sidekiq::Worker
7
+
8
+ def perform(event)
9
+ topic = event['topic']
10
+ materializer = "#{topic.to_s.singularize.classify}Materializer".constantize
11
+ materializer.perform(event['url'], event['type'].to_sym)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,143 @@
1
+ require 'routemaster/api_client'
2
+
3
+ module Materialist
4
+ module Materializer
5
+
6
+ def self.included(base)
7
+ base.extend(Internals::ClassMethods)
8
+ base.extend(Internals::DSL)
9
+
10
+ root_mapping = []
11
+ base.instance_variable_set(:@materialist_options, { mapping: root_mapping })
12
+ base.instance_variable_set(:@__materialist_dsl_mapping_stack, [root_mapping])
13
+ end
14
+
15
+ module Internals
16
+ class FieldMapping
17
+ def initialize(key:, as:)
18
+ @key = key
19
+ @as = as
20
+ end
21
+
22
+ attr_reader :key, :as
23
+ end
24
+
25
+ class LinkMapping
26
+ def initialize(key:)
27
+ @key = key
28
+ @mapping = []
29
+ end
30
+
31
+ attr_reader :key, :mapping
32
+ end
33
+
34
+ module ClassMethods
35
+ attr_reader :materialist_options, :__materialist_dsl_mapping_stack
36
+
37
+ def perform(url, action)
38
+ materializer = Materializer.new(url, self)
39
+ action == :delete ? materializer.destroy : materializer.upsert
40
+ end
41
+ end
42
+
43
+ module DSL
44
+
45
+ def materialize(key, as: key)
46
+ __materialist_dsl_mapping_stack.last << FieldMapping.new(key: key, as: as)
47
+ end
48
+
49
+ def link(key)
50
+ link_mapping = LinkMapping.new(key: key)
51
+ __materialist_dsl_mapping_stack.last << link_mapping
52
+ __materialist_dsl_mapping_stack << link_mapping.mapping
53
+ yield
54
+ __materialist_dsl_mapping_stack.pop
55
+ end
56
+
57
+ def use_model(klass)
58
+ materialist_options[:model_class] = klass
59
+ end
60
+
61
+ def after_upsert(method_name)
62
+ materialist_options[:after_upsert] = method_name
63
+ end
64
+
65
+ end
66
+
67
+ class Materializer
68
+
69
+ def initialize(url, klass)
70
+ @url = url
71
+ @instance = klass.new
72
+ @options = klass.materialist_options
73
+ end
74
+
75
+ def upsert
76
+ upsert_record.tap do |entity|
77
+ instance.send(after_upsert, entity) if after_upsert
78
+ end
79
+ end
80
+
81
+ def destroy
82
+ model_class.where(source_url: url).destroy_all
83
+ end
84
+
85
+ private
86
+
87
+ attr_reader :url, :instance, :options
88
+
89
+ def upsert_record
90
+ model_class.find_or_initialize_by(source_url: url).tap do |entity|
91
+ entity.update_attributes attributes
92
+ entity.save!
93
+ end
94
+ end
95
+
96
+ def mapping
97
+ options.fetch :mapping
98
+ end
99
+
100
+ def after_upsert
101
+ options[:after_upsert]
102
+ end
103
+
104
+ def model_class
105
+ options.fetch(:model_class).to_s.classify.constantize
106
+ end
107
+
108
+ def attributes
109
+ build_attributes resource_at(url), mapping
110
+ end
111
+
112
+ def build_attributes(resource, mapping)
113
+ return {} unless resource
114
+
115
+ mapping.inject({}) do |result, m|
116
+ case m
117
+ when FieldMapping
118
+ result.tap { |r| r[m.as] = resource.body[m.key] }
119
+ when LinkMapping
120
+ resource.body._links.include?(m.key) ?
121
+ result.merge(build_attributes(resource_at(resource.send(m.key).url, allow_nil: true), m.mapping || [])) :
122
+ result
123
+ else
124
+ result
125
+ end
126
+ end
127
+ end
128
+
129
+ def resource_at(url, allow_nil: false)
130
+ api_client.get(url, options: { enable_caching: false })
131
+ rescue Routemaster::Errors::ResourceNotFound
132
+ raise unless allow_nil
133
+ end
134
+
135
+ def api_client
136
+ @_api_client ||= Routemaster::APIClient.new(
137
+ response_class: Routemaster::Responses::HateoasResponse
138
+ )
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,20 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'materialist'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'materialist'
8
+ spec.version = Materialist::VERSION
9
+ spec.authors = ['Mo Valipour']
10
+ spec.email = ['valipour@gmail.com']
11
+ spec.summary = %q{Utilities to materialize routemaster topics}
12
+ spec.homepage = 'http://github.com/deliveroo/materialist'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.test_files = spec.files.grep(%r{^spec/})
17
+ spec.require_paths = %w(lib)
18
+
19
+ spec.add_runtime_dependency 'sidekiq'
20
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+ require 'materialist/event_handler'
3
+ require 'materialist/event_worker'
4
+
5
+ RSpec.describe Materialist::EventHandler do
6
+ let(:options) {{}}
7
+ subject { described_class.new options }
8
+
9
+ let(:worker_double) { double() }
10
+ before do
11
+ allow(Materialist::EventWorker).to receive(:set)
12
+ .and_return worker_double
13
+ end
14
+
15
+ describe "#on_events_received" do
16
+ let(:events) {[{ "topic" => :topic_a }, { "topic" => :topic_b }]}
17
+ let(:perform) { subject.on_events_received events.map() }
18
+
19
+ context "when no topic is specified" do
20
+ let(:options) {{ topics: [] }}
21
+
22
+ it "doesn't enqueue any event" do
23
+ expect(worker_double).to_not receive(:perform_async)
24
+ perform
25
+ end
26
+ end
27
+
28
+ context "when a topic is specified" do
29
+ let(:options) {{ topics: [:topic_a] }}
30
+
31
+ it "enqueues event of that topic" do
32
+ expect(worker_double).to receive(:perform_async).with(events[0])
33
+ perform
34
+ end
35
+ end
36
+
37
+ context "when both topics are specified" do
38
+ let(:options) {{ topics: [:topic_a, :topic_b] }}
39
+
40
+ it "enqueues event of both topics" do
41
+ expect(worker_double).to receive(:perform_async).twice
42
+ perform
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "#call" do
48
+ let(:event) { { "topic" => :foobar } }
49
+ let(:perform) { subject.call event }
50
+
51
+ it "enqueues the event" do
52
+ expect(worker_double).to receive(:perform_async).with(event)
53
+ perform
54
+ end
55
+
56
+ context "if queue name is privided" do
57
+ let(:queue_name) { :some_queue_name }
58
+ let(:options) {{ queue: queue_name }}
59
+
60
+ it "enqueues the event in the given queue" do
61
+ expect(Materialist::EventWorker).to receive(:set)
62
+ .with(queue: queue_name)
63
+ expect(worker_double).to receive(:perform_async).with(event)
64
+ perform
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'materialist/event_worker'
3
+
4
+ RSpec.describe Materialist::EventWorker do
5
+ describe "#perform" do
6
+ let(:source_url) { 'https://service.dev/foobars/1' }
7
+ let(:event) {{ 'topic' => :foobar, 'url' => source_url, 'type' => 'noop' }}
8
+ let!(:materializer_class) { class FoobarMaterializer; end }
9
+
10
+ before do
11
+ allow(FoobarMaterializer).to receive(:perform)
12
+ end
13
+
14
+ context "when run synchronously" do
15
+ let(:perform) { subject.perform(event) }
16
+
17
+ it "calls the relevant materializer" do
18
+ expect(FoobarMaterializer).to receive(:perform).with(source_url, :noop)
19
+ perform
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+ require 'materialist/materializer'
3
+
4
+ RSpec.describe Materialist::Materializer do
5
+ describe "#perform" do
6
+ let!(:materializer_class) do
7
+ class FoobarMaterializer
8
+ include Materialist::Materializer
9
+
10
+ use_model :foobar
11
+ materialize :name
12
+ materialize :age, as: :how_old
13
+
14
+ link :city do
15
+ materialize :timezone
16
+
17
+ link :country do
18
+ materialize :tld, as: :country_tld
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ let!(:foobar_class) { class Foobar; end }
25
+ let(:country_url) { 'https://service.dev/countries/1' }
26
+ let(:country_body) {{ tld: 'fr' }}
27
+ let(:city_url) { 'https://service.dev/cities/1' }
28
+ let(:city_body) {{ _links: { country: { href: country_url }}, timezone: 'Europe/Paris' }}
29
+ let(:source_url) { 'https://service.dev/foobars/1' }
30
+ let(:source_body) {{ _links: { city: { href: city_url }}, name: 'jack', age: 30 }}
31
+ before do
32
+ stub_request(:get, source_url).to_return(
33
+ status: 200,
34
+ body: source_body.to_json,
35
+ headers: { 'Content-Type' => 'application/json' }
36
+ )
37
+ stub_request(:get, country_url).to_return(
38
+ status: 200,
39
+ body: country_body.to_json,
40
+ headers: { 'Content-Type' => 'application/json' }
41
+ )
42
+ stub_request(:get, city_url).to_return(
43
+ status: 200,
44
+ body: city_body.to_json,
45
+ headers: { 'Content-Type' => 'application/json' }
46
+ )
47
+ end
48
+
49
+ let(:expected_attributes) do
50
+ { name: 'jack', how_old: 30, country_tld: 'fr', timezone: 'Europe/Paris' }
51
+ end
52
+
53
+ let(:record_double) { double() }
54
+ before do
55
+ allow(Foobar).to receive(:find_or_initialize_by).and_return record_double
56
+ end
57
+
58
+ let(:action) { :create }
59
+ let(:perform) { FoobarMaterializer.perform(source_url, action) }
60
+
61
+ def performs_upsert
62
+ expect(Foobar).to receive(:find_or_initialize_by)
63
+ .with(source_url: source_url)
64
+ expect(record_double).to receive(:update_attributes).with(expected_attributes)
65
+ expect(record_double).to receive(:save!)
66
+ perform
67
+ end
68
+
69
+ def performs_destroy
70
+ expect(Foobar).to receive(:where)
71
+ .with(source_url: source_url)
72
+ .and_return record_double
73
+ expect(record_double).to receive(:destroy_all)
74
+ perform
75
+ end
76
+
77
+ it { performs_upsert }
78
+
79
+ %i(create update noop).each do |action_name|
80
+ context "when action is :#{action_name}" do
81
+ let(:action) { action_name }
82
+ it { performs_upsert }
83
+ end
84
+ end
85
+
86
+ context "when action is :delete" do
87
+ let(:action) { :delete }
88
+
89
+ it { performs_destroy }
90
+ end
91
+
92
+ context "if resource returns 404" do
93
+ before { stub_request(:get, source_url).to_return(status: 404) }
94
+
95
+ it "bubbles up routemaster not found error" do
96
+ expect { perform }.to raise_error Routemaster::Errors::ResourceNotFound
97
+ end
98
+ end
99
+
100
+ context "if a linked resource returns 404" do
101
+ before { stub_request(:get, city_url).to_return(status: 404) }
102
+
103
+ let(:expected_attributes) do
104
+ { name: 'jack', how_old: 30 }
105
+ end
106
+
107
+ it "ignores keys from the relation" do
108
+ performs_upsert
109
+ end
110
+ end
111
+
112
+ context "when after_upsert is configured" do
113
+ let(:expected_attributes) {{}}
114
+ let!(:materializer_class) do
115
+ class FoobarMaterializer
116
+ include Materialist::Materializer
117
+
118
+ use_model :foobar
119
+ after_upsert :my_method
120
+
121
+ def my_method(entity)
122
+ entity.after_upsert_action
123
+ end
124
+ end
125
+ end
126
+
127
+ %i(create update noop).each do |action_name|
128
+ context "when action is :#{action_name}" do
129
+ let(:action) { action_name }
130
+ it "calls after_upsert method" do
131
+ expect(record_double).to receive(:after_upsert_action)
132
+ performs_upsert
133
+ end
134
+ end
135
+ end
136
+
137
+ context "when action is :delete" do
138
+ let(:action) { :delete }
139
+
140
+ it "does not call after_upsert method" do
141
+ expect(record_double).to_not receive(:after_upsert_action)
142
+ performs_destroy
143
+ end
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,28 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'codecov'
5
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov if ENV['CI']
6
+
7
+ require 'webmock/rspec'
8
+ require 'dotenv'
9
+ require 'pry'
10
+ require 'byebug'
11
+ Dotenv.overload('.env.test')
12
+
13
+ # This file was generated by the `rspec --init` command. Conventionally, all
14
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
15
+ # Require this file using `require "spec_helper"` to ensure that it is only
16
+ # loaded once.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+ config.run_all_when_everything_filtered = true
21
+ config.raise_errors_for_deprecations!
22
+
23
+ # Run specs in random order to surface order dependencies. If you find an
24
+ # order dependency and want to debug it, you can fix the order by providing
25
+ # the seed, which is printed after each run.
26
+ # --seed 1234
27
+ config.order = 'random'
28
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: materialist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mo Valipour
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sidekiq
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email:
29
+ - valipour@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".env.test"
35
+ - ".gitignore"
36
+ - ".rspec"
37
+ - ".ruby-version"
38
+ - Appraisals
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - Guardfile
42
+ - LICENSE.txt
43
+ - README.md
44
+ - Rakefile
45
+ - appraise
46
+ - lib/materialist.rb
47
+ - lib/materialist/event_handler.rb
48
+ - lib/materialist/event_worker.rb
49
+ - lib/materialist/materializer.rb
50
+ - materialist.gemspec
51
+ - spec/materialist/event_handler_spec.rb
52
+ - spec/materialist/event_worker_spec.rb
53
+ - spec/materialist/materializer_spec.rb
54
+ - spec/spec_helper.rb
55
+ homepage: http://github.com/deliveroo/materialist
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.6.8
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Utilities to materialize routemaster topics
79
+ test_files:
80
+ - spec/materialist/event_handler_spec.rb
81
+ - spec/materialist/event_worker_spec.rb
82
+ - spec/materialist/materializer_spec.rb
83
+ - spec/spec_helper.rb