knuckles 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +33 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +77 -0
- data/Rakefile +9 -0
- data/bench/bench_helper.rb +48 -0
- data/bench/fixtures/serializers.rb +93 -0
- data/bench/fixtures/submissions.json +1 -0
- data/bench/profiling.rb +14 -0
- data/bench/realistic.rb +9 -0
- data/bench/simple.rb +25 -0
- data/bin/rspec +16 -0
- data/knuckles.gemspec +24 -0
- data/lib/knuckles/combiner.rb +26 -0
- data/lib/knuckles/dumper.rb +21 -0
- data/lib/knuckles/fetcher.rb +30 -0
- data/lib/knuckles/hydrator.rb +29 -0
- data/lib/knuckles/keygen.rb +13 -0
- data/lib/knuckles/pipeline.rb +56 -0
- data/lib/knuckles/renderer.rb +27 -0
- data/lib/knuckles/version.rb +3 -0
- data/lib/knuckles/view.rb +28 -0
- data/lib/knuckles/writer.rb +41 -0
- data/lib/knuckles.rb +50 -0
- data/spec/knuckles/combiner_spec.rb +36 -0
- data/spec/knuckles/dumper_spec.rb +33 -0
- data/spec/knuckles/fetcher_spec.rb +32 -0
- data/spec/knuckles/hydrator_spec.rb +22 -0
- data/spec/knuckles/keygen_spec.rb +19 -0
- data/spec/knuckles/pipeline_spec.rb +96 -0
- data/spec/knuckles/renderer_spec.rb +32 -0
- data/spec/knuckles/view_spec.rb +38 -0
- data/spec/knuckles/writer_spec.rb +41 -0
- data/spec/knuckles_spec.rb +33 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/models.rb +11 -0
- data/spec/support/views.rb +27 -0
- metadata +154 -0
data/bench/profiling.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative "./bench_helper"
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "stackprof"
|
5
|
+
|
6
|
+
FileUtils.mkdir_p("tmp")
|
7
|
+
|
8
|
+
models = BenchHelper.submissions
|
9
|
+
|
10
|
+
StackProf.run(mode: :wall, interval: 500, out: "tmp/stackprof-wall.dump") do
|
11
|
+
100.times do
|
12
|
+
Knuckles.new.call(models, view: SubmissionView)
|
13
|
+
end
|
14
|
+
end
|
data/bench/realistic.rb
ADDED
data/bench/simple.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "./bench_helper"
|
2
|
+
|
3
|
+
Post = Struct.new(:id, :title, :updated_at)
|
4
|
+
|
5
|
+
module PostView
|
6
|
+
extend Knuckles::View
|
7
|
+
|
8
|
+
def self.root
|
9
|
+
:posts
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.data(object, _)
|
13
|
+
{id: object.id,
|
14
|
+
title: object.title,
|
15
|
+
updated_at: object.updated_at}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
models = 100.times.map { |i| Post.new(i, "title", Time.new) }
|
20
|
+
|
21
|
+
Benchmark.ips do |x|
|
22
|
+
x.report("serialize.main") do
|
23
|
+
Knuckles.new.call(models, view: PostView)
|
24
|
+
end
|
25
|
+
end
|
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require "pathname"
|
10
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require "rubygems"
|
14
|
+
require "bundler/setup"
|
15
|
+
|
16
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/knuckles.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "knuckles/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "knuckles"
|
8
|
+
spec.version = Knuckles::VERSION
|
9
|
+
spec.authors = ["Parker Selbert"]
|
10
|
+
spec.email = ["parker@sorentwo.com"]
|
11
|
+
spec.summary = "Simple performance aware data serialization"
|
12
|
+
spec.homepage = ""
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = []
|
17
|
+
spec.test_files = spec.files.grep(%r{^(spec)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "activesupport"
|
21
|
+
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module Knuckles
|
4
|
+
module Combiner
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def name
|
8
|
+
"combiner".freeze
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(prepared, _)
|
12
|
+
prepared.each_with_object(set_backed_hash) do |hash, memo|
|
13
|
+
hash[:result].each do |root, values|
|
14
|
+
case values
|
15
|
+
when Hash then memo[root] << values
|
16
|
+
when Array then memo[root] += values
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_backed_hash
|
23
|
+
Hash.new { |hash, key| hash[key] = Set.new }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Knuckles
|
2
|
+
module Dumper
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def name
|
6
|
+
"dumper".freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(objects, _options)
|
10
|
+
Knuckles.serializer.dump(keys_to_arrays(objects))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def keys_to_arrays(objects)
|
16
|
+
objects.each do |key, value|
|
17
|
+
objects[key] = value.to_a if value.is_a?(Set)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Knuckles
|
2
|
+
module Fetcher
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def name
|
6
|
+
"fetcher".freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(prepared, options)
|
10
|
+
results = get_cached(prepared, options)
|
11
|
+
|
12
|
+
prepared.each do |hash|
|
13
|
+
result = results[hash[:key]]
|
14
|
+
hash[:cached?] = !result.nil?
|
15
|
+
hash[:result] = result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def get_cached(prepared, options)
|
22
|
+
kgen = options.fetch(:keygen, Knuckles.keygen)
|
23
|
+
keys = prepared.map do |hash|
|
24
|
+
hash[:key] = kgen.expand_key(hash[:object])
|
25
|
+
end
|
26
|
+
|
27
|
+
Knuckles.cache.read_multi(*keys)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Knuckles
|
2
|
+
module Hydrator
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def name
|
6
|
+
"hydrator".freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(prepared, options)
|
10
|
+
hydrate = options[:hydrate]
|
11
|
+
|
12
|
+
if hydrate && any_missing?(prepared)
|
13
|
+
hydrate.call(hydratable(prepared))
|
14
|
+
end
|
15
|
+
|
16
|
+
prepared
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def any_missing?(prepared)
|
22
|
+
prepared.any? { |hash| !hash[:cached?] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def hydratable(prepared)
|
26
|
+
prepared.reject { |hash| hash[:cached?] }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Knuckles
|
2
|
+
class Pipeline
|
3
|
+
def self.default_stages
|
4
|
+
[Knuckles::Fetcher,
|
5
|
+
Knuckles::Hydrator,
|
6
|
+
Knuckles::Renderer,
|
7
|
+
Knuckles::Writer,
|
8
|
+
Knuckles::Combiner,
|
9
|
+
Knuckles::Dumper]
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :stages
|
13
|
+
|
14
|
+
def initialize(stages: self.class.default_stages)
|
15
|
+
@stages = stages
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(stage)
|
19
|
+
stages.delete(stage)
|
20
|
+
end
|
21
|
+
|
22
|
+
def insert_after(stage, new_stage)
|
23
|
+
index = stages.index(stage)
|
24
|
+
|
25
|
+
stages.insert(index + 1, new_stage)
|
26
|
+
end
|
27
|
+
|
28
|
+
def insert_before(stage, new_stage)
|
29
|
+
index = stages.index(stage)
|
30
|
+
|
31
|
+
stages.insert(index, new_stage)
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(objects, options)
|
35
|
+
prepared = prepare(objects)
|
36
|
+
|
37
|
+
stages.reduce(prepared) do |results, stage|
|
38
|
+
instrument("knuckles.stage", stage: stage.name) do
|
39
|
+
stage.call(results, options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def prepare(objects)
|
45
|
+
objects.map do |object|
|
46
|
+
{object: object, key: nil, cached?: false, result: nil}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def instrument(operation, payload, &block)
|
53
|
+
Knuckles.notifications.instrument(operation, payload, &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Knuckles
|
2
|
+
module Renderer
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def name
|
6
|
+
"renderer".freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(objects, options)
|
10
|
+
view = options.fetch(:view)
|
11
|
+
|
12
|
+
objects.each do |hash|
|
13
|
+
unless hash[:cached?]
|
14
|
+
hash[:result] = do_render(hash[:object], view, options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def do_render(object, view, options)
|
22
|
+
view.relations(object, options).merge!(
|
23
|
+
view.root => [view.data(object, options)]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Knuckles
|
2
|
+
module View
|
3
|
+
extend self
|
4
|
+
|
5
|
+
## Callbacks
|
6
|
+
|
7
|
+
def root
|
8
|
+
end
|
9
|
+
|
10
|
+
def data(_object, _options = {})
|
11
|
+
{}
|
12
|
+
end
|
13
|
+
|
14
|
+
def relations(_object, _options = {})
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
|
18
|
+
## Relations
|
19
|
+
|
20
|
+
def has_one(object, view, options = {})
|
21
|
+
[view.data(object, options)]
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_many(objects, view, options = {})
|
25
|
+
objects.map { |object| view.data(object, options) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Knuckles
|
2
|
+
module Writer
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def name
|
6
|
+
"writer".freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(objects, _)
|
10
|
+
if cache.respond_to?(:write_multi)
|
11
|
+
write_multi(objects)
|
12
|
+
else
|
13
|
+
write_each(objects)
|
14
|
+
end
|
15
|
+
|
16
|
+
objects
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def cache
|
22
|
+
Knuckles.cache
|
23
|
+
end
|
24
|
+
|
25
|
+
def write_each(objects)
|
26
|
+
objects.each do |hash|
|
27
|
+
cache.write(hash[:key], hash[:result]) unless hash[:cached?]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_multi(objects)
|
32
|
+
writable = objects.each_with_object({}) do |hash, memo|
|
33
|
+
next if hash[:cached?]
|
34
|
+
|
35
|
+
memo[hash[:key]] = hash[:result]
|
36
|
+
end
|
37
|
+
|
38
|
+
cache.write_multi(writable) if writable.any?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/knuckles.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "active_support/notifications"
|
2
|
+
require "active_support/cache"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Knuckles
|
6
|
+
autoload :Combiner, "knuckles/combiner"
|
7
|
+
autoload :Dumper, "knuckles/dumper"
|
8
|
+
autoload :Fetcher, "knuckles/fetcher"
|
9
|
+
autoload :Hydrator, "knuckles/hydrator"
|
10
|
+
autoload :Keygen, "knuckles/keygen"
|
11
|
+
autoload :Pipeline, "knuckles/pipeline"
|
12
|
+
autoload :Renderer, "knuckles/renderer"
|
13
|
+
autoload :View, "knuckles/view"
|
14
|
+
autoload :Writer, "knuckles/writer"
|
15
|
+
|
16
|
+
extend self
|
17
|
+
|
18
|
+
attr_writer :cache, :keygen, :notifications, :serializer
|
19
|
+
|
20
|
+
def new(*args)
|
21
|
+
Knuckles::Pipeline.new(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def cache
|
25
|
+
@cache ||= ActiveSupport::Cache::MemoryStore.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def keygen
|
29
|
+
@keygen ||= Knuckles::Keygen
|
30
|
+
end
|
31
|
+
|
32
|
+
def notifications
|
33
|
+
@notifications ||= ActiveSupport::Notifications
|
34
|
+
end
|
35
|
+
|
36
|
+
def serializer
|
37
|
+
@serializer ||= JSON
|
38
|
+
end
|
39
|
+
|
40
|
+
def configure
|
41
|
+
yield self
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset!
|
45
|
+
@cache = nil
|
46
|
+
@keygen = nil
|
47
|
+
@notifications = nil
|
48
|
+
@serializer = nil
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
RSpec.describe Knuckles::Combiner do
|
2
|
+
describe ".call" do
|
3
|
+
it "merges all results into a single object" do
|
4
|
+
prepared = [
|
5
|
+
{
|
6
|
+
result: {
|
7
|
+
author: {id: 1, name: "Michael"},
|
8
|
+
posts: [{id: 1, title: "hello", tag_ids: [1, 2]}],
|
9
|
+
tags: [{id: 1, name: "alpha"}, {id: 2, name: "gamma"}]
|
10
|
+
}
|
11
|
+
}, {
|
12
|
+
result: {
|
13
|
+
posts: [{id: 2, title: "there", tag_ids: [1]}],
|
14
|
+
tags: [{id: 1, name: "alpha"}]
|
15
|
+
}
|
16
|
+
}
|
17
|
+
]
|
18
|
+
|
19
|
+
combined = Knuckles::Combiner.call(prepared, {})
|
20
|
+
|
21
|
+
expect(combined).to eq(
|
22
|
+
author: Set.new([
|
23
|
+
{id: 1, name: "Michael"}
|
24
|
+
]),
|
25
|
+
posts: Set.new([
|
26
|
+
{id: 1, title: "hello", tag_ids: [1, 2]},
|
27
|
+
{id: 2, title: "there", tag_ids: [1]}
|
28
|
+
]),
|
29
|
+
tags: Set.new([
|
30
|
+
{id: 1, name: "alpha"},
|
31
|
+
{id: 2, name: "gamma"}
|
32
|
+
])
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
RSpec.describe Knuckles::Dumper do
|
2
|
+
describe ".call" do
|
3
|
+
it "dumps a tree of objects" do
|
4
|
+
objects = {
|
5
|
+
author: {id: 1, name: "Ernest"},
|
6
|
+
posts: Set.new([
|
7
|
+
{id: 1, title: "great"},
|
8
|
+
{id: 2, title: "stuff"}
|
9
|
+
]),
|
10
|
+
tags: Set.new([
|
11
|
+
{id: 1, name: "alpha"},
|
12
|
+
{id: 2, name: "gamma"}
|
13
|
+
])
|
14
|
+
}
|
15
|
+
|
16
|
+
dumped = Knuckles::Dumper.call(objects, {})
|
17
|
+
|
18
|
+
expect(dumped).to eq(
|
19
|
+
JSON.dump(
|
20
|
+
author: {id: 1, name: "Ernest"},
|
21
|
+
posts: [
|
22
|
+
{id: 1, title: "great"},
|
23
|
+
{id: 2, title: "stuff"}
|
24
|
+
],
|
25
|
+
tags: [
|
26
|
+
{id: 1, name: "alpha"},
|
27
|
+
{id: 2, name: "gamma"}
|
28
|
+
]
|
29
|
+
)
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
RSpec.describe Knuckles::Fetcher do
|
2
|
+
describe ".call" do
|
3
|
+
it "fetches all cached data for top level objects" do
|
4
|
+
objects = [Tag.new(1, "alpha"), Tag.new(2, "gamma")]
|
5
|
+
|
6
|
+
Knuckles.cache.write(Knuckles.keygen.expand_key(objects.first), "result")
|
7
|
+
|
8
|
+
objects = prepare(objects)
|
9
|
+
results = Knuckles::Fetcher.call(objects, {})
|
10
|
+
|
11
|
+
expect(pluck(results, :result)).to eq(["result", nil])
|
12
|
+
expect(pluck(results, :cached?)).to eq([true, false])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "allows passing a custom keygen" do
|
16
|
+
keygen = Module.new do
|
17
|
+
def self.expand_key(object)
|
18
|
+
object.name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
objects = prepare([Tag.new(1, "alpha")])
|
23
|
+
results = Knuckles::Fetcher.call(objects, keygen: keygen)
|
24
|
+
|
25
|
+
expect(pluck(results, :key)).to eq(["alpha"])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def pluck(enum, key)
|
30
|
+
enum.map { |hash| hash[key] }
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
RSpec.describe Knuckles::Hydrator do
|
2
|
+
describe ".call" do
|
3
|
+
it "is a noop without a hydrate lambda" do
|
4
|
+
objects = [Tag.new(1, "alpha")]
|
5
|
+
|
6
|
+
expect(Knuckles::Hydrator.call(objects, {})).to eq(objects)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "refines the object collection using hydrate" do
|
10
|
+
objects = [Tag.new(1, "alpha"), Tag.new(2, "gamma")]
|
11
|
+
prepared = prepare(objects)
|
12
|
+
|
13
|
+
hydrate = lambda do |hashes|
|
14
|
+
hashes.each { |hash| hash[:object] = :updated }
|
15
|
+
end
|
16
|
+
|
17
|
+
hydrated = Knuckles::Hydrator.call(prepared, hydrate: hydrate)
|
18
|
+
|
19
|
+
expect(hydrated.map { |hash| hash[:object] }).to eq([:updated, :updated])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
RSpec.describe Knuckles::Keygen do
|
2
|
+
FakeKeyModel = Struct.new(:id, :updated_at)
|
3
|
+
|
4
|
+
describe ".cache_key" do
|
5
|
+
it "provides a simple default cache key" do
|
6
|
+
cache_key = Knuckles::Keygen.expand_key(FakeKeyModel.new(123, Time.now))
|
7
|
+
|
8
|
+
expect(cache_key).to match(%r{FakeKeyModel/123/\d+})
|
9
|
+
end
|
10
|
+
|
11
|
+
it "respects an object with a cache_key method" do
|
12
|
+
model = double(:model, cache_key: "abcdefg")
|
13
|
+
|
14
|
+
cache_key = Knuckles::Keygen.expand_key(model)
|
15
|
+
|
16
|
+
expect(cache_key).to eq("abcdefg")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
RSpec.describe Knuckles::Pipeline do
|
2
|
+
describe "#stages" do
|
3
|
+
it "sets the default stages" do
|
4
|
+
pipeline = Knuckles::Pipeline.new
|
5
|
+
|
6
|
+
expect(pipeline.stages).not_to be_empty
|
7
|
+
end
|
8
|
+
|
9
|
+
it "allows stages to be customeized on initialization" do
|
10
|
+
pipeline = Knuckles::Pipeline.new(stages: [])
|
11
|
+
|
12
|
+
expect(pipeline.stages).to be_empty
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#delete" do
|
17
|
+
it "removes an existing stage" do
|
18
|
+
pipeline = Knuckles::Pipeline.new
|
19
|
+
|
20
|
+
pipeline.delete(Knuckles::Writer)
|
21
|
+
|
22
|
+
expect(pipeline.stages).not_to include(Knuckles::Writer)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#insert_after" do
|
27
|
+
it "adds a stage after an existing stage" do
|
28
|
+
custom = Module.new
|
29
|
+
pipeline = Knuckles::Pipeline.new
|
30
|
+
|
31
|
+
pipeline.insert_after(Knuckles::Fetcher, custom)
|
32
|
+
|
33
|
+
expect(pipeline.stages).to include(custom)
|
34
|
+
expect(pipeline.stages.take(2)).to eq([
|
35
|
+
Knuckles::Fetcher,
|
36
|
+
custom
|
37
|
+
])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#insert_before" do
|
42
|
+
it "adds a stage after an existing stage" do
|
43
|
+
custom = Module.new
|
44
|
+
pipeline = Knuckles::Pipeline.new
|
45
|
+
|
46
|
+
pipeline.insert_before(Knuckles::Fetcher, custom)
|
47
|
+
|
48
|
+
expect(pipeline.stages).to include(custom)
|
49
|
+
expect(pipeline.stages.take(2)).to eq([
|
50
|
+
custom,
|
51
|
+
Knuckles::Fetcher
|
52
|
+
])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#call" do
|
57
|
+
it "aggregates the result of all stages" do
|
58
|
+
filter_a = Module.new do
|
59
|
+
def self.name
|
60
|
+
"strip"
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.call(objects, _)
|
64
|
+
objects.each { |hash| hash[:object].strip! }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
filter_b = Module.new do
|
69
|
+
def self.name
|
70
|
+
"downcase"
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.call(objects, _)
|
74
|
+
objects.each { |hash| hash[:object].downcase! }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
pipeline = Knuckles::Pipeline.new(stages: [filter_a, filter_b])
|
79
|
+
|
80
|
+
expect(pipeline.call([" KNUCKLES "], {}))
|
81
|
+
.to eq([{object: "knuckles", cached?: false, key: nil, result: nil}])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#prepare" do
|
86
|
+
it "wraps all objects in entities" do
|
87
|
+
object = Object.new
|
88
|
+
prepared, = Knuckles::Pipeline.new.prepare([object])
|
89
|
+
|
90
|
+
expect(prepared[:object]).to be(object)
|
91
|
+
expect(prepared[:key]).to be_nil
|
92
|
+
expect(prepared[:result]).to be_nil
|
93
|
+
expect(prepared[:cached?]).to be_falsey
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|