cloud-templates 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +29 -0
  3. data/.simplecov +6 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +201 -0
  6. data/NOTICE +13 -0
  7. data/README.md +124 -0
  8. data/Rakefile +27 -0
  9. data/cloud-templates.gemspec +27 -0
  10. data/examples/lib/user_directory/artifacts/catalogized.rb +11 -0
  11. data/examples/lib/user_directory/artifacts/group.rb +37 -0
  12. data/examples/lib/user_directory/artifacts/ided.rb +11 -0
  13. data/examples/lib/user_directory/artifacts/organization.rb +17 -0
  14. data/examples/lib/user_directory/artifacts/pathed.rb +22 -0
  15. data/examples/lib/user_directory/artifacts/person.rb +20 -0
  16. data/examples/lib/user_directory/artifacts/team.rb +31 -0
  17. data/examples/lib/user_directory/artifacts/unit.rb +24 -0
  18. data/examples/lib/user_directory/artifacts/user.rb +29 -0
  19. data/examples/lib/user_directory/render/etc/artifact_view.rb +15 -0
  20. data/examples/lib/user_directory/render/etc/composite_view.rb +26 -0
  21. data/examples/lib/user_directory/render/etc/group_view.rb +23 -0
  22. data/examples/lib/user_directory/render/etc/person_view.rb +19 -0
  23. data/examples/lib/user_directory/render/etc/registry.rb +33 -0
  24. data/examples/lib/user_directory/render/etc/user_view.rb +35 -0
  25. data/examples/lib/user_directory/render/etc.rb +3 -0
  26. data/examples/lib/user_directory/render/ldap/artifact_view.rb +27 -0
  27. data/examples/lib/user_directory/render/ldap/composite_view.rb +32 -0
  28. data/examples/lib/user_directory/render/ldap/group_view.rb +28 -0
  29. data/examples/lib/user_directory/render/ldap/organization_view.rb +26 -0
  30. data/examples/lib/user_directory/render/ldap/person_view.rb +39 -0
  31. data/examples/lib/user_directory/render/ldap/registry.rb +16 -0
  32. data/examples/lib/user_directory/render/ldap/unit_view.rb +26 -0
  33. data/examples/lib/user_directory/render/ldap/user_view.rb +39 -0
  34. data/examples/lib/user_directory/render/ldap.rb +3 -0
  35. data/examples/lib/user_directory/utils.rb +18 -0
  36. data/examples/lib/user_directory.rb +23 -0
  37. data/examples/lib_path.rb +2 -0
  38. data/examples/spec/spec_helper.rb +1 -0
  39. data/examples/spec/user_directory_spec.rb +568 -0
  40. data/lib/aws/templates/artifact.rb +140 -0
  41. data/lib/aws/templates/composite.rb +178 -0
  42. data/lib/aws/templates/exceptions.rb +221 -0
  43. data/lib/aws/templates/render/registry.rb +60 -0
  44. data/lib/aws/templates/render/utils/base_type_views.rb +131 -0
  45. data/lib/aws/templates/render/view.rb +127 -0
  46. data/lib/aws/templates/render.rb +72 -0
  47. data/lib/aws/templates/utils/artifact_storage.rb +141 -0
  48. data/lib/aws/templates/utils/contextualized/filters.rb +437 -0
  49. data/lib/aws/templates/utils/contextualized/hash.rb +13 -0
  50. data/lib/aws/templates/utils/contextualized/nil.rb +13 -0
  51. data/lib/aws/templates/utils/contextualized/proc.rb +13 -0
  52. data/lib/aws/templates/utils/contextualized.rb +113 -0
  53. data/lib/aws/templates/utils/default.rb +185 -0
  54. data/lib/aws/templates/utils/dependency/enumerable.rb +13 -0
  55. data/lib/aws/templates/utils/dependency/object.rb +46 -0
  56. data/lib/aws/templates/utils/dependency.rb +121 -0
  57. data/lib/aws/templates/utils/dependent.rb +28 -0
  58. data/lib/aws/templates/utils/inheritable.rb +52 -0
  59. data/lib/aws/templates/utils/late_bound.rb +89 -0
  60. data/lib/aws/templates/utils/memoized.rb +27 -0
  61. data/lib/aws/templates/utils/named.rb +19 -0
  62. data/lib/aws/templates/utils/options.rb +279 -0
  63. data/lib/aws/templates/utils/parametrized/constraints.rb +423 -0
  64. data/lib/aws/templates/utils/parametrized/getters.rb +293 -0
  65. data/lib/aws/templates/utils/parametrized/guarded.rb +32 -0
  66. data/lib/aws/templates/utils/parametrized/mapper.rb +73 -0
  67. data/lib/aws/templates/utils/parametrized/nested.rb +72 -0
  68. data/lib/aws/templates/utils/parametrized/transformations.rb +660 -0
  69. data/lib/aws/templates/utils/parametrized.rb +240 -0
  70. data/lib/aws/templates/utils.rb +219 -0
  71. data/lib/aws/templates.rb +16 -0
  72. data/spec/aws/templates/artifact_spec.rb +161 -0
  73. data/spec/aws/templates/composite_spec.rb +361 -0
  74. data/spec/aws/templates/render/utils/base_type_views_spec.rb +104 -0
  75. data/spec/aws/templates/render_spec.rb +62 -0
  76. data/spec/aws/templates/utils/as_named_spec.rb +31 -0
  77. data/spec/aws/templates/utils/contextualized/filters_spec.rb +108 -0
  78. data/spec/aws/templates/utils/contextualized_spec.rb +115 -0
  79. data/spec/aws/templates/utils/late_bound_spec.rb +52 -0
  80. data/spec/aws/templates/utils/options_spec.rb +67 -0
  81. data/spec/aws/templates/utils/parametrized/constraint_spec.rb +175 -0
  82. data/spec/aws/templates/utils/parametrized/getters_spec.rb +139 -0
  83. data/spec/aws/templates/utils/parametrized/transformation_spec.rb +314 -0
  84. data/spec/aws/templates/utils/parametrized_spec.rb +241 -0
  85. data/spec/spec_helper.rb +6 -0
  86. metadata +244 -0
@@ -0,0 +1,140 @@
1
+ require 'aws/templates/utils/parametrized'
2
+ require 'aws/templates/utils/parametrized/getters'
3
+ require 'aws/templates/utils/parametrized/constraints'
4
+ require 'aws/templates/utils/default'
5
+ require 'aws/templates/utils/options'
6
+ require 'aws/templates/utils/dependent'
7
+
8
+ module Aws
9
+ module Templates
10
+ ##
11
+ # Basic artifact
12
+ #
13
+ # "Artifact" in the terminology of the framework is an entity which
14
+ # can represent any parametrizable object type from any problem domain
15
+ # possible. For instance, for CloudFormation, artifacts are resources
16
+ # in a CFN template, the template itself
17
+ #
18
+ # If your target domain is infrastructure orchestration, artifact is
19
+ # usually a single entity such as S3 volume, ASG, DynamoDB table, etc.
20
+ # However, the notion of artifact is not particularly fixed since the
21
+ # framework can be used for different purposes including documents
22
+ # generation and general templating tasks. Basically, artifact is a
23
+ # single parametrizable entity or a step in input hash
24
+ # transformations.
25
+ #
26
+ # Artifact classes work together with meta-programming mechanisms in
27
+ # Ruby language enabling artifacts inheritance in the natural way
28
+ # using simple Ruby classes. The feature is useful when you have a
29
+ # group of artifacts which share the same basic parameters but differ
30
+ # in details.
31
+ #
32
+ # The central part in the framework is played by processed hash. All
33
+ # mechanisms are based on simple ad-hoc merging rules which are
34
+ # described at merge method but basis can be described as following:
35
+ # each superclass initializer accepts children class processed hash
36
+ # as input hash and the hash is processed recursively through
37
+ # class hierarchy. Old values are newer removed by default so the
38
+ # whole process is no more than continuous hash expansion.
39
+ #
40
+ # class Piece < Artifact
41
+ # default { { output: options[:param] } }
42
+ # end
43
+ #
44
+ # class ConcretePiece < Piece
45
+ # default param: 'Came from child'
46
+ # end
47
+ #
48
+ # Piece.new(a: 1, b: 2).options.to_hash # => { a: 1, b: 2, output: nil }
49
+ # ConcretePiece.new(a: 1, b: 2).options.to_hash
50
+ # # => { a: 1, b: 2, output: 'Came from child', param: 'Came from child' }
51
+ #
52
+ # Also, as one of the peculiarities of the framework, you can override
53
+ # any auto-generated parameter with input hash if they have the same
54
+ # name/path.
55
+ class Artifact
56
+ include Utils::Default
57
+ include Utils::Parametrized
58
+ include Utils::Dependent
59
+
60
+ attr_accessor :options
61
+
62
+ def self.getter
63
+ as_is
64
+ end
65
+
66
+ def self.to_s
67
+ return super unless name.nil?
68
+ "<Subclass of (#{superclass}) with features #{features}>"
69
+ end
70
+
71
+ def self.features
72
+ @features ||= ancestors.take_while { |mod| mod != superclass }
73
+ end
74
+
75
+ # Create new child class with mixins
76
+ #
77
+ # The class method is useful when you want to mix-in some behavior adjustments
78
+ # without creating a new named class. For instance when you want to mix-in
79
+ # some defaults into class and instantiate a few instances out of that.
80
+ def self.featuring(*modules)
81
+ return self if modules.empty?
82
+
83
+ modules.inject(Class.new(self)) do |klass, mod|
84
+ klass.send(:include, mod)
85
+ end
86
+ end
87
+
88
+ ##
89
+ # Artifact's label
90
+ #
91
+ # All artifacts have labels assigned to them to simplify reverse
92
+ # look-up while linking dependencies. Interpretation of this field is purely
93
+ # application-specific.
94
+ default label: proc { object_id }
95
+
96
+ parameter :label, description: 'Artifact\'s label', constraint: not_nil
97
+
98
+ ##
99
+ # Artifact's root
100
+ #
101
+ # A root is an object which bundles artifacts into common rendering group helping to find
102
+ # disconnected pieces of dependency graph. If two artifacts have different roots they
103
+ # definitelly belong to different graphs.
104
+ default root: proc { object_id }
105
+
106
+ def root
107
+ options[:root]
108
+ end
109
+
110
+ ##
111
+ # Artifact's parent
112
+ #
113
+ # Artifacts can be organized into a hierarchy of composition. This field points back to the
114
+ # artifact's parent.
115
+ parameter :parent, description: 'Artifact parent'
116
+
117
+ ##
118
+ # Create a new artifact
119
+ #
120
+ # Artifact constructor. If you want to override it you might probably
121
+ # want to look into default first. All default values specified in the
122
+ # class definition and all its ancestors are processed and merged with
123
+ # options so it contains fully-processed hash.
124
+ #
125
+ # The algorithm of the processing is the following:
126
+ # * merge defaults hash with passed options; options take precedence
127
+ # * merge the hash with default calculations return results; calculations output
128
+ # takes preference
129
+ # * pass resulting hash to superclass initializer
130
+ # * merge resulting hash with options; options take preference
131
+ #
132
+ # * +params+ - input parameters hash to be used during following
133
+ # hash transformations and expansions.
134
+ def initialize(params)
135
+ @options = Utils::Options.new(params)
136
+ process_options(params)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,178 @@
1
+ require 'aws/templates/artifact'
2
+ require 'aws/templates/utils/artifact_storage'
3
+ require 'aws/templates/utils/contextualized'
4
+ require 'aws/templates/utils/dependency'
5
+
6
+ module Aws
7
+ module Templates
8
+ ##
9
+ # Composite
10
+ #
11
+ # Composite is an artifact which contain other artifacts and provide
12
+ # DSL syntax sugar to define it
13
+ #
14
+ # A composite can represent complex entities as CloudFormation
15
+ # stacks which can contain settings and infrastructure artifacts inside it.
16
+ # However, it's still a single entity which may be versioned as a
17
+ # whole and can represent the current state of deployed application.
18
+ #
19
+ # Composite is still an artifact and has all methods inherited so
20
+ # besides grouping different artifacts alltogether you can process
21
+ # input parameters too.
22
+ #
23
+ # Composite is a recursive structure as a result of being an artifact
24
+ # so you can construct arbitrary deep hierarchies of objects. Also it
25
+ # supports inheritance as artifact does. So every component defined in
26
+ # the parent class will be initialized properly in all children too.
27
+ class Composite < Artifact
28
+ include Aws::Templates::Utils::Contextualized
29
+
30
+ # propagate root to the components and set itself as the parent
31
+ contextualize filter(:add, :root) & (filter(:override) { { parent: self } })
32
+
33
+ ##
34
+ # Dictionary of artifacts and their labels the composite is
35
+ # consisting of
36
+ #
37
+ # Accessor returning dictionary of artifacts currently residing in
38
+ # composite instance with labels as keys
39
+ def artifacts
40
+ @artifacts ||= Utils::ArtifactStorage.new
41
+ end
42
+
43
+ ##
44
+ # Shortcut for accessing artifacts by their labels
45
+ #
46
+ # The method returns either stored artifact object or throws a
47
+ # descriptive exception.
48
+ def [](artifact_label)
49
+ unless artifacts.key?(artifact_label)
50
+ raise "There is no artifact #{artifact_label}" \
51
+ " in composite #{label}"
52
+ end
53
+
54
+ artifacts[artifact_label].as_a_dependency.to_self
55
+ end
56
+
57
+ def []=(artifact_label, artifact_object)
58
+ if artifacts.key?(artifact_label)
59
+ if artifacts[artifact_label] != artifact_object.object
60
+ raise "Artifact #{artifact_label} is already present " \
61
+ "in composite #{label}"
62
+ end
63
+ else
64
+ artifacts[artifact_label] = artifact_object.object
65
+ end
66
+
67
+ artifact_object.as_a_dependency.to_self
68
+ end
69
+
70
+ ##
71
+ # Artifacts definition block for DSL
72
+ #
73
+ # An element of the framework DSL. Allows you to define
74
+ # composite's artifacts declaratively with using standard language
75
+ # features.
76
+ def self.components(*args, &blk)
77
+ define_method(:create_components) do
78
+ super()
79
+ instance_exec(*args, &blk)
80
+ end
81
+
82
+ self
83
+ end
84
+
85
+ ##
86
+ # Add components into the composite's instance
87
+ #
88
+ # Analog of class-level "components" method to add components after
89
+ # artifact creation when using class-level definitions are not
90
+ # appropriate
91
+ def components(&blk)
92
+ instance_exec(&blk)
93
+ self
94
+ end
95
+
96
+ ##
97
+ # Syntax sugar to create composite classes on spot
98
+ #
99
+ # Create a new child class of the current class and executes a
100
+ # block in the context of the class object optionally passing a list
101
+ # of arguments to it.
102
+ def self.for(*args, &blk)
103
+ klass = Class.new(self)
104
+ klass.instance_eval(*args, &blk) unless blk.nil?
105
+ klass
106
+ end
107
+
108
+ ##
109
+ # Artifact definition constructor in DSL
110
+ #
111
+ # Defines a single artifact in composite's definition block. This
112
+ # method was designed to be used inside of composite block but you
113
+ # can use it elsewhere else applied on a class instance.
114
+ #
115
+ # * +type+ - artifact type (class)
116
+ # * +params+ - optional map of artifact options
117
+ # * +blk+ - a block which will be passed to artifacts constructor;
118
+ # applications may vary but particular one is adding
119
+ # artifacts into composite during instantiation
120
+ def artifact(type, params = nil, &blk)
121
+ artifact_object = create_artifact_object(type, params, &blk)
122
+ self[artifact_object.label] = artifact_object
123
+ artifact_object.as_a_dependency.to_self
124
+ end
125
+
126
+ ##
127
+ # Put labels on the artifact
128
+ #
129
+ # Put the artifact into the artifact storage under arbitrary aliases.
130
+ #
131
+ # * +artifact_object+ - artifact object to put
132
+ # * +labels+ - labels to assign to the artifact
133
+ def label_as(artifact_object, *labels)
134
+ labels.flatten.each do |artifact_label|
135
+ self[artifact_label] = artifact_object
136
+ end
137
+
138
+ artifact_object
139
+ end
140
+
141
+ ##
142
+ # Provisions parameters and initializes nested artifacts
143
+ def initialize(*params, &blk)
144
+ super(*params)
145
+ create_components
146
+ instance_exec(&blk) if blk
147
+ end
148
+
149
+ ##
150
+ # Find artifacts by criteria
151
+ #
152
+ # The method allows flexible introspection of the artifacts
153
+ # enclosed into the composite's storage. The method is just a proxy
154
+ # for the storage method with the same name
155
+ #
156
+ # * +search_params+ - map of search parameters:
157
+ # ** +recursive+ - if true, search will be performed recusrsively
158
+ # in nested composites
159
+ # ** +label+ - search for artifacts which have the label
160
+ # ** +parameters+ - search for artifacts which have specified
161
+ # parameters values; it's a multi-level map so
162
+ # you can check for nested values also
163
+ def search(search_params = {})
164
+ artifacts.search(search_params)
165
+ end
166
+
167
+ protected
168
+
169
+ def create_components; end
170
+
171
+ private
172
+
173
+ def create_artifact_object(type, params, &blk)
174
+ type.new(options.filter(&contextualize(params.to_filter)), &blk)
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,221 @@
1
+ module Aws
2
+ module Templates
3
+ ##
4
+ # Parameter definition exception
5
+ #
6
+ # Meta-programming exception related to Parametrized DSL
7
+ class ParametrizedDSLError < StandardError
8
+ end
9
+
10
+ ##
11
+ # Parameter already exists
12
+ #
13
+ # If you're trying to define a parameter in a parametrized artifact
14
+ # and this parameter either already defined for the class or defined
15
+ # in an ancestor.
16
+ class ParameterAlreadyExist < ParametrizedDSLError
17
+ # Parameter object of the conflicting parameter
18
+ attr_reader :parameter
19
+
20
+ def initialize(target_parameter)
21
+ @parameter = target_parameter
22
+ super(
23
+ "Parameter #{target_parameter.name} already in " \
24
+ "#{target_parameter.klass}."
25
+ )
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Invalid parameter specification hash
31
+ #
32
+ # If unknown option is passed in a parameter description block
33
+ class ParameterSpecificationIsInvalid < ParametrizedDSLError
34
+ # Parameter object faulty options were specified for
35
+ attr_reader :parameter
36
+
37
+ # Options unknown to Parametrized
38
+ attr_reader :options
39
+
40
+ def initialize(target_parameter, opts)
41
+ @parameter = target_parameter
42
+ @options = opts
43
+
44
+ super(
45
+ 'Unsupported options are in specification for ' \
46
+ "parameter #{target_parameter.name} in class " \
47
+ "#{target_parameter.klass} : #{opts}"
48
+ )
49
+ end
50
+ end
51
+
52
+ ##
53
+ # A regular method and a parameter have the same name in a class
54
+ #
55
+ # A parameter was specified with the same name as exsiting method
56
+ # in the class or in an ancestor of the class.
57
+ class ParameterMethodNameConflict < ParametrizedDSLError
58
+ # Method object of the method specified
59
+ attr_reader :method_object
60
+
61
+ def initialize(target_method)
62
+ @method_object = target_method
63
+
64
+ super(
65
+ "Parameter name #{target_method.name} clashes with a method name in " \
66
+ "#{target_method.owner.name}"
67
+ )
68
+ end
69
+ end
70
+
71
+ ##
72
+ # View was not found for the object
73
+ #
74
+ # View map was checked and there is no appropriate view class
75
+ # for the object class found in the registry.
76
+ class ViewNotFound < RuntimeError
77
+ # Instance of the object class render lookup was performed for
78
+ attr_reader :instance
79
+
80
+ def message
81
+ "Can't find any view for #{instance} of class #{instance.class}"
82
+ end
83
+
84
+ def initialize(target_instance)
85
+ super()
86
+ @instance = target_instance
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Parameter exception
92
+ #
93
+ # Happens during runtime if an error happens during parameter
94
+ # evaluation
95
+ class ParameterException < RuntimeError
96
+ # Parameter object
97
+ attr_reader :parameter
98
+
99
+ def message
100
+ cause.nil? ? super : "#{super} : #{cause.message}"
101
+ end
102
+
103
+ def initialize(target_parameter, custom_message)
104
+ @parameter = target_parameter
105
+ super(custom_message)
106
+ end
107
+ end
108
+
109
+ ##
110
+ # If something happens during parameter calculation
111
+ class NestedParameterException < ParameterException
112
+ def initialize(target_parameter)
113
+ super(
114
+ target_parameter,
115
+ 'Exception was thrown by nested parameter while calculating ' \
116
+ "#{target_parameter.name} (#{target_parameter.description})"
117
+ )
118
+ end
119
+ end
120
+
121
+ ##
122
+ # A value failed constraints
123
+ class ParameterValueInvalid < ParameterException
124
+ attr_reader :value
125
+ attr_reader :object
126
+
127
+ def initialize(target_parameter, target_object, target_value)
128
+ @value = target_value
129
+ @object = target_object
130
+ super(
131
+ target_parameter,
132
+ message_text(target_parameter, target_object, target_value)
133
+ )
134
+ end
135
+
136
+ private
137
+
138
+ def message_text(target_parameter, target_object, target_value)
139
+ message = "Value '(#{target_value.inspect})' violates constraints specified for " \
140
+ "#{target_parameter.name} (#{target_parameter.description}) in " \
141
+ "#{target_parameter.klass}"
142
+
143
+ unless target_object.class == target_parameter.klass
144
+ message += " and inherited by #{target_object.class}"
145
+ end
146
+
147
+ message
148
+ end
149
+ end
150
+
151
+ ##
152
+ # Getter is not specified
153
+ #
154
+ # Getter wasn't specified neither for the individual parameter nor for the mixing instance nor
155
+ # for its class.
156
+ class ParameterGetterIsNotDefined < ParameterException
157
+ def initialize(target_parameter)
158
+ super(
159
+ target_parameter,
160
+ "Can't find getter for #{target_parameter.name} (#{target_parameter.description}): " \
161
+ 'a getter should be attached either to the parameter or the instance ' \
162
+ 'or the instance class'
163
+ )
164
+ end
165
+ end
166
+
167
+ ##
168
+ # Options exception
169
+ #
170
+ # The parent of all exceptions Options method can throw
171
+ class OptionError < ArgumentError
172
+ end
173
+
174
+ ##
175
+ # Recursive value is expected
176
+ #
177
+ # Value passed doesn't not support "recursive" contract. See Utils.recursive?
178
+ class OptionShouldBeRecursive < OptionError
179
+ attr_reader :value
180
+
181
+ def initialize(value)
182
+ @value = value
183
+ super("Value #{value} is not a recursive data structure")
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Deleted branch detected
189
+ #
190
+ # While traversing Options layers for a value, deleted branch marker was discovered.
191
+ class OptionValueDeleted < OptionError
192
+ attr_reader :path
193
+
194
+ def initialize(path)
195
+ @path = path
196
+ super(
197
+ "Deleted value was detected while traversing path. The path left untraversed: #{path}"
198
+ )
199
+ end
200
+ end
201
+
202
+ ##
203
+ # Scalar is met while traversing Options path
204
+ #
205
+ # Path is not empty yet but we can't traverse deeper because the current value is a scalar
206
+ class OptionScalarOnTheWay < OptionError
207
+ attr_reader :value
208
+ attr_reader :path
209
+
210
+ def initialize(value, path)
211
+ @value = value
212
+ @path = path
213
+
214
+ super(
215
+ "Value #{value} is not a recursive data structure and we have still #{path} keys " \
216
+ 'to look-up'
217
+ )
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,60 @@
1
+ require 'aws/templates/exceptions'
2
+
3
+ module Aws
4
+ module Templates
5
+ module Render
6
+ ##
7
+ # View registry
8
+ #
9
+ # View registries encapsulate differerent ways of transforming
10
+ # your artifacts into a domain-specific output.
11
+ # In nutshell, they are registries of View classes which are able
12
+ # to lookup proper View for object instance passed to it.
13
+ class Registry
14
+ # View registry accessor
15
+ def registry
16
+ @registry ||= {}
17
+ end
18
+
19
+ ##
20
+ # Register pair artifact-view
21
+ #
22
+ # Invoked from inside of a View class at definition of the link
23
+ # between the view class and an artifact
24
+ # * +artifact+ - artifact class the view claims to be able to render
25
+ # * +render+ - view class
26
+ def register(artifact, view)
27
+ registry[artifact] = view
28
+ end
29
+
30
+ ##
31
+ # Can object be rendered
32
+ #
33
+ # Returns true if the object passed can be rendered by one of the views in the registry
34
+ def can_render?(instance)
35
+ instance.class.ancestors.any? { |ancestor| registry.include?(ancestor) }
36
+ end
37
+
38
+ ##
39
+ # Lookup a view for the artifact
40
+ #
41
+ # Searches registry for artifact's class and all its ancestors
42
+ # in the registry and returns the closest matching view
43
+ # * +instance+ - artifact instance to render
44
+ # * +params+ - assigned parameters; it can be arbitrary value;
45
+ # it is propagated to selected render
46
+ def view_for(instance, params = nil)
47
+ return instance if instance.respond_to?(:to_rendered)
48
+
49
+ mod = instance.class.ancestors.find do |ancestor|
50
+ registry.include?(ancestor)
51
+ end
52
+
53
+ raise ViewNotFound.new(instance) unless mod
54
+
55
+ registry[mod].new(instance, params)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end