spectifly 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.gitignore +17 -0
  2. data/.ruby-version +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +128 -0
  6. data/Rakefile +8 -0
  7. data/lib/entities/association.entity +11 -0
  8. data/lib/entities/entity.entity +16 -0
  9. data/lib/entities/field.entity +35 -0
  10. data/lib/entities/related_entity.entity +28 -0
  11. data/lib/spectifly.rb +7 -0
  12. data/lib/spectifly/base.rb +8 -0
  13. data/lib/spectifly/base/association.rb +18 -0
  14. data/lib/spectifly/base/builder.rb +102 -0
  15. data/lib/spectifly/base/configuration.rb +19 -0
  16. data/lib/spectifly/base/entity_node.rb +55 -0
  17. data/lib/spectifly/base/field.rb +42 -0
  18. data/lib/spectifly/base/types.rb +27 -0
  19. data/lib/spectifly/configuration.rb +17 -0
  20. data/lib/spectifly/entity.rb +106 -0
  21. data/lib/spectifly/json.rb +8 -0
  22. data/lib/spectifly/json/association.rb +19 -0
  23. data/lib/spectifly/json/builder.rb +21 -0
  24. data/lib/spectifly/json/field.rb +28 -0
  25. data/lib/spectifly/json/types.rb +6 -0
  26. data/lib/spectifly/support.rb +32 -0
  27. data/lib/spectifly/tasks.rb +20 -0
  28. data/lib/spectifly/version.rb +3 -0
  29. data/lib/spectifly/xsd.rb +8 -0
  30. data/lib/spectifly/xsd/association.rb +31 -0
  31. data/lib/spectifly/xsd/builder.rb +43 -0
  32. data/lib/spectifly/xsd/field.rb +92 -0
  33. data/lib/spectifly/xsd/types.rb +32 -0
  34. data/lib/tasks/spectifly.rake +6 -0
  35. data/spec/expectations/extended.xsd +15 -0
  36. data/spec/expectations/group.json +37 -0
  37. data/spec/expectations/group.xsd +39 -0
  38. data/spec/expectations/individual.json +57 -0
  39. data/spec/expectations/individual.xsd +47 -0
  40. data/spec/expectations/presented/masterless_group.json +30 -0
  41. data/spec/expectations/presented/masterless_group.xsd +34 -0
  42. data/spec/expectations/presented/positionless_individual.json +44 -0
  43. data/spec/expectations/presented/positionless_individual.xsd +35 -0
  44. data/spec/fixtures/group.entity +23 -0
  45. data/spec/fixtures/individual.entity +33 -0
  46. data/spec/fixtures/invalid/multiple_root.entity +8 -0
  47. data/spec/fixtures/invalid/no_fields.entity +2 -0
  48. data/spec/fixtures/presenters/masterless_group.entity +8 -0
  49. data/spec/fixtures/presenters/positionless_individual.entity +12 -0
  50. data/spec/spec_helper.rb +10 -0
  51. data/spec/spectifly/base/builder_spec.rb +29 -0
  52. data/spec/spectifly/base/entity_node_spec.rb +29 -0
  53. data/spec/spectifly/base/field_spec.rb +100 -0
  54. data/spec/spectifly/configuration_spec.rb +42 -0
  55. data/spec/spectifly/entity_spec.rb +189 -0
  56. data/spec/spectifly/json/builder_spec.rb +42 -0
  57. data/spec/spectifly/json/field_spec.rb +26 -0
  58. data/spec/spectifly/support_spec.rb +53 -0
  59. data/spec/spectifly/xsd/builder_spec.rb +51 -0
  60. data/spec/spectifly/xsd/field_spec.rb +12 -0
  61. data/spec/spectifly/xsd/types_spec.rb +11 -0
  62. data/spec/support/path_helper.rb +28 -0
  63. data/spectifly.gemspec +32 -0
  64. metadata +251 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p392
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specified in spectifly.gemspec
4
+ gemspec
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,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'spectifly/tasks'
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec) do |spec|
5
+ spec.pattern = FileList['spec/**/*_spec.rb']
6
+ end
7
+
8
+ task :default => :spec
@@ -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,7 @@
1
+ require_relative 'spectifly/support'
2
+ require_relative 'spectifly/entity'
3
+ require_relative 'spectifly/base'
4
+ require_relative 'spectifly/configuration'
5
+
6
+ module Spectifly
7
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'base/builder'
2
+ require_relative 'base/field'
3
+ require_relative 'base/types'
4
+
5
+ module Spectifly
6
+ module Base
7
+ end
8
+ end
@@ -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