spectifly 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +128 -0
- data/Rakefile +8 -0
- data/lib/entities/association.entity +11 -0
- data/lib/entities/entity.entity +16 -0
- data/lib/entities/field.entity +35 -0
- data/lib/entities/related_entity.entity +28 -0
- data/lib/spectifly.rb +7 -0
- data/lib/spectifly/base.rb +8 -0
- data/lib/spectifly/base/association.rb +18 -0
- data/lib/spectifly/base/builder.rb +102 -0
- data/lib/spectifly/base/configuration.rb +19 -0
- data/lib/spectifly/base/entity_node.rb +55 -0
- data/lib/spectifly/base/field.rb +42 -0
- data/lib/spectifly/base/types.rb +27 -0
- data/lib/spectifly/configuration.rb +17 -0
- data/lib/spectifly/entity.rb +106 -0
- data/lib/spectifly/json.rb +8 -0
- data/lib/spectifly/json/association.rb +19 -0
- data/lib/spectifly/json/builder.rb +21 -0
- data/lib/spectifly/json/field.rb +28 -0
- data/lib/spectifly/json/types.rb +6 -0
- data/lib/spectifly/support.rb +32 -0
- data/lib/spectifly/tasks.rb +20 -0
- data/lib/spectifly/version.rb +3 -0
- data/lib/spectifly/xsd.rb +8 -0
- data/lib/spectifly/xsd/association.rb +31 -0
- data/lib/spectifly/xsd/builder.rb +43 -0
- data/lib/spectifly/xsd/field.rb +92 -0
- data/lib/spectifly/xsd/types.rb +32 -0
- data/lib/tasks/spectifly.rake +6 -0
- data/spec/expectations/extended.xsd +15 -0
- data/spec/expectations/group.json +37 -0
- data/spec/expectations/group.xsd +39 -0
- data/spec/expectations/individual.json +57 -0
- data/spec/expectations/individual.xsd +47 -0
- data/spec/expectations/presented/masterless_group.json +30 -0
- data/spec/expectations/presented/masterless_group.xsd +34 -0
- data/spec/expectations/presented/positionless_individual.json +44 -0
- data/spec/expectations/presented/positionless_individual.xsd +35 -0
- data/spec/fixtures/group.entity +23 -0
- data/spec/fixtures/individual.entity +33 -0
- data/spec/fixtures/invalid/multiple_root.entity +8 -0
- data/spec/fixtures/invalid/no_fields.entity +2 -0
- data/spec/fixtures/presenters/masterless_group.entity +8 -0
- data/spec/fixtures/presenters/positionless_individual.entity +12 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/spectifly/base/builder_spec.rb +29 -0
- data/spec/spectifly/base/entity_node_spec.rb +29 -0
- data/spec/spectifly/base/field_spec.rb +100 -0
- data/spec/spectifly/configuration_spec.rb +42 -0
- data/spec/spectifly/entity_spec.rb +189 -0
- data/spec/spectifly/json/builder_spec.rb +42 -0
- data/spec/spectifly/json/field_spec.rb +26 -0
- data/spec/spectifly/support_spec.rb +53 -0
- data/spec/spectifly/xsd/builder_spec.rb +51 -0
- data/spec/spectifly/xsd/field_spec.rb +12 -0
- data/spec/spectifly/xsd/types_spec.rb +11 -0
- data/spec/support/path_helper.rb +28 -0
- data/spectifly.gemspec +32 -0
- metadata +251 -0
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p392
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ravi Gadad
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# Spectifly
|
2
|
+
|
3
|
+
Say you've been tasked with building a web service that accepts a purchase
|
4
|
+
order in XML format, adds some additional fields, sends the modified purchase
|
5
|
+
order to another web service via JSON, receives a JSON response with fulfillment
|
6
|
+
details, and finally sends back the same order with all this additional data
|
7
|
+
back to the original client, via XML. You've been given specifications for
|
8
|
+
what fields the purchase order needs to have at every step of the process, and
|
9
|
+
you need to validate the object differently for each transaction.
|
10
|
+
|
11
|
+
Spectifly is here to help. It's a library with a highly opinionated markup language (based on YAML) for specifying entities and their presentations (using
|
12
|
+
`*.entity` files), and uses these entity specifications to generate XSDs, JSON
|
13
|
+
validators, and fixture data for testing.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'spectifly'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install spectifly
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
The Spectifly markup language is a subset of YAML. Only one root node is allowed
|
32
|
+
per file (since a single `.entity` file represents a single business entity),
|
33
|
+
and there are other restrictions, as well as additional features, which we'll
|
34
|
+
discuss here.
|
35
|
+
|
36
|
+
Here's an example entity:
|
37
|
+
|
38
|
+
```YAML
|
39
|
+
Widget:
|
40
|
+
Description: A widget produced by WidgetCo
|
41
|
+
Fields:
|
42
|
+
Name*:
|
43
|
+
Description: Display name of widget
|
44
|
+
|
45
|
+
Created At:
|
46
|
+
Description: When the widget was built
|
47
|
+
Type: DateTime
|
48
|
+
|
49
|
+
Awesome?:
|
50
|
+
Description: Whether or not the widget is awesome
|
51
|
+
Related Entities:
|
52
|
+
Belongs To:
|
53
|
+
Manufacturing Plant*:
|
54
|
+
Description: The WidgetCo location that constructed the widget
|
55
|
+
Type: Manufacturing Location
|
56
|
+
Has Many:
|
57
|
+
Accessories:
|
58
|
+
Description: Widget Add-ons that augment the widget's
|
59
|
+
functionality
|
60
|
+
Type: Add On
|
61
|
+
```
|
62
|
+
|
63
|
+
The root node, `Widget:` above, is required, and there can be only one node at
|
64
|
+
this level.
|
65
|
+
|
66
|
+
Directly below the root node, the only valid nodes are `Description` and
|
67
|
+
`Fields`. The `Description` is a plain language description of the entity. The `Related Entities` node includes associations to other entities, grouped by how the entity is related.
|
68
|
+
|
69
|
+
### Fields
|
70
|
+
|
71
|
+
`Fields` is a YAML tree with one or more field entries, which have their own
|
72
|
+
specification. The key of a field node is the field's name, and the value can
|
73
|
+
either be null, or another tree with several possible keys, all of which are
|
74
|
+
optional themselves:
|
75
|
+
|
76
|
+
* `Description` (string): A plain-language description of the variable
|
77
|
+
* `Multiple` (Boolean): Whether or not this variable can occur multiple times
|
78
|
+
* `Type` (string): Type of data contained in this field (defaults to String)
|
79
|
+
* `Validations` (string or list of strings): Restrictions on the values allowed
|
80
|
+
in this field
|
81
|
+
* `Valid Values` (list of strings): List of possible literal values for this
|
82
|
+
field
|
83
|
+
* `Minimum Value` (numeric): Minimum value allowed for this field (numeric
|
84
|
+
fields only)
|
85
|
+
* `Maximum Value` (numeric): Maximum value allowed for this field (numeric
|
86
|
+
fields only)
|
87
|
+
|
88
|
+
#### Special Tokens
|
89
|
+
|
90
|
+
The key of the field node (the name of the field) can also end with one or two
|
91
|
+
special "tokens":
|
92
|
+
|
93
|
+
* The `*` token, as in the `Name*` field in the above example, makes the field
|
94
|
+
required, which means it must occur once (or at least once, for Multiple
|
95
|
+
fields).
|
96
|
+
* The `?` token, as in the example's `Awesome?` field, sets `Type` to Boolean.
|
97
|
+
|
98
|
+
These tokens can be combined, if you have a required Boolean field.
|
99
|
+
|
100
|
+
If the `?` shortcut conflicts with the explicit key (e.g. a `Rad?` field with
|
101
|
+
`Type: String`), an exception will be thrown during parsing.
|
102
|
+
|
103
|
+
### Related Entities
|
104
|
+
|
105
|
+
`Related Entities`, is a YAML tree that enumerates the entities with which the defined entity are associated. They are grouped by type of relationship and include the name of the association and which type of entity that association links to. Like `Fields`, the `*` special token can be used to mark an entity as required.
|
106
|
+
|
107
|
+
* `Belongs To` is fairly straight-forward, i.e. a kitten belongs to a
|
108
|
+
litter of kittens
|
109
|
+
|
110
|
+
* `Has One` also self-explanatory, i.e. a website user has one (and only
|
111
|
+
one) profile -- an inverse of `Belongs To`
|
112
|
+
|
113
|
+
* `Has Many` also an inverse of `Belongs To`, but 1 or more
|
114
|
+
|
115
|
+
* `Has and Belongs To Many` is a many-to-many relationship between two
|
116
|
+
entities, such as a dish being made up of many ingredients and an
|
117
|
+
ingredient being part of many dishes
|
118
|
+
|
119
|
+
Under each of these Association nodes the YAML tree lists entities that relate to
|
120
|
+
the described entity in that way.
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
1. Fork it
|
125
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
126
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
127
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
128
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Association:
|
2
|
+
Description: The way in which an entity relates to another entity
|
3
|
+
Related Entities:
|
4
|
+
Has Many:
|
5
|
+
Entities:
|
6
|
+
Type: Entity
|
7
|
+
Description: The entity to which the given entity relates
|
8
|
+
|
9
|
+
Belongs To:
|
10
|
+
Related Entity*:
|
11
|
+
Description: The list of associations to entities that a given entity has
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Entity:
|
2
|
+
Description: A Spectifly entity definition
|
3
|
+
Fields:
|
4
|
+
Description:
|
5
|
+
Description: A plain-language description of the entity
|
6
|
+
|
7
|
+
Related Entities:
|
8
|
+
Has Many:
|
9
|
+
Fields*:
|
10
|
+
Description: Data variables associated with the given entity
|
11
|
+
Type: Field
|
12
|
+
|
13
|
+
Has One:
|
14
|
+
Related Entities:
|
15
|
+
Description: Entities associated with the given entity
|
16
|
+
Type: Related Entity
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Field:
|
2
|
+
Description: A single data variable associated with an entity
|
3
|
+
Fields:
|
4
|
+
Description:
|
5
|
+
Description: A plain-language description of the variable
|
6
|
+
|
7
|
+
Multiple?:
|
8
|
+
Description: Whether or not this variable can occur multiple times
|
9
|
+
|
10
|
+
Type:
|
11
|
+
Description: Type of data contained in this field (defaults to String)
|
12
|
+
|
13
|
+
Validations:
|
14
|
+
Description: Restrictions on the values allowed in this field
|
15
|
+
Multiple: True
|
16
|
+
Valid Values:
|
17
|
+
- Must be positive
|
18
|
+
- Must be non-negative
|
19
|
+
|
20
|
+
Valid Values:
|
21
|
+
Description: List of possible literal values for this field
|
22
|
+
Multiple: True
|
23
|
+
|
24
|
+
Minimum Value:
|
25
|
+
Description: Minimum value allowed for this field (numeric fields only)
|
26
|
+
Type: Numeric
|
27
|
+
|
28
|
+
Maximum Value:
|
29
|
+
Description: Maximum value allowed for this field (numeric fields only)
|
30
|
+
Type: Numeric
|
31
|
+
|
32
|
+
Related Entities:
|
33
|
+
Belongs To:
|
34
|
+
Entity*:
|
35
|
+
Type: Entity
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Related Entity:
|
2
|
+
Description: Entities associated with the base entity
|
3
|
+
Related Entities:
|
4
|
+
Has One:
|
5
|
+
Belongs To:
|
6
|
+
Description: Entities that own one of the given entity.
|
7
|
+
Type: Association
|
8
|
+
Example: A comment belongs to a blog post
|
9
|
+
|
10
|
+
Has One:
|
11
|
+
Description: Entities that are owned by the given entity. Given entity has one of these entities.
|
12
|
+
Type: Association
|
13
|
+
Example: A building has one address
|
14
|
+
|
15
|
+
Has Many:
|
16
|
+
Description: Entities that are owned by the given entity. Given entity can have multiple of these entities.
|
17
|
+
Type: Association
|
18
|
+
Example: A book has many pages
|
19
|
+
|
20
|
+
Belongs To Many:
|
21
|
+
Description: Entities that own multiple of a given entitiy.
|
22
|
+
Type: Association
|
23
|
+
Example: a Street belongs to many addresses
|
24
|
+
|
25
|
+
Has and Belongs To Many:
|
26
|
+
Description: Entities that are associated with other entities. Each given entity can have multiple of this entity and can also belong to many of this entity.
|
27
|
+
Type: Association
|
28
|
+
Example: a Library user can borrow many books and a book can be borrowed by many library users
|
data/lib/spectifly.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'entity_node'
|
2
|
+
|
3
|
+
module Spectifly
|
4
|
+
module Base
|
5
|
+
class Association < EntityNode
|
6
|
+
attr_reader :relationship
|
7
|
+
|
8
|
+
def initialize(field_name, options = {})
|
9
|
+
super
|
10
|
+
@relationship = options.delete(:relationship)
|
11
|
+
end
|
12
|
+
|
13
|
+
def multiple?
|
14
|
+
['has_many', 'has_many_and_belongs_to'].include? relationship
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require_relative 'field'
|
3
|
+
require_relative 'association'
|
4
|
+
require_relative 'types'
|
5
|
+
|
6
|
+
module Spectifly
|
7
|
+
module Base
|
8
|
+
class Builder
|
9
|
+
class << self
|
10
|
+
def from_path(path, options = {})
|
11
|
+
new(Spectifly::Entity.parse(path), options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def module_name
|
15
|
+
Spectifly::Support.get_module(self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(entity, options = {})
|
20
|
+
@options = options
|
21
|
+
@entity = entity
|
22
|
+
end
|
23
|
+
|
24
|
+
def present_as(presenter_entity)
|
25
|
+
@entity = @entity.present_as(presenter_entity)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def types
|
30
|
+
[fields.map(&:type) + associations.map(&:type)].flatten.compact.uniq
|
31
|
+
end
|
32
|
+
|
33
|
+
def native_types
|
34
|
+
@native_types ||= begin
|
35
|
+
eval("#{self.class.module_name}::Types::Native")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def utilized_extended_types
|
40
|
+
@utilized_extended_types ||= begin
|
41
|
+
extended_types = eval("#{self.class.module_name}::Types::Extended")
|
42
|
+
extended_types.select { |k, v| types.include?(k) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def custom_types
|
47
|
+
types - native_types - utilized_extended_types.keys
|
48
|
+
end
|
49
|
+
|
50
|
+
def fields
|
51
|
+
@fields ||= begin
|
52
|
+
fields = []
|
53
|
+
@entity.fields.each do |name, attributes|
|
54
|
+
fields << field_class.new(name.dup, attributes.dup)
|
55
|
+
end
|
56
|
+
fields
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def associations
|
61
|
+
@associations ||= begin
|
62
|
+
associations = []
|
63
|
+
@entity.relationships.each do |relationship_type, type_associations|
|
64
|
+
relationship_type = Spectifly::Support.tokenize(relationship_type)
|
65
|
+
type_associations.each do |name, attributes|
|
66
|
+
associations << association_class.new(
|
67
|
+
name.dup, attributes.dup.merge(:relationship => relationship_type)
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
associations
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def utilized_extended_type_fields
|
76
|
+
@utilized_extended_type_fields ||= begin
|
77
|
+
fields = []
|
78
|
+
utilized_extended_types.each do |name, attributes|
|
79
|
+
fields << field_class.new(name.dup, attributes.dup)
|
80
|
+
end
|
81
|
+
fields
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def root
|
86
|
+
@entity.root
|
87
|
+
end
|
88
|
+
|
89
|
+
def field_class
|
90
|
+
eval("#{self.class.module_name}::Field")
|
91
|
+
end
|
92
|
+
|
93
|
+
def association_class
|
94
|
+
eval("#{self.class.module_name}::Association")
|
95
|
+
end
|
96
|
+
|
97
|
+
def build
|
98
|
+
raise 'Subclass Responsibility'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Spectifly
|
2
|
+
module Base
|
3
|
+
class Configuration
|
4
|
+
def initialize(config = {})
|
5
|
+
@entity_path = config.fetch(:entity_path)
|
6
|
+
@presenter_path = config[:presenter_path]
|
7
|
+
end
|
8
|
+
|
9
|
+
def presenter_path
|
10
|
+
@presenter_path ||= begin
|
11
|
+
proposed_path = File.join(@entity_path, 'presenters')
|
12
|
+
if Dir.exists?(proposed_path)
|
13
|
+
@presenter_path = proposed_path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|