materialist 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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