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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8db17897f669a971aa5b22ba819978953a3eda10
4
- data.tar.gz: d3f9514bb722c2dd6eb51a4beb06d0e48e606524
3
+ metadata.gz: 31bf7a0b474a852c085027772e7d40715b88ae07
4
+ data.tar.gz: ed1008231c6ec39463300838f284f16951d51850
5
5
  SHA512:
6
- metadata.gz: 22a3ec1e45a1e3dce5cb8c36751723c88a37002d6a654e7ac7899a0dd926a5f3657f422592e5e94795df8cf1235e1bc6023a277dcef638445dbd99cbfeb1c3c3
7
- data.tar.gz: d34e1f0f3288fa495664c7c63710185ee1a9eb7ae2a61591a3e50b85c8a2b561e67ac5f5184cefcf3bca1bb44a7508dbc3de949fec934943d1159c8be8b4810a
6
+ metadata.gz: d931a7f8efab73769eeaedaa45ee3935693e17c0bf88d8b3da9f1116a1538f84cd644821c2f048f0fa6562efcd76667cde7fe0a6ba9155e8fb686d6245ef3877
7
+ data.tar.gz: 6ecc80dbd308f689ca8fc19ec4571dab4587c9b8de7620d66669c27715b4a7613c494d5a8ddf56c11b1bc428cfa37c36a2ebab216a3049849680c9de82991c15
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /.tags*
11
+ /tags
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --order random
3
+
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 a plugin for building class from reusable components.
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
- - [ ] Basic case for nested commands
61
- - [ ] Allow for "constantization" of command_namespaces option.
60
+ - [X] Basic case for nested commands
61
+ - [X] Allow for "constantization" of command_namespaces option.
data/Rakefile CHANGED
@@ -30,3 +30,4 @@ FlayTask.new do |task|
30
30
  end
31
31
 
32
32
  task(default: ['rubocop', 'reek', 'flay', 'spec'])
33
+ task(release: :default)
@@ -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{A placeholder for an attribute declaration mechanism}
13
- spec.description = %q{A placeholder for an attribute declaration mechanism}
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 "reek"
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 "byebug"
31
+ spec.add_development_dependency "shoulda-matchers"
33
32
  end
@@ -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] Gives a builder to provide additional command style customizations
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
- def defer(options = {}, &deferred_operation)
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 public
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
- # @api private
27
- def defer(options = {}, &block)
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, :options
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 :name, :options
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 additional_configuration [Proc] A means to nest additional configuration
16
- def initialize(builder:, &additional_configuration)
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.additional_configuration = additional_configuration
18
+ self.pre_deferment_operation = pre_deferment_operation
19
19
  self.attribute_names = []
20
20
  end
21
21
 
22
- # :reek:NestedIterators: { exclude: [ 'Democritus::ClassBuilder::Commands::Attributes#call' ] }
23
- # :reek:TooManyStatements: { exclude: [ 'Democritus::ClassBuilder::Commands::Attributes#call' ] }
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, &additional_configuration) if additional_configuration.respond_to?(:call)
28
- defer do |subject|
29
- subject.module_exec(@attribute_names) do |attribute_names|
30
- define_method(:initialize) do |**attributes|
31
- attribute_names.each do |attribute_name|
32
- send("#{attribute_name}=", attributes.fetch(attribute_name.to_sym, nil))
33
- end
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 :additional_configuration, :attribute_names
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
@@ -49,6 +49,7 @@ module Democritus
49
49
  class_builder.generate_class
50
50
  end
51
51
 
52
+ # Used to answer the question "Does the given Hash key represent a command?"
52
53
  KEY_IS_COMMAND_REGEXP = /\A\#(.+)$/.freeze
53
54
 
54
55
  private
@@ -2,5 +2,5 @@ module Democritus
2
2
  # Democritus is striving to adhere to semantic versioning.
3
3
  #
4
4
  # @see semver.org
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.1"
6
6
  end
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.0
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-07 00:00:00.000000000 Z
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: reek
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: byebug
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: A placeholder for an attribute declaration mechanism
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: A placeholder for an attribute declaration mechanism
201
+ summary: Democritus is library for building classes from reusable components.
215
202
  test_files: []