knuckles 0.1.0
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 +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
|