universe_compiler 0.2.11

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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +183 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +3 -0
  5. data/.travis.yml +4 -0
  6. data/CODE_OF_CONDUCT.md +49 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +318 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +10 -0
  12. data/bin/setup +8 -0
  13. data/lib/universe_compiler/entity/auto_named.rb +31 -0
  14. data/lib/universe_compiler/entity/conversion.rb +66 -0
  15. data/lib/universe_compiler/entity/field_binder.rb +36 -0
  16. data/lib/universe_compiler/entity/field_constraint_management.rb +95 -0
  17. data/lib/universe_compiler/entity/field_management.rb +57 -0
  18. data/lib/universe_compiler/entity/inheritance.rb +87 -0
  19. data/lib/universe_compiler/entity/inheritance_merge_policy.rb +19 -0
  20. data/lib/universe_compiler/entity/marshalling.rb +71 -0
  21. data/lib/universe_compiler/entity/overridden.rb +31 -0
  22. data/lib/universe_compiler/entity/persistence.rb +34 -0
  23. data/lib/universe_compiler/entity/reference.rb +77 -0
  24. data/lib/universe_compiler/entity/relations_management.rb +46 -0
  25. data/lib/universe_compiler/entity/type_management.rb +43 -0
  26. data/lib/universe_compiler/entity/validation.rb +92 -0
  27. data/lib/universe_compiler/entity.rb +64 -0
  28. data/lib/universe_compiler/error.rb +15 -0
  29. data/lib/universe_compiler/override.rb +24 -0
  30. data/lib/universe_compiler/package/bootstrap.rb +46 -0
  31. data/lib/universe_compiler/package.rb +17 -0
  32. data/lib/universe_compiler/persistence/basic_yaml_engine.rb +68 -0
  33. data/lib/universe_compiler/persistence/management.rb +30 -0
  34. data/lib/universe_compiler/universe/compile.rb +45 -0
  35. data/lib/universe_compiler/universe/duplication.rb +62 -0
  36. data/lib/universe_compiler/universe/entities.rb +52 -0
  37. data/lib/universe_compiler/universe/index.rb +40 -0
  38. data/lib/universe_compiler/universe/multiverse.rb +44 -0
  39. data/lib/universe_compiler/universe/persistence.rb +23 -0
  40. data/lib/universe_compiler/universe/query.rb +45 -0
  41. data/lib/universe_compiler/universe/validation.rb +30 -0
  42. data/lib/universe_compiler/universe.rb +38 -0
  43. data/lib/universe_compiler/utils/array_utils.rb +59 -0
  44. data/lib/universe_compiler/utils/basic_logger.rb +24 -0
  45. data/lib/universe_compiler/utils/deep_traverse.rb +61 -0
  46. data/lib/universe_compiler/utils/error_propagation.rb +20 -0
  47. data/lib/universe_compiler/utils/with_unique_name.rb +75 -0
  48. data/lib/universe_compiler/version.rb +3 -0
  49. data/lib/universe_compiler.rb +60 -0
  50. data/universe_compiler.gemspec +32 -0
  51. metadata +218 -0
@@ -0,0 +1,92 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ module Validation
5
+
6
+ def invalid_fields
7
+ invalid = {}
8
+ self.class.fields_constraints.each do |field_name, constraints|
9
+ constraints.each do |constraint_name, value|
10
+ case constraint_name
11
+ when :not_null
12
+ invalid_for_constraint invalid, field_name, constraint_name, value if fields[field_name].nil?
13
+ when :not_empty
14
+ invalid_for_constraint invalid, field_name, constraint_name, value if fields[field_name].nil? or fields[field_name].empty?
15
+ when :should_match
16
+ unless fields[field_name].nil?
17
+ invalid_for_constraint invalid, field_name, constraint_name, value unless fields[field_name].to_s.match value
18
+ end
19
+ when :class_name
20
+ unless fields[field_name].nil?
21
+ case value
22
+ when String
23
+ invalid_for_constraint invalid, field_name, constraint_name, value unless fields[field_name].class.name == value
24
+ else
25
+ invalid_for_constraint invalid, field_name, constraint_name, value unless fields[field_name].class == value
26
+ end
27
+ end
28
+ when :is_array
29
+ if fields[field_name].nil? or not fields[field_name].is_a? Array
30
+ invalid_for_constraint invalid, field_name, constraint_name, value
31
+ end
32
+ when :is_hash
33
+ if fields[field_name].nil? or not fields[field_name].is_a? Hash
34
+ invalid_for_constraint invalid, field_name, constraint_name, value
35
+ end
36
+ when :has_one
37
+ unless fields[field_name].nil?
38
+ if fields[field_name].respond_to? :type
39
+ invalid_for_constraint invalid, field_name, constraint_name, value unless fields[field_name].type == value
40
+ else
41
+ invalid_for_constraint invalid, field_name, constraint_name, value
42
+ end
43
+ end
44
+ when :has_many
45
+ if fields[field_name].nil? or not fields[field_name].is_a? Array
46
+ invalid_for_constraint invalid, field_name, constraint_name, value
47
+ else
48
+ fields[field_name].each do |related_object|
49
+ if related_object.respond_to? :type
50
+ invalid_for_constraint invalid, field_name, constraint_name, value unless related_object.type == value
51
+ else
52
+ invalid_for_constraint invalid, field_name, constraint_name, value
53
+ end
54
+ end
55
+ end
56
+ else
57
+ UniverseCompiler.logger.warn "Cannot handle unknown constraint '#{constraint_name}'! Skipping..."
58
+ end
59
+ end
60
+ end
61
+ invalid
62
+ end
63
+
64
+ def valid_for_fields_constraints?(raise_error: false)
65
+ return true if invalid_fields.empty?
66
+ invalid_fields.each do |name,value|
67
+ UniverseCompiler.logger.error "Invalid field '#{name}' not compliant with '#{value}'"
68
+ end
69
+ fields_labels = invalid_fields.keys.join ', '
70
+ false_or_raise "Invalid entity '#{to_composite_key}' for fields #{fields_labels} !", raise_error: raise_error
71
+ end
72
+
73
+ def valid?(raise_error: false)
74
+ return false unless valid_for_fields_constraints? raise_error: raise_error
75
+ return false unless valid_for_inheritance? raise_error: raise_error
76
+ if respond_to? :specifically_valid?
77
+ return false unless specifically_valid? raise_error: raise_error
78
+ end
79
+ true
80
+ end
81
+
82
+ private
83
+
84
+ def invalid_for_constraint(invalid_fields_definition, field_name, constraint_name, value)
85
+ invalid_fields_definition[field_name] ||= []
86
+ invalid_fields_definition[field_name] << { constraint_name => value}
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,64 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ class Base
5
+
6
+ include UniverseCompiler::Utils::ErrorPropagation
7
+ include UniverseCompiler::Utils::DeepTraverse
8
+ include UniverseCompiler::Entity::TypeManagement
9
+ include UniverseCompiler::Entity::FieldManagement
10
+ extend UniverseCompiler::Entity::AutoNamed
11
+ extend UniverseCompiler::Entity::FieldConstraintManagement
12
+ extend UniverseCompiler::Entity::RelationsManagement
13
+ extend UniverseCompiler::Entity::FieldBinder
14
+ include UniverseCompiler::Entity::Validation
15
+ include UniverseCompiler::Entity::Marshalling
16
+ include UniverseCompiler::Entity::Inheritance
17
+ include UniverseCompiler::Entity::Conversion
18
+ include UniverseCompiler::Entity::Overridden
19
+ include UniverseCompiler::Entity::Persistence
20
+
21
+ attr_reader :fields
22
+ attr_accessor :universe
23
+
24
+ field_accessor :name
25
+
26
+ def initialize(fields: {}, universe: nil)
27
+ @fields = fields
28
+ define_known_fields_accessors
29
+ self.universe = universe
30
+ self.fully_resolved = true
31
+ self.name = self.class.get_unique_name if self.class.auto_named_entity_type?
32
+ end
33
+
34
+ def [](key)
35
+ fields[key]
36
+ end
37
+
38
+ def []=(key, value)
39
+ fields[key] = value
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
@@ -0,0 +1,15 @@
1
+ module UniverseCompiler
2
+
3
+ class Error < StandardError
4
+
5
+ attr_accessor :original
6
+
7
+ def self.from(exception, message = exception.message)
8
+ wrapper = new message
9
+ wrapper.original = exception
10
+ wrapper
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,24 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ class Override < UniverseCompiler::Entity::Base
5
+
6
+ UNMERGEABLE_FIELDS = %i(name type overrides scenario extends)
7
+
8
+ entity_type :entity_override
9
+
10
+ field :overrides, :is_array
11
+ field_accessor :scenario
12
+
13
+ def apply_overrides
14
+ overrides.each do |override|
15
+ fields_to_be_merged = fields.reject { |key, _| UNMERGEABLE_FIELDS.include? key }
16
+ UniverseCompiler.logger.debug "Overriding '#{override.to_composite_key}' from overrides defined in '#{to_composite_key}'."
17
+ override.apply_override fields_to_be_merged, self
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,46 @@
1
+ module UniverseCompiler
2
+ module Package
3
+
4
+ module Bootstrap
5
+
6
+ DEFAULT_BOOTSTRAP_FILE = 'main.rb'.freeze
7
+
8
+ attr_reader :path
9
+
10
+ def path=(path)
11
+ @path = path if path_valid? path
12
+ end
13
+
14
+ def path_valid?(path = self.path)
15
+ return false unless path.is_a?(String) && File.readable?(path)
16
+ return true if File.file? path
17
+ File.exist? default_bootstrap_file(path)
18
+ end
19
+
20
+ def bootstrap_file
21
+ return nil unless path_valid?
22
+ if File.file? path
23
+ path
24
+ else
25
+ default_bootstrap_file
26
+ end
27
+ end
28
+
29
+ def load
30
+ require bootstrap_file
31
+ rescue ScriptError => e
32
+ msg = "Invalid package: '#{bootstrap_file}': #{e.message}"
33
+ UniverseCompiler.logger.error msg
34
+ raise UniverseCompiler::Error.from(e, msg)
35
+ end
36
+
37
+ private
38
+
39
+ def default_bootstrap_file(path = self.path)
40
+ File.join(path, DEFAULT_BOOTSTRAP_FILE)
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ module UniverseCompiler
2
+
3
+ module Package
4
+
5
+ class Base
6
+
7
+ include UniverseCompiler::Utils::WithUniqueName
8
+ include UniverseCompiler::Package::Bootstrap
9
+
10
+ def initialize(path = nil)
11
+ self.path = path
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,68 @@
1
+ require 'fileutils'
2
+
3
+ module UniverseCompiler
4
+ module Persistence
5
+
6
+ class BasicYamlEngine
7
+
8
+ attr_accessor :universe, :universe_uri
9
+
10
+ def initialize(universe, universe_uri)
11
+ @universe = universe
12
+ @universe_uri = universe_uri
13
+ end
14
+
15
+ def export_universe
16
+ FileUtils.mkdir_p universe_uri
17
+ universe.get_entities.each do |entity|
18
+ export_entity entity
19
+ end
20
+ end
21
+
22
+ def import_universe(recursive: false, stop_on_error: true, &block)
23
+ sub_search_path = recursive ? '**' : '*'
24
+ glob_pattern = File.join universe_uri, sub_search_path, '*.yaml'
25
+ Dir.glob(glob_pattern) do |file|
26
+ UniverseCompiler.logger.debug "Importing '#{file}' entity file."
27
+ new_entity = nil
28
+ begin
29
+ new_entity = import_entity file
30
+ rescue => e
31
+ raise e if stop_on_error
32
+ end
33
+ if new_entity.nil?
34
+ UniverseCompiler.logger.debug "#{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
35
+ UniverseCompiler.logger.warn "Could not load entity from file '#{file}' because '#{e.message}'"
36
+ else
37
+ block.call new_entity if block_given?
38
+ end
39
+ end
40
+ end
41
+
42
+ def export_entity(entity)
43
+ dir = File.join(universe_uri, entity.type.to_s)
44
+ FileUtils.mkdir_p dir
45
+ entity_file = entity.source_uri || File.join(dir, "#{entity.name}.yaml")
46
+ entity.save entity_file
47
+ UniverseCompiler.logger.info 'Exporting entity "%s" to file "%s"' % [entity.name, entity_file]
48
+ UniverseCompiler.logger.debug "Saved entity:\n%s" % [entity.to_yaml]
49
+ end
50
+
51
+ def import_entity(uri)
52
+ entity = UniverseCompiler::Entity::Persistence.load uri
53
+ universe.add entity
54
+ # Fix references link to universe
55
+ entity.traverse_fields do |leaf|
56
+ if leaf.is_a? UniverseCompiler::Entity::Reference
57
+ leaf.universe = universe
58
+ end
59
+ end
60
+ entity
61
+ end
62
+
63
+
64
+
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ module UniverseCompiler
2
+ module Persistence
3
+
4
+ module Management
5
+
6
+ DEFAULT_ENGINE_NAME = 'BasicYamlEngine'.freeze
7
+
8
+ def persistence_engine
9
+ if @persistence_engine.nil?
10
+ self.persistence_engine_name = DEFAULT_ENGINE_NAME
11
+ end
12
+ @persistence_engine
13
+ end
14
+
15
+ def persistence_engines
16
+ UniverseCompiler::Persistence.constants.map(&:to_s).grep(/Engine$/).map do |engine_name|
17
+ self.persistence_engine_name = engine_name
18
+ persistence_engine
19
+ end
20
+ end
21
+
22
+ def persistence_engine_name=(engine_name)
23
+ @persistence_engine = UniverseCompiler::Persistence.const_get engine_name
24
+ @persistence_engine_name = engine_name
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ module UniverseCompiler
2
+ module Universe
3
+
4
+ module Compile
5
+
6
+ include UniverseCompiler::Utils::DeepTraverse
7
+
8
+ def compile(scenario: nil)
9
+ valid? raise_error: true
10
+ UniverseCompiler.logger.info "Starting compilation of universe '#{name}'"
11
+ # Pass 1 - Universe duplication
12
+ target_universe = dup
13
+
14
+ # Pass 2 - Applying entities inheritance
15
+ target_universe.apply_entities_inheritance
16
+
17
+ # Apply scenario
18
+ target_universe.apply_entities_overrides scenario unless scenario.nil?
19
+
20
+ UniverseCompiler.logger.info "Completed compilation of universe '#{name}' into '#{target_universe.name}'"
21
+ target_universe
22
+ end
23
+
24
+ protected
25
+
26
+ def apply_entities_overrides(scenario)
27
+ UniverseCompiler.logger.debug "Starting override process for universe '#{name}'."
28
+ candidates = by_type.fetch(UniverseCompiler::Entity::Override.entity_type, []).select { |overrider| overrider.scenario == scenario }
29
+ UniverseCompiler.logger.warn "No override found for scenario '#{scenario}' in universe '#{name}'." if candidates.empty?
30
+ candidates.each(&:apply_overrides)
31
+ UniverseCompiler.logger.debug "Completed override process for universe '#{name}'."
32
+ self
33
+ end
34
+
35
+ def apply_entities_inheritance
36
+ UniverseCompiler.logger.debug "Starting inheritance process for universe '#{name}'."
37
+ entities.each(&:apply_inheritance)
38
+ UniverseCompiler.logger.debug "Completed inheritance process for universe '#{name}'."
39
+ self
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,62 @@
1
+ module UniverseCompiler
2
+ module Universe
3
+
4
+ module Duplication
5
+
6
+ def dup
7
+ UniverseCompiler.logger.debug "Starting '#{name}' universe duplication"
8
+ duplicated_universe = build_duplicated_universe_with_references
9
+ duplicated_universe.resolve_entities_reference
10
+ raise UniverseCompiler::Error, "Compilation error: Generated universe '#{duplicated_universe.name}' is invalid!" unless duplicated_universe.valid?
11
+ UniverseCompiler.logger.debug "Completed '#{name}' universe duplication"
12
+ duplicated_universe
13
+ end
14
+
15
+
16
+ protected
17
+
18
+ def resolve_entities_reference
19
+ entities.map!(&:resolve_fields_references!)
20
+ self
21
+ end
22
+
23
+ private
24
+
25
+ def duplicate_entities(target_universe = nil)
26
+ entities.map do |entity|
27
+ debug_msg = "Duplicating '#{entity.to_composite_key}' from universe '#{self.name}'"
28
+ debug_msg += " to universe '#{target_universe.name}'" unless target_universe.nil?
29
+ UniverseCompiler.logger.debug debug_msg
30
+ fields = Marshal::load(Marshal.dump entity).fields
31
+ entity_copy = entity.class.new fields: fields
32
+ entity_copy.universe = target_universe
33
+ deep_map entity_copy.fields do |leaf|
34
+ case leaf
35
+ when UniverseCompiler::Entity::Base
36
+ # All entities are supposed to be now references instead after the Marshall dump/load process
37
+ raise UniverseCompiler::Error, 'Should never happen!!'
38
+ when UniverseCompiler::Entity::Reference
39
+ leaf.universe = target_universe
40
+ else
41
+ raise UniverseCompiler::Error, 'Should never happen too!!' if leaf.respond_to? :universe
42
+ leaf
43
+ end
44
+ end
45
+ target_universe.add entity_copy unless target_universe.nil?
46
+ end
47
+ end
48
+
49
+ def build_duplicated_universe_with_references
50
+ target_name = '%s - COMPILED #%s' % [name, '%s']
51
+ duplicated_universe = self.class.new target_name
52
+ duplicated_universe.name = duplicated_universe.name % [duplicated_universe.object_id]
53
+ UniverseCompiler.logger.debug "Generating new universe '#{duplicated_universe.name}'."
54
+ duplicate_entities duplicated_universe
55
+ duplicated_universe
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
62
+
@@ -0,0 +1,52 @@
1
+ module UniverseCompiler
2
+ module Universe
3
+
4
+ module Entities
5
+
6
+ def empty?
7
+ entities.empty?
8
+ end
9
+
10
+ def add(entity)
11
+ raise UniverseCompiler::Error, 'An entity cannot be null !' if entity.nil?
12
+ raise UniverseCompiler::Error, 'An entity must have a name !' unless entity.respond_to? :name
13
+ raise UniverseCompiler::Error, 'An entity must have a name !' if entity.name.nil? or entity.name.empty?
14
+ unless UniverseCompiler::Entity::TypeManagement.valid_for_type? entity
15
+ entity.extend UniverseCompiler::Entity::TypeManagement
16
+ end
17
+ raise UniverseCompiler::Error, "An entity named '#{entity.name}' already exists with the type '#{entity.type}' !" if by_uniq_key.keys.include? [entity.type, entity.name]
18
+ raise UniverseCompiler::Error, 'Invalid type specified' if entity.type.nil?
19
+ raise UniverseCompiler::Error, 'Type cannot contain spaces' if entity.type =~ /\s/
20
+ # Normally here the entity is valid
21
+ # We add it to the universe and index it
22
+ entities << entity
23
+ if entity.respond_to? :'universe='
24
+ entity.universe = self
25
+ end
26
+ index entity
27
+ entity
28
+ end
29
+
30
+ def <<(entity)
31
+ add entity
32
+ self
33
+ end
34
+
35
+ def delete(entity)
36
+ entities.delete entity
37
+ reindex_all entities
38
+ end
39
+
40
+ def clear
41
+ entities.clear
42
+ clear_indices
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :entities
48
+
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,40 @@
1
+ module UniverseCompiler
2
+ module Universe
3
+
4
+ module Index
5
+
6
+ private
7
+
8
+ attr_reader :by_name, :by_type, :by_uniq_key
9
+
10
+ def setup_indices
11
+ @by_name = {}
12
+ @by_type = {}
13
+ @by_uniq_key = {}
14
+ end
15
+ alias_method :clear_indices, :setup_indices
16
+
17
+
18
+ def index(entity)
19
+ # Non unique indices
20
+ by_name[entity.name] ||= []
21
+ by_type[entity.type] ||= []
22
+ by_name[entity.name] << entity
23
+ by_type[entity.type] << entity
24
+ # Unique index
25
+ ref = entity.respond_to?(:to_composite_key) ? entity.to_composite_key : [entity.type, entity.name]
26
+ by_uniq_key[ref] = entity
27
+ end
28
+
29
+ def reindex_all(entities)
30
+ clear_indices
31
+ entities.each do |entity|
32
+ index entity
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
40
+
@@ -0,0 +1,44 @@
1
+ module UniverseCompiler
2
+ module Universe
3
+
4
+ module Multiverse
5
+
6
+ DEFAULT_UNIVERSE_NAME = 'Unnamed Universe'.freeze
7
+
8
+ def universes
9
+ @universes ||= {}
10
+ end
11
+
12
+ def register(universe)
13
+ raise UniverseCompiler::Error, "Universe '#{universe.name}' already exists in this continuum !" if universes.keys.include? universe.name
14
+ universes[universe.name] = universe
15
+ end
16
+
17
+ def get_unique_name(seed = DEFAULT_UNIVERSE_NAME)
18
+ max_index = 1
19
+ universes.keys.each do |universe_name|
20
+ universe_name.match /^#{seed}(?:(?: - #)(?<index>\d+))?$/ do |md|
21
+ index = md['index'] || '1'
22
+ index = index.to_i
23
+ max_index = index > max_index ? index : max_index
24
+ end
25
+ end
26
+ if max_index == 1
27
+ seed
28
+ else
29
+ UniverseCompiler.logger.debug "Universe #{seed} reached its #{max_index} iteration."
30
+ format_name(seed, max_index)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def format_name(name, index)
37
+ '%s - #%d' % [name, index]
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
44
+
@@ -0,0 +1,23 @@
1
+ module UniverseCompiler
2
+ module Universe
3
+
4
+ module Persistence
5
+
6
+ include UniverseCompiler::Persistence::Management
7
+
8
+ def export(uri)
9
+ engine = persistence_engine.new self, uri
10
+ engine.export_universe
11
+ end
12
+
13
+ def import(uri, force: false, stop_on_error: true, &block)
14
+ engine = persistence_engine.new self, uri
15
+ clear if force
16
+ engine.import_universe recursive: true, stop_on_error: stop_on_error, &block
17
+ resolve_entities_reference
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ module UniverseCompiler
2
+ module Universe
3
+
4
+ module Query
5
+
6
+ def basic_criteria
7
+ self.private_methods.grep /^by_/
8
+ end
9
+
10
+ def get_entity(type, name)
11
+ res = get_entities criterion: :by_uniq_key, value: [type, name]
12
+ res.empty? ? nil : res.first
13
+ end
14
+
15
+ def get_entities(criterion: nil, value: nil, &filter_block)
16
+ res = if criterion.nil? then
17
+ entities.clone
18
+ else
19
+ raise "Invalid criterion '#{criterion}' !" unless basic_criteria.include? criterion
20
+ if value.nil?
21
+ self.send(criterion).clone
22
+ else
23
+ self.send(criterion).clone[value]
24
+ end
25
+ end
26
+ res = case res
27
+ when NilClass
28
+ []
29
+ when Array
30
+ res
31
+ else
32
+ [res]
33
+ end
34
+ if block_given?
35
+ res.select! do |entity|
36
+ filter_block.call entity
37
+ end
38
+ end
39
+ res
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end