democritus 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/README.md +3 -3
- data/Rakefile +1 -0
- data/democritus.gemspec +4 -5
- data/lib/democritus.rb +24 -1
- data/lib/democritus/class_builder.rb +60 -3
- data/lib/democritus/class_builder/command.rb +8 -5
- data/lib/democritus/class_builder/commands/attribute.rb +13 -2
- data/lib/democritus/class_builder/commands/attributes.rb +48 -13
- data/lib/democritus/from_json_class_builder.rb +1 -0
- data/lib/democritus/version.rb +1 -1
- metadata +7 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31bf7a0b474a852c085027772e7d40715b88ae07
|
4
|
+
data.tar.gz: ed1008231c6ec39463300838f284f16951d51850
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d931a7f8efab73769eeaedaa45ee3935693e17c0bf88d8b3da9f1116a1538f84cd644821c2f048f0fa6562efcd76667cde7fe0a6ba9155e8fb686d6245ef3877
|
7
|
+
data.tar.gz: 6ecc80dbd308f689ca8fc19ec4571dab4587c9b8de7620d66669c27715b4a7613c494d5a8ddf56c11b1bc428cfa37c36a2ebab216a3049849680c9de82991c15
|
data/.gitignore
CHANGED
data/.rspec
ADDED
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
[![Test Coverage](https://codeclimate.com/github/jeremyf/democritus/badges/coverage.svg)](https://codeclimate.com/github/jeremyf/democritus)
|
7
7
|
[![Documentation Status](http://inch-ci.org/github/jeremyf/democritus.svg?branch=master)](http://inch-ci.org/github/jeremyf/democritus)
|
8
8
|
|
9
|
-
Democritus is
|
9
|
+
Democritus is library for building classes from reusable components.
|
10
10
|
|
11
11
|
Democritus is inspired as followup of a common pattern that I saw in the development of [Sipity's](https://github.com/ndlib/sipity/) form objects.
|
12
12
|
It also aims to address the needs of Sipity's yet to be developed sibling application; The dissemination of processed data.
|
@@ -57,5 +57,5 @@ ApprovalForm = Demcritus.build_from_json(json)
|
|
57
57
|
- [x] [Command::Attirubtes](./lib/democritus/class_builder/commands/attributes.rb)
|
58
58
|
- [ ] Create the commands that build a Sipity processing form
|
59
59
|
- [X] Build class from JSON configuration
|
60
|
-
- [
|
61
|
-
- [
|
60
|
+
- [X] Basic case for nested commands
|
61
|
+
- [X] Allow for "constantization" of command_namespaces option.
|
data/Rakefile
CHANGED
data/democritus.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Jeremy Friesen"]
|
10
10
|
spec.email = ["jeremy.n.friesen@gmail.com"]
|
11
11
|
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.description = %q{
|
12
|
+
spec.summary = %q{Democritus is library for building classes from reusable components.}
|
13
|
+
spec.description = %q{Democritus is library for building classes from reusable components.}
|
14
14
|
spec.homepage = "https://github.com/jeremyf/democritus"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
@@ -23,11 +23,10 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
24
|
spec.add_development_dependency "rspec", "~> 3.2"
|
25
25
|
spec.add_development_dependency "rspec-its", "~> 1.2"
|
26
|
-
spec.add_development_dependency "
|
27
|
-
spec.add_development_dependency "flay"
|
26
|
+
spec.add_development_dependency "rubycritic"
|
28
27
|
spec.add_development_dependency "rubocop"
|
29
28
|
spec.add_development_dependency "rubocop-rspec"
|
30
29
|
spec.add_development_dependency "simplecov"
|
31
30
|
spec.add_development_dependency "codeclimate-test-reporter"
|
32
|
-
spec.add_development_dependency "
|
31
|
+
spec.add_development_dependency "shoulda-matchers"
|
33
32
|
end
|
data/lib/democritus.rb
CHANGED
@@ -8,11 +8,23 @@ require 'democritus/from_json_class_builder'
|
|
8
8
|
# for understanding the galaxy of objects.
|
9
9
|
module Democritus
|
10
10
|
# @api public
|
11
|
+
# @since 0.1.0
|
11
12
|
#
|
12
13
|
# Responsible for building a class based on atomic components.
|
13
14
|
#
|
14
|
-
# @yield [Democritus::ClassBuilder]
|
15
|
+
# @yield [Democritus::ClassBuilder] a builder object to provide additional command style customizations
|
15
16
|
# @return Class
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# NamedPerson = Democritus.build do |builder|
|
20
|
+
# builder.attributes do
|
21
|
+
# attribute(name: :given_name)
|
22
|
+
# attribute(name: :surname)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# NamedPerson.new(given_name: 'Farrokh', surname: 'Bulsara')
|
26
|
+
#
|
27
|
+
# @see Demcoritus::ClassBuilder
|
16
28
|
def self.build(&configuration_block)
|
17
29
|
builder = ClassBuilder.new
|
18
30
|
builder.customize(&configuration_block)
|
@@ -20,10 +32,21 @@ module Democritus
|
|
20
32
|
end
|
21
33
|
|
22
34
|
# @api public
|
35
|
+
# @since 0.2.0
|
23
36
|
#
|
24
37
|
# Responsible for building a class based on the given JSON object.
|
25
38
|
#
|
39
|
+
# @param [String] A "well-formed" JSON document
|
26
40
|
# @return Class
|
41
|
+
#
|
42
|
+
# NamedPerson = Democritus.build_from_json(%{
|
43
|
+
# "#attributes": {
|
44
|
+
# "#attribute": [{ "name": "given_name" }, { "name": "surname"}]
|
45
|
+
# }
|
46
|
+
# })
|
47
|
+
# NamedPerson.new(given_name: 'Farrokh', surname: 'Bulsara')
|
48
|
+
#
|
49
|
+
# @see Demcoritus::FromJsonClassBuilder for details on "well-formed"
|
27
50
|
def self.build_from_json(json)
|
28
51
|
builder = FromJsonClassBuilder.new(json)
|
29
52
|
builder.generate_class
|
@@ -29,11 +29,23 @@ module Democritus
|
|
29
29
|
# The above #a_customization method is captured in the customization_module and applied as an instance method
|
30
30
|
# to the generated class.
|
31
31
|
attr_accessor :customization_module
|
32
|
+
|
33
|
+
# The module with the generated code.
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# Democritus.build do |builder|
|
37
|
+
# builder.a_command
|
38
|
+
# def a_customization
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# The above `builder.a_command` invocation is captured and is applied to the generation_module.
|
32
43
|
attr_accessor :generation_module
|
33
44
|
|
34
45
|
# Command operations to be applied as class methods of the generated_class.
|
35
46
|
attr_accessor :class_operations
|
36
47
|
|
48
|
+
# The default namespaces in which Democritus will look up commands.
|
37
49
|
def default_command_namespaces
|
38
50
|
[Democritus::ClassBuilder::Commands]
|
39
51
|
end
|
@@ -42,7 +54,14 @@ module Democritus
|
|
42
54
|
attr_reader :command_namespaces
|
43
55
|
|
44
56
|
def command_namespaces=(input)
|
45
|
-
@command_namespaces = Array(input)
|
57
|
+
@command_namespaces = Array(input).map do |command_namespace|
|
58
|
+
case command_namespace
|
59
|
+
when Module, Class
|
60
|
+
command_namespace
|
61
|
+
else
|
62
|
+
Object.const_get(command_namespace.to_s)
|
63
|
+
end
|
64
|
+
end
|
46
65
|
end
|
47
66
|
|
48
67
|
# Command operations to be applied as instance methods of the generated_class.
|
@@ -89,17 +108,36 @@ module Democritus
|
|
89
108
|
customization_mod = customization_module # get a local binding
|
90
109
|
apply_operations(instance_operations, generation_mod)
|
91
110
|
generated_class = Class.new do
|
111
|
+
# requires the local binding from above
|
92
112
|
const_set :GeneratedMethods, generation_mod
|
93
113
|
const_set :Customizations, customization_mod
|
114
|
+
# because == and case operators are useful
|
94
115
|
include DemocritusObjectTag
|
95
116
|
include generation_mod
|
117
|
+
|
118
|
+
# customization should be applied last as it allows for "overrides" of generated methods
|
96
119
|
include customization_mod
|
97
120
|
end
|
98
121
|
generated_class
|
99
122
|
end
|
100
123
|
# rubocop:enable MethodLength
|
101
124
|
|
102
|
-
|
125
|
+
# @api public
|
126
|
+
#
|
127
|
+
# When configuring the class that is being built, we don't want to apply all of the modifications at once, instead allowing them
|
128
|
+
# to be applied in a specified order.
|
129
|
+
#
|
130
|
+
# @example
|
131
|
+
# Democritus::ClassBuilder.new.defer(prepend: true) do
|
132
|
+
# define_method(:help) { 'Did you try turning it off and on again?' }
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# @param [Hash] options
|
136
|
+
# @option options [Boolean] :prepend Is there something about this deferred_operation that should happen first?
|
137
|
+
# @param deferred_operation [#call] The operation that will be applied to the generated class
|
138
|
+
#
|
139
|
+
# @return void
|
140
|
+
def defer(**options, &deferred_operation)
|
103
141
|
if options[:prepend]
|
104
142
|
instance_operations.unshift(deferred_operation)
|
105
143
|
else
|
@@ -121,6 +159,16 @@ module Democritus
|
|
121
159
|
private
|
122
160
|
|
123
161
|
# @api public
|
162
|
+
#
|
163
|
+
# The guts of the Democritus plugin system. The ClassBuilder brokers missing methods to registered commands within the
|
164
|
+
# CommandNamespace.
|
165
|
+
#
|
166
|
+
# @param method_name [Symbol] Name of the message being sent to this object
|
167
|
+
# @param args Non-keyword arguments for the message sent to this object
|
168
|
+
# @param kargs Keyword arguments for the message sent to this object
|
169
|
+
# @param block Block argument for the message sent to this object
|
170
|
+
# @return void if there is a Command object that is called
|
171
|
+
# @return unknown if no Command object is found
|
124
172
|
def method_missing(method_name, *args, **kargs, &block)
|
125
173
|
command_name = self.class.command_name_for_method(method_name)
|
126
174
|
command_namespace = command_namespace_for(command_name)
|
@@ -133,17 +181,26 @@ module Democritus
|
|
133
181
|
end
|
134
182
|
|
135
183
|
# @api public
|
184
|
+
#
|
185
|
+
# A required sibling method when implementing #method_missing
|
186
|
+
#
|
187
|
+
# @param method_name [Symbol] Name of the message being sent to this object
|
188
|
+
# @param args Additional arguments passed to the query
|
189
|
+
# @return Boolean
|
136
190
|
def respond_to_missing?(method_name, *args)
|
137
191
|
respond_to_definition(method_name, :respond_to_missing?, *args)
|
138
192
|
end
|
139
193
|
|
140
|
-
# @api
|
194
|
+
# @api private
|
141
195
|
def respond_to_definition(method_name, *)
|
142
196
|
command_name = self.class.command_name_for_method(method_name)
|
143
197
|
command_namespace_for(command_name)
|
144
198
|
end
|
145
199
|
|
146
200
|
# @api private
|
201
|
+
#
|
202
|
+
# Find the first matching the command_namespace that contains the given
|
203
|
+
# command_name
|
147
204
|
def command_namespace_for(command_name)
|
148
205
|
command_namespaces.detect { |cs| cs.const_defined?(command_name) }
|
149
206
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Democritus
|
2
4
|
class ClassBuilder
|
3
5
|
# @api public
|
@@ -17,16 +19,17 @@ module Democritus
|
|
17
19
|
end
|
18
20
|
|
19
21
|
# @api public
|
20
|
-
#
|
21
22
|
# @abstract Subclass and override #call to implement
|
23
|
+
#
|
24
|
+
# Responsible for applying changes to the class that is being built.
|
25
|
+
#
|
26
|
+
# @return void
|
22
27
|
def call
|
23
28
|
fail(NotImplementedError, 'Method #call should be overriden in child classes')
|
24
29
|
end
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
builder.defer(options, &block)
|
29
|
-
end
|
31
|
+
extend Forwardable
|
32
|
+
def_delegator :builder, :defer
|
30
33
|
|
31
34
|
private
|
32
35
|
|
@@ -5,14 +5,21 @@ module Democritus
|
|
5
5
|
module Commands
|
6
6
|
# Command to assign an attribute to the given built class.
|
7
7
|
class Attribute < ::Democritus::ClassBuilder::Command
|
8
|
+
# @param builder [#defer, Democritus::ClassBuilder]
|
9
|
+
# @param name [#to_sym] The name of the attribute
|
10
|
+
# @param options [Hash] The configuration options of the attribute
|
8
11
|
def initialize(name:, **options)
|
9
12
|
self.builder = options.fetch(:builder)
|
10
13
|
self.name = name
|
11
14
|
self.options = options
|
12
15
|
end
|
13
16
|
|
14
|
-
attr_reader :name
|
17
|
+
attr_reader :name
|
18
|
+
attr_reader :options
|
15
19
|
|
20
|
+
# Generate the code for the attribute
|
21
|
+
#
|
22
|
+
# @return void
|
16
23
|
# :reek:NestedIterators: { exclude: [ 'Democritus::ClassBuilder::Commands::Attribute#call' ] }
|
17
24
|
def call
|
18
25
|
defer do |subject|
|
@@ -26,7 +33,11 @@ module Democritus
|
|
26
33
|
|
27
34
|
private
|
28
35
|
|
29
|
-
attr_writer :
|
36
|
+
attr_writer :options
|
37
|
+
|
38
|
+
def name=(input)
|
39
|
+
@name = input.to_sym
|
40
|
+
end
|
30
41
|
end
|
31
42
|
end
|
32
43
|
end
|
@@ -12,39 +12,74 @@ module Democritus
|
|
12
12
|
# end
|
13
13
|
class Attributes < ::Democritus::ClassBuilder::Command
|
14
14
|
# @param builder [Democritus::ClassBuilder]
|
15
|
-
# @param
|
16
|
-
def initialize(builder:, &
|
15
|
+
# @param pre_deferment_operation [Proc] A means to nest additional operations
|
16
|
+
def initialize(builder:, &pre_deferment_operation)
|
17
17
|
self.builder = builder
|
18
|
-
self.
|
18
|
+
self.pre_deferment_operation = pre_deferment_operation
|
19
19
|
self.attribute_names = []
|
20
20
|
end
|
21
21
|
|
22
|
-
#
|
23
|
-
#
|
22
|
+
# @api public
|
23
|
+
#
|
24
|
+
# Method that generates the behavior for the Attributes command
|
25
|
+
#
|
26
|
+
# @return void
|
27
|
+
# @see Democritus::ClassBuilder#defer
|
24
28
|
def call
|
29
|
+
# In order to register any nested attributes, the pre_deferment_operation must be performed
|
30
|
+
# outside of the defer method call. If it were done inside the defer block, then the pre_deferment_operation
|
31
|
+
# might get lost.
|
32
|
+
execute_pre_deferment_operation
|
33
|
+
defer { |subject| configure(subject: subject) }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def execute_pre_deferment_operation
|
39
|
+
return unless pre_deferment_operation.respond_to?(:call)
|
25
40
|
# It may seem a little odd to yield self via an instance_exec, however in some cases I need a
|
26
41
|
# receiver for messages (i.e. FromJsonClassBuilder)
|
27
|
-
instance_exec(self, &
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
42
|
+
instance_exec(self, &pre_deferment_operation)
|
43
|
+
end
|
44
|
+
|
45
|
+
# :reek:NestedIterators: { exclude: [ 'Democritus::ClassBuilder::Commands::Attributes#configure' ] }
|
46
|
+
def configure(subject:)
|
47
|
+
subject.module_exec(@attribute_names) do |attribute_names|
|
48
|
+
define_method(:initialize) do |**attributes|
|
49
|
+
attribute_names.each do |attribute_name|
|
50
|
+
send("#{attribute_name}=", attributes.fetch(attribute_name.to_sym, nil))
|
34
51
|
end
|
35
52
|
end
|
36
53
|
end
|
37
54
|
end
|
38
55
|
|
56
|
+
public
|
57
|
+
|
58
|
+
# @!group Commands Available Within the pre_deferment_operation
|
59
|
+
|
60
|
+
# @api public
|
61
|
+
#
|
62
|
+
# Exposes a mechanism for assigning individual attributes as part of the #attributes command.
|
63
|
+
#
|
64
|
+
# @param name [#to_sym] the name of the attribute
|
65
|
+
# @param options [Hash] additional options passed to the builder#attribute command
|
66
|
+
#
|
67
|
+
# @see Democritus::ClassBuilder::Commands::Attribute
|
68
|
+
# @see Democritus::ClassBuilder
|
39
69
|
def attribute(name:, **options)
|
40
70
|
name = name.to_sym
|
41
71
|
attribute_names << name
|
42
72
|
builder.attribute(name: name, **options)
|
43
73
|
end
|
44
74
|
|
75
|
+
# @!endgroup
|
76
|
+
|
45
77
|
private
|
46
78
|
|
47
|
-
attr_accessor :
|
79
|
+
attr_accessor :pre_deferment_operation
|
80
|
+
|
81
|
+
# [Array] of attribute names that have been generated
|
82
|
+
attr_accessor :attribute_names
|
48
83
|
end
|
49
84
|
end
|
50
85
|
end
|
data/lib/democritus/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: democritus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Friesen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -67,21 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.2'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: flay
|
70
|
+
name: rubycritic
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
73
|
- - ">="
|
@@ -151,7 +137,7 @@ dependencies:
|
|
151
137
|
- !ruby/object:Gem::Version
|
152
138
|
version: '0'
|
153
139
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
140
|
+
name: shoulda-matchers
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
156
142
|
requirements:
|
157
143
|
- - ">="
|
@@ -164,7 +150,7 @@ dependencies:
|
|
164
150
|
- - ">="
|
165
151
|
- !ruby/object:Gem::Version
|
166
152
|
version: '0'
|
167
|
-
description:
|
153
|
+
description: Democritus is library for building classes from reusable components.
|
168
154
|
email:
|
169
155
|
- jeremy.n.friesen@gmail.com
|
170
156
|
executables: []
|
@@ -173,6 +159,7 @@ extra_rdoc_files: []
|
|
173
159
|
files:
|
174
160
|
- ".gitignore"
|
175
161
|
- ".hound.yml"
|
162
|
+
- ".rspec"
|
176
163
|
- ".travis.yml"
|
177
164
|
- Gemfile
|
178
165
|
- LICENSE
|
@@ -211,5 +198,5 @@ rubyforge_project:
|
|
211
198
|
rubygems_version: 2.4.7
|
212
199
|
signing_key:
|
213
200
|
specification_version: 4
|
214
|
-
summary:
|
201
|
+
summary: Democritus is library for building classes from reusable components.
|
215
202
|
test_files: []
|