democritus 0.2.0 → 0.2.1
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 +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
|
[](https://codeclimate.com/github/jeremyf/democritus)
|
7
7
|
[](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: []
|