edgycircle_toolbox 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0fac7454ff83b9d4e105dd363d09f0db834c9216
4
+ data.tar.gz: 394e20e69e366e0049e81cd827e7fd3e2bf67e1b
5
+ SHA512:
6
+ metadata.gz: 35b8761a11d9e893dd0872c10fee0f01e9ea57c6a0c161089b89a005e4434524e7a2f05ce2519e596b3b2fb4daea06f4a31f2836e3a9292d90d8d32ce1e531c5
7
+ data.tar.gz: 72421b139359fe670c455ba756f15164f79a38bf9c84f6c70777b64df7f12491d1c065473677ecae032f565cc06afe28062976e065b5d46c009d4e0c8ec81df9
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in edgycircle_toolbox.gemspec
4
+ gemspec
@@ -0,0 +1,214 @@
1
+ A loose collection of Ruby related code that currently has no specific place to live in.
2
+
3
+ ## HasAttributes Module
4
+ ~~~ruby
5
+ require 'edgycircle_toolbox/has_attributes'
6
+
7
+ class Dummy
8
+ include EdgycircleToolbox::HasAttributes
9
+ attributes :a, :b
10
+ end
11
+
12
+ dummy = Dummy.new({ a: 1, b: 2, c: 3 })
13
+
14
+ dummy.a # => 1
15
+ dummy.b # => 2
16
+ dummy.c # => NoMethodError
17
+
18
+ dummy.attributes # => [:a, :b]
19
+ ~~~
20
+
21
+ ## CQRS CommandResult
22
+ Returning `self` from `add_error`, `add_event` and `set_command` allows us write concise code like `return result.add_error(:authentication_error) unless authenticated?`.
23
+ ~~~ruby
24
+ require 'edgycircle_toolbox/cqrs/command_result'
25
+
26
+ command = :command_a
27
+ result = EdgycircleToolbox::CQRS::CommandResult.new(command)
28
+
29
+ result.add_error(:error) # => result
30
+ result.add_event(:event) # => result
31
+ result.set_command(:command_b) # => result
32
+ result.failure? # => true
33
+ ~~~
34
+
35
+ ## CQRS Handler Module
36
+ A handler class has to implement `call(command)` and accept a command instance as parameter.
37
+ ~~~ruby
38
+ require 'edgycircle_toolbox/cqrs/handler'
39
+
40
+ class DummyHandler
41
+ include EdgycircleToolbox::CQRS::Handler
42
+
43
+ def call(command)
44
+ # Business Logic
45
+ end
46
+ end
47
+ ~~~
48
+
49
+ ## CQRS Command Module
50
+ A command class can have a schema and attributes. The schema can be used by the command bus to validate the data before a command is built. Currently the definitions are redundant, changing this could be an improvement made in the future. Every command has a default attribute `id` and a schema to validate its presence and make sure it's a string.
51
+
52
+ The module uses [dry-validation](http://dry-rb.org/gems/dry-validation/) for the schema and its validation.
53
+ ~~~ruby
54
+ require 'edgycircle_toolbox/cqrs/command'
55
+
56
+ class DummyCommand
57
+ include EdgycircleToolbox::CQRS::Command
58
+
59
+ schema do
60
+ required(:title).filled(:str?)
61
+ end
62
+
63
+ attributes :title
64
+ end
65
+ ~~~
66
+
67
+ ## CQRS Command Bus
68
+ Can build commands from a parameter hash if the data fits the commands schema. A submitted command is passed to the corresponding handler. Events from the command handler get published on the message bus. Both `build` and `submit` return a `CommandResult`.
69
+ ~~~ruby
70
+ require 'edgycircle_toolbox/cqrs/command_bus'
71
+
72
+ command_bus = EdgycircleToolbox::CQRS::CommandBus.new
73
+ command_bus.register EnterTicketCommand, EnterTicketHandler
74
+
75
+ command_result = command_bus.build(parameter_hash)
76
+ command_result = command_bus.submit(command_result.command)
77
+ ~~~
78
+
79
+ ## CQRS Message Bus
80
+ ~~~ruby
81
+ require 'edgycircle_toolbox/cqrs/message_bus'
82
+
83
+ class RandomEvent
84
+ end
85
+
86
+ class CommonEvent
87
+ end
88
+
89
+ EdgycircleToolbox::CQRS::MessageBus.subscribe([RandomEvent], ->(event) { puts 'Output 1' })
90
+ EdgycircleToolbox::CQRS::MessageBus.subscribe([RandomEvent, CommonEvent], ->(event) { puts 'Output 2' })
91
+
92
+ EdgycircleToolbox::CQRS::MessageBus.publish(RandomEvent.new)
93
+ # =>
94
+ # Output 1
95
+ # Output 2
96
+
97
+ EdgycircleToolbox::CQRS::MessageBus.publish(CommonEvent.new)
98
+ # =>
99
+ # Output 2
100
+ ~~~
101
+
102
+ ## CQRS Model Collector
103
+ Can be used to collect data based on events.
104
+ ~~~ruby
105
+ require 'edgycircle_toolbox/cqrs/model_collector'
106
+
107
+ class RandomEvent
108
+ end
109
+
110
+ class CommonEvent
111
+ end
112
+
113
+ EdgycircleToolbox::CQRS::ModelCollector.register(RandomEvent, ->(event) { [1, 2] })
114
+ EdgycircleToolbox::CQRS::ModelCollector.register(RandomEvent, ->(event) { [3] })
115
+ EdgycircleToolbox::CQRS::ModelCollector.register(CommonEvent, ->(event) { [4] })
116
+
117
+ EdgycircleToolbox::CQRS::ModelCollector.for_events([
118
+ RandomEvent.new,
119
+ CommonEvent.new
120
+ ])
121
+ # =>
122
+ # [1, 2, 3, 4]
123
+ ~~~
124
+
125
+ ## Sonapi Resource Module
126
+ ~~~ruby
127
+ require 'edgycircle_toolbox/sonapi/resource'
128
+
129
+ class CommandResource
130
+ include EdgycircleToolbox::Sonapi::Resource
131
+
132
+ type "commands"
133
+
134
+ dynamic_attributes Proc.new { |object| object.attribute_names - [:id] }
135
+ parameter_filter Proc.new { |name, value| true }
136
+ end
137
+
138
+ class TicketResource
139
+ include EdgycircleToolbox::Sonapi::Resource
140
+
141
+ type "tickets"
142
+
143
+ attribute :estimate
144
+ end
145
+ ~~~
146
+
147
+ ## Sonapi Dynamic Resource
148
+ ~~~ruby
149
+ require 'edgycircle_toolbox/sonapi/dynamic_resource'
150
+
151
+ class Dummy
152
+ end
153
+
154
+ class Tree
155
+ end
156
+
157
+ class DummyResource
158
+ include EdgycircleToolbox::Sonapi::Resource
159
+
160
+ type "dummies"
161
+ end
162
+
163
+ class TreeResource
164
+ include EdgycircleToolbox::Sonapi::Resource
165
+
166
+ type "trees"
167
+ end
168
+
169
+ EdgycircleToolbox::Sonapi::DynamicResource.register(Dummy, DummyResource)
170
+ EdgycircleToolbox::Sonapi::DynamicResource.register(Tree, TreeResource)
171
+
172
+ EdgycircleToolbox::Sonapi::DynamicResource.serialize([Dummy.new, Tree.new])
173
+ ~~~
174
+
175
+ ## Sonapi Error Resource
176
+ The `ErrorResource` can serialize errors conforming to the `SerializableError` interface.
177
+ ~~~ruby
178
+ require 'edgycircle_toolbox/sonapi/error_resource'
179
+
180
+ EdgycircleToolbox::Sonapi::ErrorResource.serialize([error_a, error_b])
181
+ ~~~
182
+
183
+ ## Sonapi SerializableError Module
184
+ An error class has to implement the `title`, `pointer` and `detail` methods.
185
+ ~~~ruby
186
+ require 'edgycircle_toolbox/sonapi/serializable_error'
187
+
188
+ class EmailTakenError
189
+ include EdgycircleToolbox::Sonapi::SerializableError
190
+
191
+ def initialize(email)
192
+ @email = email
193
+ end
194
+
195
+ def title
196
+ "Email Taken Error"
197
+ end
198
+
199
+ def pointer
200
+ nil
201
+ end
202
+
203
+ def detail
204
+ "The email #{@email} is already taken, please use something different"
205
+ end
206
+ end
207
+ ~~~
208
+
209
+ ## Sonapi ValidationError
210
+ ~~~ruby
211
+ require 'edgycircle_toolbox/sonapi/validation_error'
212
+
213
+ EdgycircleToolbox::Sonapi::ValidationError.new(:title, "Title is not long enough")
214
+ ~~~
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'edgycircle_toolbox/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "edgycircle_toolbox"
8
+ spec.version = EdgycircleToolbox::VERSION
9
+ spec.authors = ["David Strauß"]
10
+ spec.email = ["david@strauss.io"]
11
+
12
+ spec.summary = %q{A loose collection of Ruby related code that currently has specific place to live in.}
13
+ spec.description = %q{A loose collection of Ruby related code that currently has specific place to live in.}
14
+ spec.homepage = "https://www.edgycircle.com"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.13"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "minitest", "~> 5.0"
36
+
37
+ spec.add_dependency "dry-validation", "~> 0.10"
38
+ spec.add_dependency "dry-container", "~> 0.5"
39
+ end
@@ -0,0 +1,5 @@
1
+ require "edgycircle_toolbox/version"
2
+
3
+ module EdgycircleToolbox
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,31 @@
1
+ require 'dry-validation'
2
+ require 'edgycircle_toolbox/has_attributes'
3
+
4
+ module EdgycircleToolbox
5
+ module CQRS
6
+ module Command
7
+ module ClassMethods
8
+ def schema(&block)
9
+ if block_given?
10
+ base = Dry::Validation.Schema(build: false) do
11
+ required(:id).filled(:str?)
12
+ end
13
+
14
+ @schema = Dry::Validation.Form(rules: base.rules, &block)
15
+ else
16
+ @schema
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.included(base)
22
+ base.extend(ClassMethods)
23
+ base.include(HasAttributes)
24
+
25
+ base.class_eval do
26
+ attributes :id
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ require 'dry-container'
2
+
3
+ module EdgycircleToolbox
4
+ module CQRS
5
+ class CommandBuilder
6
+ class DecoratorContainer
7
+ include Dry::Container::Mixin
8
+
9
+ configure do |config|
10
+ config.registry = ->(container, key, item, options) { (container[key] ||= []).push(item) }
11
+ config.resolver = ->(container, key) { container[key] || [] }
12
+ end
13
+ end
14
+
15
+ def initialize
16
+ @command_container = Dry::Container.new
17
+ @decorator_container = DecoratorContainer.new
18
+ end
19
+
20
+ def register(command_class)
21
+ key = command_class.name.split("::").last.gsub("Command", "")
22
+ @command_container.register(key, command_class)
23
+ end
24
+
25
+ def decorate(command_class, decorator_class)
26
+ @decorator_container.register(command_class, decorator_class)
27
+ end
28
+
29
+ def build(valid_parameters, context = nil)
30
+ command_class = @command_container.resolve(valid_parameters[:name])
31
+ decorator_classes = @decorator_container.resolve(command_class)
32
+ command = command_class.new(valid_parameters)
33
+ decorator_classes.inject(command) { |command, decorator_class| decorator_class.new(command, context) }
34
+ end
35
+
36
+ def validate(original_parameters)
37
+ command_class = @command_container.resolve(original_parameters[:name])
38
+ command_class.schema.call(original_parameters)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,62 @@
1
+ require 'dry-container'
2
+ require 'edgycircle_toolbox/cqrs/message_bus'
3
+ require 'edgycircle_toolbox/cqrs/command_result'
4
+ require 'edgycircle_toolbox/cqrs/command_builder'
5
+ require 'edgycircle_toolbox/sonapi/validation_error'
6
+
7
+ module EdgycircleToolbox
8
+ module CQRS
9
+ class CommandBus
10
+ def initialize
11
+ @container = Dry::Container.new
12
+ @command_builder = CommandBuilder.new
13
+ end
14
+
15
+ def register(command, handler)
16
+ command_builder.register(command)
17
+
18
+ container.namespace(:handlers) do
19
+ register(command, handler)
20
+ end
21
+ end
22
+
23
+ def decorate_command(command_class, decorator_class)
24
+ command_builder.decorate(command_class, decorator_class)
25
+ end
26
+
27
+ def submit(command)
28
+ result = container.resolve("handlers.#{command.class.name}").new.call(command)
29
+ result.events.each { |event| MessageBus.publish(event) }
30
+ result
31
+ end
32
+
33
+ def build(original_parameters, context = nil)
34
+ result = CommandResult.new
35
+ validation = command_builder.validate(original_parameters)
36
+
37
+ if validation.failure?
38
+ build_validation_errors(validation).each { |error| result.add_error(error) }
39
+ else
40
+ validated_parameters = validation.output
41
+ command = command_builder.build(validated_parameters, context)
42
+ result.set_command(command)
43
+ end
44
+
45
+ result
46
+ end
47
+
48
+ private
49
+ attr_reader :container, :command_builder
50
+
51
+ def command_class_for(name)
52
+ container.resolve("commands.#{name}")
53
+ end
54
+
55
+ def build_validation_errors(validation)
56
+ validation.messages(full: true).map do |attribute, messages|
57
+ messages.map { |message| ValidationError.new(attribute, message) }
58
+ end.flatten
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,33 @@
1
+ module EdgycircleToolbox
2
+ module CQRS
3
+ class CommandResult
4
+ attr_reader :command, :errors, :events
5
+
6
+ def initialize(command = nil)
7
+ @command = command
8
+ @events = []
9
+ @errors = []
10
+ end
11
+
12
+ def add_error(error)
13
+ errors << error
14
+ self
15
+ end
16
+
17
+ def add_event(event)
18
+ events << event
19
+ self
20
+ end
21
+
22
+ def set_command(command)
23
+ @command = command
24
+ self
25
+ end
26
+
27
+ def failure?
28
+ errors.size > 0
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,9 @@
1
+ module EdgycircleToolbox
2
+ module CQRS
3
+ module Handler
4
+ def call(command)
5
+ raise NotImplementedError
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ require 'dry-container'
2
+
3
+ module EdgycircleToolBox
4
+ module CQRS
5
+ class MessageBus
6
+ extend Dry::Container::Mixin
7
+
8
+ configure do |config|
9
+ config.registry = ->(container, key, item, options) { (container[key] ||= []).push(item) }
10
+ config.resolver = ->(container, key) { container[key] || [] }
11
+ end
12
+
13
+ def self.subscribe(events, callable)
14
+ events.each { |event| register(event, callable) }
15
+ end
16
+
17
+ def self.publish(event)
18
+ resolve(event.class).each { |callable| callable.call(event) }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ require 'dry-container'
2
+
3
+ module EdgycircleToolbox
4
+ module CQRS
5
+ class ModelCollector
6
+ extend Dry::Container::Mixin
7
+
8
+ configure do |config|
9
+ config.registry = ->(container, key, item, options) { (container[key] ||= []).push(item) }
10
+ config.resolver = ->(container, key) { container[key] || [] }
11
+ end
12
+
13
+ def self.for_events(events)
14
+ events.map { |event| resolve(event.class).map { |callable| callable.call(event) } }.flatten
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ module EdgycircleToolbox
2
+ module HasAttributes
3
+ module ClassMethods
4
+ def attributes(*attributes)
5
+ @attributes ||= []
6
+
7
+ return @attributes unless attributes.any?
8
+
9
+ attributes.each do |attribute|
10
+ define_attr_accessor(attribute)
11
+ @attributes << attribute
12
+ end
13
+ end
14
+
15
+ def define_attr_accessor(attribute)
16
+ attr_accessor(attribute)
17
+ end
18
+ end
19
+
20
+ def self.included(base)
21
+ base.class_eval do
22
+ extend ClassMethods
23
+ end
24
+ end
25
+
26
+ def initialize(attributes = {})
27
+ attributes.each do |key, value|
28
+ setter = "#{key}="
29
+ public_send(setter, value) if respond_to?(setter)
30
+ end
31
+ end
32
+
33
+ def attributes
34
+ self.class.attributes
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ module EdgycircleToolbox
2
+ module Sonapi
3
+ class Blueprint
4
+ attr_accessor :type, :attribute_names, :dynamic_attributes, :parameter_filter
5
+
6
+ def initialize(type, attribute_names = [])
7
+ @type = type
8
+ @attribute_names = attribute_names
9
+ @dynamic_attributes = Proc.new { [] }
10
+ @parameter_filter = Proc.new do |name, value|
11
+ @attribute_names.include?(name.to_s) || name == :id
12
+ end
13
+ end
14
+
15
+ def add_attribute(attribute_name)
16
+ @attribute_names << attribute_name
17
+ end
18
+
19
+ def attribute_names(object)
20
+ @attribute_names + dynamic_attributes.call(object).map(&:to_s)
21
+ end
22
+
23
+ def deserialize_parameter?(name, value)
24
+ parameter_filter.call(name, value)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ module EdgycircleToolbox
2
+ module Sonapi
3
+ class Deserializer
4
+ attr_reader :blueprint
5
+
6
+ def initialize(blueprint)
7
+ @blueprint = blueprint
8
+ end
9
+
10
+ def call(parameters)
11
+ Hash[
12
+ filter_parameter_pairs(parameter_pairs(parameters))
13
+ ]
14
+ end
15
+
16
+ private
17
+ def parameter_pairs(parameters)
18
+ id_pair = [:id, parameters["data"]["id"]]
19
+
20
+ parameters["data"]["attributes"].to_a.map do |pair|
21
+ [format_attribute_name(pair[0]), pair[1]]
22
+ end << id_pair
23
+ end
24
+
25
+ def format_attribute_name(attribute_name)
26
+ attribute_name.to_s.gsub("-", "_").to_sym
27
+ end
28
+
29
+ def filter_parameter_pairs(pairs)
30
+ pairs.select { |pair| blueprint.deserialize_parameter?(pair[0], pair[1]) }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ require 'dry-container'
2
+
3
+ module EdgycircleToolbox
4
+ module Sonapi
5
+ class DynamicResource
6
+ extend Dry::Container::Mixin
7
+
8
+ def self.serialize(models)
9
+ models.map { |model| resolve(model.class).serialize_resource(model) }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module EdgycircleToolbox
2
+ module Sonapi
3
+ class ErrorResource
4
+ def self.serialize(errors)
5
+ {
6
+ "errors" => errors.map do |error|
7
+ hash = {
8
+ "title" => error.title,
9
+ "detail" => error.detail
10
+ }
11
+
12
+ if error.pointer
13
+ hash["source"] = { "pointer" => error.pointer }
14
+ end
15
+
16
+ hash
17
+ end
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ require 'edgycircle_toolbox/sonapi/blueprint'
2
+ require 'edgycircle_toolbox/sonapi/serializer'
3
+ require 'edgycircle_toolbox/sonapi/deserializer'
4
+
5
+ module EdgycircleToolbox
6
+ module Sonapi
7
+ module Resource
8
+ module ClassMethods
9
+ def type(type)
10
+ @blueprint = Blueprint.new(type)
11
+ end
12
+
13
+ def serialize_resource(object)
14
+ Serializer.new(@blueprint).call(object)
15
+ end
16
+
17
+ def serialize(object, options = {})
18
+ if object.respond_to?(:map)
19
+ data = object.map { |item| serialize_resource(item) }
20
+ else
21
+ data = serialize_resource(object)
22
+ end
23
+
24
+ result = {
25
+ "data" => data
26
+ }
27
+
28
+ if options[:included_resources]
29
+ result.merge!({ "included" => options[:included_resources] })
30
+ end
31
+
32
+ result
33
+ end
34
+
35
+ def deserialize(parameters)
36
+ Deserializer.new(@blueprint).call(parameters)
37
+ end
38
+
39
+ def attribute(attribute_name)
40
+ @blueprint.add_attribute attribute_name.to_s
41
+ end
42
+
43
+ def dynamic_attributes(callable)
44
+ @blueprint.dynamic_attributes = callable
45
+ end
46
+
47
+ def parameter_filter(callable)
48
+ @blueprint.parameter_filter = callable
49
+ end
50
+ end
51
+
52
+ def self.included(base)
53
+ base.extend(ClassMethods)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ module EdgycircleToolbox
2
+ module Sonapi
3
+ module SerializableError
4
+ def title
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def pointer
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def detail
13
+ raise NotImplementedError
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module EdgycircleToolbox
2
+ module Sonapi
3
+ class Serializer
4
+ attr_reader :blueprint
5
+
6
+ def initialize(blueprint)
7
+ @blueprint = blueprint
8
+ end
9
+
10
+ def call(object)
11
+ {
12
+ "type" => blueprint.type,
13
+ "id" => object.id,
14
+ "attributes" => collect_attributes(object)
15
+ }
16
+ end
17
+
18
+ private
19
+ def collect_attributes(object)
20
+ Hash[
21
+ blueprint.attribute_names(object).map do |attribute_name|
22
+ [format_attribute_name(attribute_name), object.send(attribute_name)]
23
+ end
24
+ ]
25
+ end
26
+
27
+ def format_attribute_name(attribute_name)
28
+ attribute_name.gsub("_", "-")
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,22 @@
1
+ module EdgycircleToolbox
2
+ module Sonapi
3
+ class ValidationError
4
+ include SerializableError
5
+
6
+ attr_reader :detail, :attribute
7
+
8
+ def initialize(attribute, detail)
9
+ @attribute = attribute
10
+ @detail = detail
11
+ end
12
+
13
+ def pointer
14
+ "data/attributes/#{attribute.to_s.gsub("_", "-")}"
15
+ end
16
+
17
+ def title
18
+ "Validation Error"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module EdgycircleToolbox
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: edgycircle_toolbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Strauß
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-01-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-validation
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.10'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: dry-container
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.5'
83
+ description: A loose collection of Ruby related code that currently has specific place
84
+ to live in.
85
+ email:
86
+ - david@strauss.io
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - README.md
94
+ - Rakefile
95
+ - edgycircle_toolbox.gemspec
96
+ - lib/edgycircle_toolbox.rb
97
+ - lib/edgycircle_toolbox/cqrs/command.rb
98
+ - lib/edgycircle_toolbox/cqrs/command_builder.rb
99
+ - lib/edgycircle_toolbox/cqrs/command_bus.rb
100
+ - lib/edgycircle_toolbox/cqrs/command_result.rb
101
+ - lib/edgycircle_toolbox/cqrs/handler.rb
102
+ - lib/edgycircle_toolbox/cqrs/message_bus.rb
103
+ - lib/edgycircle_toolbox/cqrs/model_collector.rb
104
+ - lib/edgycircle_toolbox/has_attributes.rb
105
+ - lib/edgycircle_toolbox/sonapi/blueprint.rb
106
+ - lib/edgycircle_toolbox/sonapi/deserializer.rb
107
+ - lib/edgycircle_toolbox/sonapi/dynamic_resource.rb
108
+ - lib/edgycircle_toolbox/sonapi/error_resource.rb
109
+ - lib/edgycircle_toolbox/sonapi/resource.rb
110
+ - lib/edgycircle_toolbox/sonapi/serializable_error.rb
111
+ - lib/edgycircle_toolbox/sonapi/serializer.rb
112
+ - lib/edgycircle_toolbox/sonapi/validation_error.rb
113
+ - lib/edgycircle_toolbox/version.rb
114
+ homepage: https://www.edgycircle.com
115
+ licenses:
116
+ - MIT
117
+ metadata:
118
+ allowed_push_host: https://rubygems.org
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.4.5
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: A loose collection of Ruby related code that currently has specific place
139
+ to live in.
140
+ test_files: []