ecs_on_rails 0.2.2 → 0.3.0

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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +304 -0
  3. data/README.md +365 -50
  4. data/lib/ecs_rails/catalogue/address.rb +39 -0
  5. data/lib/ecs_rails/catalogue/calendar_date.rb +27 -0
  6. data/lib/ecs_rails/catalogue/counter.rb +46 -0
  7. data/lib/ecs_rails/catalogue/discard.rb +36 -0
  8. data/lib/ecs_rails/catalogue/email.rb +35 -0
  9. data/lib/ecs_rails/catalogue/geolocation.rb +53 -0
  10. data/lib/ecs_rails/catalogue/identifier.rb +23 -0
  11. data/lib/ecs_rails/catalogue/image.rb +28 -0
  12. data/lib/ecs_rails/catalogue/link.rb +27 -0
  13. data/lib/ecs_rails/catalogue/marker.rb +38 -0
  14. data/lib/ecs_rails/catalogue/money.rb +86 -0
  15. data/lib/ecs_rails/catalogue/name.rb +37 -0
  16. data/lib/ecs_rails/catalogue/password.rb +50 -0
  17. data/lib/ecs_rails/catalogue/period.rb +57 -0
  18. data/lib/ecs_rails/catalogue/phone.rb +30 -0
  19. data/lib/ecs_rails/catalogue/position.rb +18 -0
  20. data/lib/ecs_rails/catalogue/rating.rb +25 -0
  21. data/lib/ecs_rails/catalogue/relationship.rb +159 -0
  22. data/lib/ecs_rails/catalogue/role.rb +23 -0
  23. data/lib/ecs_rails/catalogue/schema_diff.rb +198 -0
  24. data/lib/ecs_rails/catalogue/search_vector.rb +38 -0
  25. data/lib/ecs_rails/catalogue/state.rb +71 -0
  26. data/lib/ecs_rails/catalogue/tags.rb +64 -0
  27. data/lib/ecs_rails/catalogue/text.rb +29 -0
  28. data/lib/ecs_rails/catalogue/timestamp.rb +34 -0
  29. data/lib/ecs_rails/catalogue/token.rb +77 -0
  30. data/lib/ecs_rails/catalogue.rb +405 -0
  31. data/lib/ecs_rails/component.rb +13 -7
  32. data/lib/ecs_rails/config.rb +33 -11
  33. data/lib/ecs_rails/dsl.rb +463 -97
  34. data/lib/ecs_rails/entity.rb +21 -5
  35. data/lib/ecs_rails/errors.rb +3 -0
  36. data/lib/ecs_rails/inverses.rb +398 -0
  37. data/lib/ecs_rails/lazy.rb +86 -23
  38. data/lib/ecs_rails/markers.rb +197 -0
  39. data/lib/ecs_rails/preloading.rb +19 -8
  40. data/lib/ecs_rails/presence.rb +83 -30
  41. data/lib/ecs_rails/querying.rb +158 -12
  42. data/lib/ecs_rails/railtie.rb +10 -0
  43. data/lib/ecs_rails/registry.rb +66 -21
  44. data/lib/ecs_rails/relationships.rb +168 -160
  45. data/lib/ecs_rails/slots.rb +183 -0
  46. data/lib/ecs_rails/validations.rb +3 -5
  47. data/lib/ecs_rails/version.rb +2 -2
  48. data/lib/ecs_rails.rb +7 -3
  49. data/lib/generators/ecs_rails/catalogue_options.rb +66 -0
  50. data/lib/generators/ecs_rails/component/templates/migration.rb.tt +9 -5
  51. data/lib/generators/ecs_rails/component/templates/model.rb.tt +2 -1
  52. data/lib/generators/ecs_rails/entity/entity_generator.rb +93 -0
  53. data/lib/generators/ecs_rails/entity/templates/entity.rb.tt +9 -0
  54. data/lib/generators/ecs_rails/install/install_generator.rb +29 -12
  55. data/lib/generators/ecs_rails/install/templates/application_component.rb.tt +3 -2
  56. data/lib/generators/ecs_rails/install/templates/application_entity.rb.tt +2 -2
  57. data/lib/generators/ecs_rails/install/templates/initializer.rb.tt +2 -2
  58. data/lib/generators/ecs_rails/install/templates/install_migration.rb.tt +38 -0
  59. data/lib/generators/ecs_rails/templates/catalogue_class.rb.tt +10 -0
  60. data/lib/generators/ecs_rails/upgrade/templates/catalogue_migration.rb.tt +18 -0
  61. data/lib/generators/ecs_rails/upgrade/templates/markers_migration.rb.tt +31 -0
  62. data/lib/generators/ecs_rails/upgrade/templates/relationships_migration.rb.tt +33 -0
  63. data/lib/generators/ecs_rails/upgrade/templates/slots_migration.rb.tt +23 -0
  64. data/lib/generators/ecs_rails/upgrade/upgrade_generator.rb +281 -0
  65. metadata +61 -4
  66. data/lib/generators/ecs_rails/install/templates/migration.rb.tt +0 -20
  67. data/lib/generators/ecs_rails/relationship/relationship_generator.rb +0 -99
  68. data/lib/generators/ecs_rails/relationship/templates/migration.rb.tt +0 -32
@@ -10,5 +10,15 @@ module EcsRails
10
10
  config.to_prepare do
11
11
  EcsRails.registry.clear! if EcsRails.registry.respond_to?(:clear!)
12
12
  end
13
+
14
+ # Inverse relationships (RFC-0015) are validated on first use because
15
+ # checking at class-load time is unsafe under autoloading. When the app
16
+ # eager-loads — production — every entity is present at boot, so check them
17
+ # all now and fail to start rather than at the first request.
18
+ config.after_initialize do |app|
19
+ next unless app.config.eager_load
20
+
21
+ EcsRails::Entity.descendants.reject(&:abstract_class?).each(&:validate_inverses!)
22
+ end
13
23
  end
14
24
  end
@@ -20,7 +20,9 @@ module EcsRails
20
20
  # nothing here stores a Class: entries are keyed by class *name*, and names are
21
21
  # resolved back to live constants via #constantize at read time.
22
22
  class Registry
23
- # One `component Foo` declaration on one entity class.
23
+ # One `component Foo` declaration on one entity class — under one slot
24
+ # (RFC-0014 / ADR-0015): the same component declared twice under different
25
+ # labels is two Declarations.
24
26
  #
25
27
  # A value object over *names*. #entity_class / #component_class resolve on
26
28
  # every call, so a Declaration handed out before a reload still resolves to
@@ -30,20 +32,50 @@ module EcsRails
30
32
  # @return [String] the declaring entity's class name
31
33
  # @!attribute [r] component_class_name
32
34
  # @return [String] the declared component's class name
35
+ # @!attribute [r] slot
36
+ # @return [String] the slot label; `""` is the default (singular) slot
33
37
  # @!attribute [r] options
34
- # @return [Hash] the frozen `only:`/`except:` delegation options
35
- attr_reader :entity_class_name, :component_class_name, :options
38
+ # @return [Hash] the frozen delegation options: `only:`/`except:` as
39
+ # symbol arrays, `prefix: false` for bare delegation (ADR-0016), and
40
+ # `delegate: false` for none (RFC-0014). `{}` means everything
41
+ # delegated, reader-prefixed.
42
+ # @!attribute [r] slot_options
43
+ # @return [Hash{Symbol => Object}] the frozen per-slot options the
44
+ # declaration passed through to the component (RFC-0014, e.g.
45
+ # `states: %w[...]`); see {EcsRails::Slots::Component#slot_options}
46
+ attr_reader :entity_class_name, :component_class_name, :slot, :options, :slot_options
36
47
 
37
48
  # @param entity_class_name [String] the declaring entity's class name
38
49
  # @param component_class_name [String] the declared component's class name
39
- # @param options [Hash] `only:`/`except:` delegation options
40
- def initialize(entity_class_name:, component_class_name:, options: {})
50
+ # @param slot [String] the slot label, `""` for the default slot
51
+ # @param options [Hash] `only:`/`except:`/`prefix:`/`delegate:` delegation options
52
+ # @param slot_options [Hash] per-slot options passed through to the component
53
+ def initialize(entity_class_name:, component_class_name:, slot: "", options: {}, slot_options: {})
41
54
  @entity_class_name = entity_class_name
42
55
  @component_class_name = component_class_name
56
+ @slot = slot.to_s.dup.freeze
43
57
  @options = options.dup.freeze
58
+ @slot_options = slot_options.dup.freeze
44
59
  freeze
45
60
  end
46
61
 
62
+ # The entity reader this declaration generates — `postal_address` for the
63
+ # default slot, `business_address` for slot `"business"`. Derived by
64
+ # {EcsRails::Slots::Component::ClassMethods#ecs_reader_name} so the
65
+ # component instance and the declaration cannot disagree.
66
+ #
67
+ # @return [Symbol]
68
+ def reader_name
69
+ component_class.ecs_reader_name(slot)
70
+ end
71
+
72
+ # @return [Symbol, nil] the slot as the `prefix:` keyword the DSL was given,
73
+ # or nil for the default slot — the form {EcsRails::Presence::Entity#has?}
74
+ # and {EcsRails::DSL#declaration_for} take
75
+ def prefix
76
+ slot.empty? ? nil : slot.to_sym
77
+ end
78
+
47
79
  # Resolved live, so a reloaded constant is picked up.
48
80
  #
49
81
  # @return [Class<EcsRails::Entity>] the declaring entity class
@@ -62,24 +94,28 @@ module EcsRails
62
94
 
63
95
  # @param other [Object]
64
96
  # @return [Boolean] true if both declare the same component on the same
65
- # entity with the same options
97
+ # entity, in the same slot, with the same options
66
98
  def ==(other)
67
99
  other.is_a?(Declaration) &&
68
100
  entity_class_name == other.entity_class_name &&
69
101
  component_class_name == other.component_class_name &&
70
- options == other.options
102
+ slot == other.slot &&
103
+ options == other.options &&
104
+ slot_options == other.slot_options
71
105
  end
72
106
  alias eql? ==
73
107
 
74
108
  # @return [Integer] a hash consistent with {#==}, so Declarations work as
75
109
  # Hash keys and in Sets
76
110
  def hash
77
- [self.class, entity_class_name, component_class_name, options].hash
111
+ [self.class, entity_class_name, component_class_name, slot, options, slot_options].hash
78
112
  end
79
113
 
80
- # @return [String] e.g. `#<...Declaration User => Email {}>`
114
+ # @return [String] e.g. `#<...Declaration User => Email {}>` or
115
+ # `#<...Declaration User => PostalAddress[business] {}>`
81
116
  def inspect
82
- "#<#{self.class} #{entity_class_name} => #{component_class_name} #{options.inspect}>"
117
+ label = slot.empty? ? "" : "[#{slot}]"
118
+ "#<#{self.class} #{entity_class_name} => #{component_class_name}#{label} #{options.inspect}>"
83
119
  end
84
120
  end
85
121
 
@@ -89,33 +125,41 @@ module EcsRails
89
125
 
90
126
  # Records one declaration. Returns the Declaration.
91
127
  #
92
- # Raises DuplicateComponent if this entity already declares this component —
93
- # per ADR-0005 a component appears at most once per entity, and RFC-0004
94
- # relies on the raise to catch a doubled `component` line at class-load time.
128
+ # Raises DuplicateComponent if this entity already declares this component
129
+ # in this slot — per ADR-0005 as generalised by ADR-0015, a component appears
130
+ # at most once per (entity, slot), and RFC-0004 relies on the raise to catch
131
+ # a doubled `component` line at class-load time. The same component under a
132
+ # different slot is a distinct declaration.
95
133
  #
96
134
  # @param entity_class [Class<EcsRails::Entity>] the declaring entity
97
135
  # @param component_class [Class<EcsRails::Component>] the declared component
98
- # @param options [Hash] `only:`/`except:` delegation options
136
+ # @param slot [String, Symbol] the slot label; `""` for the default slot
137
+ # @param options [Hash] `only:`/`except:`/`prefix:`/`delegate:` delegation options
138
+ # @param slot_options [Hash] per-slot options passed through to the component
99
139
  # @return [Declaration] the recorded declaration
100
140
  # @raise [EcsRails::DuplicateComponent] if this entity already declares this
101
- # component (ADR-0005)
141
+ # component in this slot (ADR-0005 / ADR-0015)
102
142
  # @raise [ArgumentError] if either class is anonymous, since the registry
103
143
  # keys entries by class name
104
- def register(entity_class:, component_class:, options: {})
144
+ def register(entity_class:, component_class:, slot: "", options: {}, slot_options: {})
105
145
  entity_name = name_for(entity_class)
106
146
  component_name = name_for(component_class)
147
+ slot = slot.to_s
107
148
 
108
149
  declarations = (@declarations[entity_name] ||= [])
109
150
 
110
- if declarations.any? { |declaration| declaration.component_class_name == component_name }
151
+ if declarations.any? { |d| d.component_class_name == component_name && d.slot == slot }
152
+ where = slot.empty? ? "" : " in slot #{slot.inspect} (prefix: :#{slot})"
111
153
  raise DuplicateComponent,
112
- "#{entity_name} already declares #{component_name}"
154
+ "#{entity_name} already declares #{component_name}#{where}"
113
155
  end
114
156
 
115
157
  declaration = Declaration.new(
116
158
  entity_class_name: entity_name,
117
159
  component_class_name: component_name,
118
- options: options
160
+ slot: slot,
161
+ options: options,
162
+ slot_options: slot_options
119
163
  )
120
164
  declarations << declaration
121
165
  declaration
@@ -140,7 +184,8 @@ module EcsRails
140
184
  declarations ? declarations.dup : []
141
185
  end
142
186
 
143
- # Every entity class declaring this component, as live class objects.
187
+ # Every entity class declaring this component, as live class objects. An
188
+ # entity declaring it under two slots appears once.
144
189
  #
145
190
  # @param component_class [Class<EcsRails::Component>] the component
146
191
  # @return [Array<Class<EcsRails::Entity>>] the entities composed from it
@@ -152,7 +197,7 @@ module EcsRails
152
197
  declarations.each do |declaration|
153
198
  entities << declaration.entity_class if declaration.component_class_name == component_name
154
199
  end
155
- end
200
+ end.uniq
156
201
  end
157
202
 
158
203
  # Resets the registry. Used between tests and by the Railtie's `to_prepare`.