edgycircle_toolbox 0.1.0

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