universe_compiler 0.2.11

Sign up to get free protection for your applications and to get access to all the features.
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