onoma 0.0.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 (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +4 -0
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +41 -0
  8. data/Rakefile +93 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/config/locales/arb.yml +5711 -0
  12. data/config/locales/eng.yml +5704 -0
  13. data/config/locales/fra.yml +5869 -0
  14. data/config/locales/jpn.yml +5713 -0
  15. data/config/locales/por.yml +5702 -0
  16. data/config/locales/spa.yml +5708 -0
  17. data/db/migrate/20150813022423_add_data.xml +5740 -0
  18. data/db/migrate/20150813224145_fix_zea.xml +14 -0
  19. data/db/migrate/20150813224155_fix_hordeum.xml +175 -0
  20. data/db/migrate/20150814104907_rename_charts_of_accounts.xml +4 -0
  21. data/db/migrate/20150817202608_rename_abaci.xml +19 -0
  22. data/db/migrate/20150818132801_add_vinifera_product_exchangers.xml +4 -0
  23. data/db/migrate/20150818152001_add_wine_bottles.xml +13 -0
  24. data/db/migrate/20150818232501_add_vinifera_sale_exchangers.xml +4 -0
  25. data/db/migrate/20150820212931_remove_abaci.xml +19 -0
  26. data/db/migrate/20150821082601_add_grain_variants.xml +4 -0
  27. data/db/migrate/20150821230800_rename_invalid_document_natures.xml +12 -0
  28. data/db/migrate/20150825180658_add_production_usages.xml +5 -0
  29. data/db/migrate/20150826091319_add_leguminous_crop.xml +11 -0
  30. data/db/migrate/20150826092813_add_cannabis_varieties.xml +51 -0
  31. data/db/migrate/20150826100013_add_thicket_varieties.xml +9 -0
  32. data/db/migrate/20150826103353_add_eis_plant.xml +31 -0
  33. data/db/migrate/20150902081701_add_garden_varieties.xml +7 -0
  34. data/db/migrate/20150905094701_add_milklic_individual_production_exchangers.xml +4 -0
  35. data/db/migrate/20150914095928_add_meteorological_analysis_nature.xml +46 -0
  36. data/db/migrate/20150916110501_add_grain_analysis.xml +5 -0
  37. data/db/migrate/20150916151652_fix_product_nature_variant_units.xml +515 -0
  38. data/db/migrate/20150919123840_fix_plant_varieties.xml +36 -0
  39. data/db/migrate/20150919223801_change_walnut_nature.xml +6 -0
  40. data/db/migrate/20150920212201_add_fruit_harvest.xml +4 -0
  41. data/db/migrate/20150921104001_add_fuel_consumption_indicator.xml +7 -0
  42. data/db/migrate/20150921110601_add_crumbs_exchangers.xml +4 -0
  43. data/db/migrate/20150921175601_add_units_liter_per_hour.xml +4 -0
  44. data/db/migrate/20151001154701_add_missing_variants.xml +9 -0
  45. data/db/migrate/20151019090110_add_json_exchange_natures.xml +11 -0
  46. data/db/migrate/20151021172901_add_missing_indicator_on_animals.xml +12 -0
  47. data/db/migrate/20151027095601_add_missing_issue_natures.xml +6 -0
  48. data/db/migrate/20151102110723_add_daily_nitrogen_production_indicator.xml +4 -0
  49. data/db/migrate/20151107122501_add_cap_statements_exchangers.xml +4 -0
  50. data/db/migrate/20151111212501_add_missing_cap_productions.xml +15 -0
  51. data/db/migrate/20151117192943_change_procedure_nomenclatures.xml +159 -0
  52. data/db/migrate/20151122101101_add_missing_tropical_cap_varieties.xml +33 -0
  53. data/db/migrate/20151125163801_add_missing_varieties.xml +7 -0
  54. data/db/migrate/20151209000401_add_missing_crop_sets.xml +12 -0
  55. data/db/migrate/20151209011801_add_missing_varieties.xml +45 -0
  56. data/db/migrate/20151209094701_add_oleaginous_missing_varieties.xml +11 -0
  57. data/db/migrate/20151209103601_add_proteaginous_missing_varieties.xml +6 -0
  58. data/db/migrate/20151210150144_add_daucus_carota_varieties.xml +38 -0
  59. data/db/migrate/20151210163440_add_phaseolus_varieties.xml +11 -0
  60. data/db/migrate/20151210164511_add_allium_porrum_varieties.xml +13 -0
  61. data/db/migrate/20151210170103_add_allium_cepa_varieties.xml +6 -0
  62. data/db/migrate/20151211114316_add_beta_varieties.xml +4 -0
  63. data/db/migrate/20151211115500_add_brassica_varieties.xml +50 -0
  64. data/db/migrate/20151211124757_add_allium_schoenoprasum_varieties.xml +4 -0
  65. data/db/migrate/20151211132045_add_cucurbita_varieties.xml +13 -0
  66. data/db/migrate/20151211143806_add_spinacia_oleracea_varieties.xml +5 -0
  67. data/db/migrate/20151211151402_add_lactuca_varieties.xml +6 -0
  68. data/db/migrate/20151211153218_add_zea_mays_varieties.xml +46 -0
  69. data/db/migrate/20151214084817_add_hordeum_varieties.xml +5 -0
  70. data/db/migrate/20151214085342_add_pastinaca_sativa_varieties.xml +4 -0
  71. data/db/migrate/20151214085721_add_pisum_sativum_varieties.xml +8 -0
  72. data/db/migrate/20151214090420_add_solanum_tuberosum_varieties.xml +4 -0
  73. data/db/migrate/20151214091020_add_raphanus_varieties.xml +8 -0
  74. data/db/migrate/20151214092727_add_glycine_max_varieties.xml +4 -0
  75. data/db/migrate/20151215132825_add_abilities.xml +7 -0
  76. data/db/migrate/20151215133320_add_equipment_variants.xml +43 -0
  77. data/db/migrate/20151215214901_add_openwheatermap_identifiers.xml +5 -0
  78. data/db/migrate/20151216095351_add_ridger_equipment_variants.xml +4 -0
  79. data/db/migrate/20151216100708_add_lifter_equipment_variants.xml +4 -0
  80. data/db/migrate/20151216160914_add_raphanus_sativus_varieties.xml +4 -0
  81. data/db/migrate/20151216182551_add_more_units.xml +7 -0
  82. data/db/migrate/20151218081701_add_crops_issue_natures.xml +11 -0
  83. data/db/migrate/20151222162657_add_varieties.xml +18 -0
  84. data/db/migrate/20151222180021_remove_population.xml +402 -0
  85. data/db/reference.xml +5727 -0
  86. data/lib/onoma/database.rb +171 -0
  87. data/lib/onoma/item.rb +272 -0
  88. data/lib/onoma/migration/actions/base.rb +19 -0
  89. data/lib/onoma/migration/actions/item_change.rb +35 -0
  90. data/lib/onoma/migration/actions/item_creation.rb +39 -0
  91. data/lib/onoma/migration/actions/item_merging.rb +19 -0
  92. data/lib/onoma/migration/actions/item_removal.rb +18 -0
  93. data/lib/onoma/migration/actions/nomenclature_change.rb +26 -0
  94. data/lib/onoma/migration/actions/nomenclature_creation.rb +24 -0
  95. data/lib/onoma/migration/actions/nomenclature_removal.rb +24 -0
  96. data/lib/onoma/migration/actions/property_creation.rb +43 -0
  97. data/lib/onoma/migration/actions.rb +9 -0
  98. data/lib/onoma/migration/base.rb +45 -0
  99. data/lib/onoma/migration.rb +11 -0
  100. data/lib/onoma/migrator/reference.rb +77 -0
  101. data/lib/onoma/migrator/translation.rb +30 -0
  102. data/lib/onoma/migrator.rb +3 -0
  103. data/lib/onoma/nomenclature.rb +507 -0
  104. data/lib/onoma/property.rb +98 -0
  105. data/lib/onoma/reference.rb +17 -0
  106. data/lib/onoma/reflection.rb +34 -0
  107. data/lib/onoma/relation.rb +9 -0
  108. data/lib/onoma/version.rb +3 -0
  109. data/lib/onoma.rb +133 -0
  110. data/onoma.gemspec +28 -0
  111. metadata +237 -0
@@ -0,0 +1,9 @@
1
+ require 'onoma/migration/actions/base'
2
+ require 'onoma/migration/actions/item_change'
3
+ require 'onoma/migration/actions/item_creation'
4
+ require 'onoma/migration/actions/item_merging'
5
+ require 'onoma/migration/actions/item_removal'
6
+ require 'onoma/migration/actions/nomenclature_change'
7
+ require 'onoma/migration/actions/nomenclature_creation'
8
+ require 'onoma/migration/actions/nomenclature_removal'
9
+ require 'onoma/migration/actions/property_creation'
@@ -0,0 +1,45 @@
1
+ require 'active_support/inflector'
2
+ require 'active_support/core_ext/array'
3
+
4
+ module Onoma
5
+ module Migration
6
+ class Base
7
+ def self.parse(file)
8
+ f = File.open(file, 'rb')
9
+ document = Nokogiri::XML(f) do |config|
10
+ config.strict.nonet.noblanks.noent
11
+ end
12
+ f.close
13
+ root = document.root
14
+ number = file.basename.to_s.split('_').first.to_i
15
+ new(number, root['name'], root)
16
+ end
17
+
18
+ attr_reader :number, :name
19
+
20
+ def initialize(number, name, element = nil)
21
+ @number = number
22
+ @name = name
23
+ @actions = []
24
+ if element
25
+ element.children.each do |child|
26
+ next unless child.is_a? Nokogiri::XML::Element
27
+ @actions << "Onoma::Migration::Actions::#{child.name.underscore.classify}".constantize.new(child)
28
+ end
29
+ end
30
+ end
31
+
32
+ def label
33
+ "#{number} #{name}"
34
+ end
35
+
36
+ def each_action(&block)
37
+ @actions.each(&block)
38
+ end
39
+
40
+ def inspect
41
+ "#<#{self.class.name}:#{sprintf('%#x', object_id)} ##{number} #{name.inspect} (#{@actions.size} actions)>"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ class ::Hash
2
+
3
+ def simple_print
4
+ map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
5
+ end
6
+
7
+ end
8
+
9
+
10
+ require 'onoma/migration/actions'
11
+ require 'onoma/migration/base'
@@ -0,0 +1,77 @@
1
+ module Onoma
2
+ module Migrator
3
+ class Reference
4
+ def self.run(migration)
5
+ puts ""
6
+ puts "== #{migration.label}: migrating ".ljust(80, "=")
7
+ start = Time.now
8
+ ref = new
9
+ migration.each_action do |action|
10
+ puts "-- #{action.label}"
11
+ ref.send(action.action_name, action)
12
+ end
13
+ ref.version = migration.number
14
+ ref.write
15
+ duration = (Time.now - start).round(4)
16
+ puts "== #{migration.label}: migrated (#{duration}s)".ljust(80, "=")
17
+ # puts "Write DB in #{Onoma.reference_path.relative_path_from(Onoma.root)}".yellow
18
+ end
19
+
20
+ def initialize
21
+ if Onoma.reference_path.exist?
22
+ @set = Onoma::Database.load_file(Onoma.reference_path)
23
+ else
24
+ @set = Onoma::Database.new
25
+ end
26
+ end
27
+
28
+ def version
29
+ @set.version
30
+ end
31
+
32
+ def version=(number)
33
+ @set.version = number
34
+ end
35
+
36
+ def write
37
+ File.write(Onoma.reference_path, @set.to_xml)
38
+ end
39
+
40
+ def nomenclature_creation(action)
41
+ @set.add_nomenclature(action.name, action.options)
42
+ end
43
+
44
+ def nomenclature_change(action)
45
+ @set.change_nomenclature(action.nomenclature, action.changes)
46
+ end
47
+
48
+ def nomenclature_removal(action)
49
+ @set.remove_nomenclature(action.nomenclature)
50
+ end
51
+
52
+ def property_creation(action)
53
+ @set.add_property(action.nomenclature, action.name, action.type, action.options)
54
+ end
55
+
56
+ def property_change(action)
57
+ @set.add_property(action.nomenclature, action.name, action.changes)
58
+ end
59
+
60
+ def item_creation(action)
61
+ @set.add_item(action.nomenclature, action.name, action.options)
62
+ end
63
+
64
+ def item_change(action)
65
+ @set.change_item(action.nomenclature, action.name, action.changes)
66
+ end
67
+
68
+ def item_merging(action)
69
+ @set.merge_item(action.nomenclature, action.name, action.into)
70
+ end
71
+
72
+ def item_removal(action)
73
+ @set.remove_item(action.nomenclature, action.name)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,30 @@
1
+ module Onoma
2
+ module Migrator
3
+ class Translation
4
+ def self.run(migration)
5
+ puts "Migration #{migration.name}"
6
+ I18n.available_locales.each do |locale|
7
+ file = Onoma.root.join('config', 'locales', "#{locale}.yml")
8
+ hash = Clean::Support.yaml_to_hash(file)
9
+ migration.each_action do |action|
10
+ ref = hash[locale.to_sym][:nomenclatures]
11
+ ref[action.nomenclature.to_sym] ||= {}
12
+ ref[action.nomenclature.to_sym][:items] ||= {}
13
+ if action.is_a?(Onoma::Migration::Actions::ItemChange) && action.new_name?
14
+ ref[action.nomenclature.to_sym][:items][action.new_name.to_sym] ||= ref[action.nomenclature.to_sym][:items].delete(action.name.to_sym)
15
+ elsif action.is_a?(Onoma::Migration::Actions::ItemMerging)
16
+ ref[action.nomenclature.to_sym][:items][action.into.to_sym] ||= ref[action.nomenclature.to_sym][:items].delete(action.name.to_sym)
17
+ elsif action.is_a?(Onoma::Migration::Actions::NomenclatureChange) && action.changes[:name]
18
+ ref[action.changes[:name].to_sym] = ref.delete(action.nomenclature.to_sym)
19
+ elsif action.is_a?(Onoma::Migration::Actions::NomenclatureRemoval)
20
+ ref.delete(action.nomenclature.to_sym)
21
+ elsif !action.is_a?(Onoma::Migration::Actions::Base)
22
+ fail "Cannot handle: #{action.inspect}"
23
+ end
24
+ File.write(file, Clean::Support.hash_to_yaml(hash))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ require 'onoma/migration'
2
+ require 'onoma/migrator/reference'
3
+ require 'onoma/migrator/translation'
@@ -0,0 +1,507 @@
1
+ require 'bigdecimal'
2
+ require 'bigdecimal/util'
3
+
4
+ module Onoma
5
+ # This class represents a nomenclature
6
+ class Nomenclature
7
+ attr_reader :properties, :items, :name, :roots
8
+ attr_accessor :name, :notions, :translateable, :forest_right
9
+ alias_method :property_natures, :properties
10
+
11
+ # Instanciate a new nomenclature
12
+ def initialize(name, options = {})
13
+ @name = name.to_sym
14
+ @set = options.delete(:set)
15
+ @items = ActiveSupport::HashWithIndifferentAccess.new
16
+ @forest_right = 0
17
+ @roots = []
18
+ @properties = ActiveSupport::HashWithIndifferentAccess.new
19
+ @translateable = !options[:translateable].is_a?(FalseClass)
20
+ @notions = options[:notions] || []
21
+ end
22
+
23
+ class << self
24
+ def harvest(element, options = {})
25
+ notions = element.attr('notions').to_s.split(/\s*\,\s*/).map(&:to_sym)
26
+ options[:notions] = notions if notions.any?
27
+ options[:translateable] = !(element.attr('translateable').to_s == 'false')
28
+ name = element.attr('name').to_s
29
+ nomenclature = new(name, options)
30
+ for property in element.xpath('xmlns:properties/xmlns:property')
31
+ nomenclature.harvest_property(property)
32
+ end
33
+ for item in element.xpath('xmlns:items/xmlns:item')
34
+ nomenclature.harvest_item(item)
35
+ end
36
+ nomenclature.rebuild_tree!
37
+ nomenclature
38
+ end
39
+ end
40
+
41
+ def roots
42
+ @items.values.select(&:root?)
43
+ end
44
+
45
+ def name=(value)
46
+ @name = value.to_sym
47
+ end
48
+
49
+ def update_attributes(attributes = {})
50
+ attributes.each do |attribute, value|
51
+ send("#{attribute}=", value)
52
+ end
53
+ end
54
+
55
+ def references
56
+ unless @references
57
+ @references = []
58
+ properties.each do |_p, property|
59
+ if property.item_reference?
60
+ @references << Onoma::Reference.new(@set, property, @set.find(property.source), property.item_list? ? :array : :key)
61
+ end
62
+ end
63
+ end
64
+ @references
65
+ end
66
+
67
+ # Write CSV file with all items an there properties
68
+ def to_csv(file, _options = {})
69
+ properties = @properties.values
70
+ CSV.open(file, 'wb') do |csv|
71
+ csv << [:name] + properties.map do |p|
72
+ suffix = (p.decimal? ? ' D' : p.integer? ? ' I' : p.boolean? ? ' B' : p.item? ? " R(#{p.choices_nomenclature})" : '')
73
+ "#{p.name}#{suffix}"
74
+ end
75
+ @items.values.each do |i|
76
+ csv << [i.name] + properties.map { |p| i.attributes[p.name] }
77
+ end
78
+ end
79
+ end
80
+
81
+ def to_xml_attrs
82
+ attrs = { name: name, translateable: translateable.to_s }
83
+ attrs[:notions] = @notions.join(', ') if @notions.any?
84
+ attrs
85
+ end
86
+
87
+ # Build a nested set index on items
88
+ # Returns last right value
89
+ def rebuild_tree!
90
+ @forest_right = 0
91
+ roots.each(&:rebuild_tree!)
92
+ end
93
+
94
+ # Add an item to the nomenclature from an XML element
95
+ def harvest_item(element, attributes = {})
96
+ name = element.attr('name').to_s
97
+ parent = attributes[:parent] || (element.key?('parent') ? element['parent'] : nil)
98
+ attributes = element.attributes.each_with_object(HashWithIndifferentAccess.new) do |(k, v), h|
99
+ next if %w(name parent).include?(k)
100
+ h[k] = cast_property(k, v.to_s)
101
+ end
102
+ attributes[:parent] = parent if parent
103
+ item = add_item(name, attributes, rebuild: false)
104
+ item
105
+ end
106
+
107
+ # Add an property to the nomenclature from an XML element
108
+ def harvest_property(element)
109
+ name = element.attr('name').to_sym
110
+ type = element.attr('type').to_sym
111
+ options = {}
112
+ if element.has_attribute?('fallbacks')
113
+ options[:fallbacks] = element.attr('fallbacks').to_s.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
114
+ end
115
+ if element.has_attribute?('default')
116
+ options[:default] = element.attr('default').to_sym
117
+ end
118
+ options[:required] = !!(element.attr('required').to_s == 'true')
119
+ # options[:inherit] = !!(element.attr('inherit').to_s == 'true')
120
+ if type == :list
121
+ type = element.has_attribute?('nomenclature') ? :item_list : :choice_list
122
+ elsif type == :choice
123
+ type = :item if element.has_attribute?('nomenclature')
124
+ end
125
+ if type == :choice || type == :choice_list
126
+ if element.has_attribute?('choices')
127
+ options[:choices] = element.attr('choices').to_s.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
128
+ else
129
+ type = :string_list
130
+ end
131
+ elsif type == :item || type == :item_list
132
+ if element.has_attribute?('choices')
133
+ options[:choices] = element.attr('choices').to_s.strip.to_sym
134
+ elsif element.has_attribute?('nomenclature')
135
+ options[:choices] = element.attr('nomenclature').to_s.strip.to_sym
136
+ else
137
+ fail MissingChoices, "[#{@name}] Property #{name} must have nomenclature as choices"
138
+ end
139
+ end
140
+ unless Property::TYPES.include?(type)
141
+ fail ArgumentError, "Property #{name} type is unknown: #{type.inspect}"
142
+ end
143
+ add_property(name, type, options)
144
+ end
145
+
146
+ # Add an item to the nomenclature
147
+ def add_item(name, attributes = {}, options = {})
148
+ i = Item.new(self, name, attributes)
149
+ if @items[i.name]
150
+ fail "Item #{i.name} is already defined in nomenclature #{@name}"
151
+ end
152
+ @items[i.name] = i
153
+ @roots << i unless i.parent?
154
+ i.rebuild_tree! unless options[:rebuild].is_a?(FalseClass)
155
+ i
156
+ end
157
+
158
+ # Add an item to the nomenclature
159
+ def change_item(name, changes = {})
160
+ i = find!(name)
161
+ has_parent = changes.key?(:parent)
162
+ new_parent = changes[:parent]
163
+ new_name = changes[:name]
164
+ changes.each do |k, v|
165
+ next if [:parent, :name].include? k
166
+ i.set(k, v)
167
+ end
168
+ if has_parent
169
+ @roots << i if i.parent? && new_parent.nil?
170
+ @roots.delete(i) if i.root? && new_parent
171
+ i.parent = new_parent
172
+ end
173
+ i = rename_item(name, new_name) if new_name
174
+ i
175
+ end
176
+
177
+ def rename_item(name, new_name)
178
+ if @items[new_name]
179
+ fail "Item #{new_name} is already defined in nomenclature #{@name}. Use merging instead."
180
+ end
181
+ i = find!(name)
182
+ i.children.each do |child|
183
+ child.parent_name = new_name
184
+ end
185
+ cascade_item_renaming(name.to_sym, new_name.to_sym)
186
+ i = @items.delete(i.name)
187
+ i.name = new_name
188
+ @items[new_name] = i
189
+ i
190
+ end
191
+
192
+ # name and new_name are Symbol
193
+ def cascade_item_renaming(name, new_name)
194
+ @set.references.each do |reference|
195
+ next unless reference.foreign_nomenclature == self
196
+ p = reference.property
197
+ if p.list?
198
+ reference.nomenclature.find_each do |item|
199
+ v = item.property(p.name)
200
+ if v && v.include?(name)
201
+ l = v.map do |n|
202
+ n == name ? new_name : n
203
+ end
204
+ item.set(p.name, l)
205
+ end
206
+ end
207
+ else
208
+ reference.nomenclature.find_each do |item|
209
+ v = item.property(p.name)
210
+ item.set(p.name, new_name) if v == name
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ def merge_item(name, into)
217
+ i = find!(name)
218
+ dest = find!(into)
219
+ i.children.each do |child|
220
+ child.parent = dest
221
+ end
222
+ cascade_item_renaming(name.to_sym, into.to_sym)
223
+ @items.delete(name)
224
+ end
225
+
226
+ def remove_item(name)
227
+ i = find!(name)
228
+ @items.delete(name)
229
+ end
230
+
231
+ # Add an property to the nomenclature
232
+ def add_property(name, type, options = {})
233
+ p = Property.new(self, name, type, options)
234
+ if @properties[p.name]
235
+ fail "Property #{p.name} is already defined in nomenclature #{@name}"
236
+ end
237
+ @properties[p.name] = p
238
+ @references = nil
239
+ p
240
+ end
241
+
242
+ def sibling(name)
243
+ @set.find(name)
244
+ end
245
+
246
+ def check!
247
+ # Check properties
248
+ for property in @properties.values
249
+ if property.choices_nomenclature && !property.inline_choices? && !Onoma[property.choices_nomenclature.to_s]
250
+ fail InvalidProperty, "[#{name}] #{property.name} nomenclature property must refer to an existing nomenclature. Got #{property.choices_nomenclature.inspect}. Expecting: #{Onoma.names.inspect}"
251
+ end
252
+ next unless property.type == :choice && property.default
253
+ unless property.choices.include?(property.default)
254
+ fail InvalidProperty, "The default choice #{property.default.inspect} is invalid (in #{name}##{property.name}). Pick one from #{property.choices.sort.inspect}."
255
+ end
256
+ end
257
+
258
+ # Check items
259
+ for item in list
260
+ for property in @properties.values
261
+ choices = property.choices
262
+ if item.property(property.name) && property.type == :choice
263
+ # Cleans for parametric reference
264
+ name = item.property(property.name).to_s.split(/\(/).first.to_sym
265
+ unless choices.include?(name)
266
+ fail InvalidProperty, "The given choice #{name.inspect} is invalid (in #{self.name}##{item.name}). Pick one from #{choices.sort.inspect}."
267
+ end
268
+ elsif item.property(property.name) && property.type == :list && property.choices_nomenclature
269
+ for name in item.property(property.name) || []
270
+ # Cleans for parametric reference
271
+ name = name.to_s.split(/\(/).first.to_sym
272
+ unless choices.include?(name)
273
+ fail InvalidProperty, "The given choice #{name.inspect} is invalid (in #{self.name}##{item.name}). Pick one from #{choices.sort.inspect}."
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ # Default return
281
+ true
282
+ end
283
+
284
+ def inspect
285
+ "Onoma::#{name.to_s.classify}"
286
+ end
287
+
288
+ # Returns hash with items in tree: {a => nil, b => {c => nil}}
289
+ def tree
290
+ x = @roots.collect(&:tree).join
291
+ return x
292
+ i.attributes.merge(parent: i.parent_name, name: i.name, left: i.left, right: i.right, depth: i.depth).deep_stringify_keys
293
+ return x
294
+ @roots.map do |_i|
295
+ end
296
+ end
297
+
298
+ def translateable?
299
+ @translateable
300
+ end
301
+
302
+ # Return human name
303
+ def human_name(options = {})
304
+ "nomenclatures.#{name}.name".t(options.merge(default: ["labels.#{name}".to_sym, name.to_s.humanize]))
305
+ end
306
+ alias_method :humanize, :human_name
307
+
308
+ def new_boundaries(count = 2)
309
+ boundaries = []
310
+ count.times do
311
+ @forest_right += 1
312
+ boundaries << @forest_right
313
+ end
314
+ boundaries
315
+ end
316
+
317
+ # Returns the given item
318
+ def [](item_name)
319
+ @items[item_name]
320
+ end
321
+
322
+ def select(&block)
323
+ list.select(&block)
324
+ end
325
+
326
+ # List all item names. Can filter on a given item name and its children
327
+ def to_a(item_name = nil)
328
+ if item_name.present? && @items[item_name]
329
+ return @items[item_name].self_and_children.map(&:name)
330
+ else
331
+ return @items.keys.sort
332
+ end
333
+ end
334
+ alias_method :all, :to_a
335
+
336
+ def <=>(other)
337
+ name <=> other.name
338
+ end
339
+
340
+ def dependency_index
341
+ unless @dependency_index
342
+ @dependency_index = 0
343
+ properties.each do |_n, p|
344
+ if p.choices_nomenclature && !p.inline_choices?
345
+ @dependency_index += 1 + Onoma[p.choices_nomenclature].dependency_index
346
+ end
347
+ end
348
+ end
349
+ @dependency_index
350
+ end
351
+
352
+ # Returns a list for select as an array of pair (array)
353
+ def selection(item_name = nil)
354
+ items = (item_name ? @items[item_name].self_and_children : @items.values)
355
+ items.collect do |item|
356
+ [item.human_name, item.name.to_s]
357
+ end.sort do |a, b|
358
+ a.first.lower_ascii <=> b.first.lower_ascii
359
+ end
360
+ end
361
+
362
+ # Returns a list for select as an array of pair (hash)
363
+ def selection_hash(item_name = nil)
364
+ @items[item_name].self_and_children.map do |item|
365
+ { label: item.human_name, value: item.name }
366
+ end.sort { |a, b| a[:label].lower_ascii <=> b[:label].lower_ascii }
367
+ end
368
+
369
+ # Returns a list for select, without specified items
370
+ def select_without(already_imported)
371
+ selection = @items.values.collect do |item|
372
+ [item.human_name, item.name.to_s] unless already_imported[item.name.to_s]
373
+ end
374
+ selection.compact!
375
+ selection.sort! do |a, b|
376
+ a.first <=> b.first
377
+ end
378
+ selection
379
+ end
380
+
381
+ # Return first item name
382
+ def first(item_name = nil)
383
+ all(item_name).first
384
+ end
385
+
386
+ # Return the default item name
387
+ def default(item_name = nil)
388
+ first(item_name)
389
+ end
390
+
391
+ # Return the Item for the given name
392
+ def find(item_name)
393
+ @items[item_name]
394
+ end
395
+ alias_method :item, :find
396
+
397
+ def property(property_name)
398
+ @properties[property_name]
399
+ end
400
+
401
+ def find!(item_name)
402
+ unless i = @items[item_name]
403
+ fail "Cannot find item #{item_name} in #{name}"
404
+ end
405
+ i
406
+ end
407
+
408
+ # Returns list of items as an Array
409
+ def list
410
+ Onoma::Relation.new(@items.values)
411
+ end
412
+
413
+ # Iterates on items
414
+ def find_each(&block)
415
+ list.each(&block)
416
+ end
417
+
418
+ # List items with properties filtering
419
+ def where(properties)
420
+ list.select do |item|
421
+ valid = true
422
+ properties.each do |name, value|
423
+ item_value = item.property(name)
424
+ if value.is_a?(Array)
425
+ one_found = false
426
+ value.each do |val|
427
+ if val.is_a?(Onoma::Item)
428
+ one_found = true if item_value == val.name.to_sym
429
+ else
430
+ one_found = true if item_value == val
431
+ end
432
+ end
433
+ valid = false unless one_found
434
+ elsif value.is_a?(Onoma::Item)
435
+ valid = false unless item_value == value.name.to_sym
436
+ else
437
+ valid = false unless item_value == value
438
+ end
439
+ end
440
+ valid
441
+ end
442
+ end
443
+
444
+ def find_by(properties)
445
+ items = where(properties)
446
+ return nil unless items.any?
447
+ items.first
448
+ end
449
+
450
+ # Returns the best match on nomenclature properties
451
+ def best_match(property_name, searched_item)
452
+ items = []
453
+ begin
454
+ list.select do |item|
455
+ items << item if item.property(property_name) == searched_item.name
456
+ end
457
+ break if items.any?
458
+ searched_item = searched_item.parent
459
+ end while searched_item
460
+ items
461
+ end
462
+
463
+ # Returns property nature
464
+ def method_missing(method_name, *args)
465
+ @properties[method_name] || super
466
+ end
467
+
468
+ def cast_options(options)
469
+ return {} if options.nil?
470
+ hash = options.each_with_object({}) do |(k, v), h|
471
+ if properties[k]
472
+ h[k.to_sym] = cast_property(k, v.to_s)
473
+ else
474
+ h[k.to_sym] = v
475
+ end
476
+ end
477
+ end
478
+
479
+ def cast_property(name, value)
480
+ value = value.to_s
481
+ if property = properties[name]
482
+ if property.type == :choice || property.type == :item
483
+ if value =~ /\,/
484
+ fail InvalidProperty, 'A property nature of choice type cannot contain commas'
485
+ end
486
+ value = value.strip.to_sym
487
+ elsif property.list?
488
+ value = value.strip.split(/[[:space:]]*\,[[:space:]]*/).map(&:to_sym)
489
+ elsif property.type == :boolean
490
+ value = (value == 'true' ? true : value == 'false' ? false : nil)
491
+ elsif property.type == :decimal
492
+ value = value.to_d
493
+ elsif property.type == :integer
494
+ value = value.to_i
495
+ elsif property.type == :symbol
496
+ unless value =~ /\A\w+\z/
497
+ fail InvalidProperty, "A property '#{name}' must contains a symbol. /[a-z0-9_]/ accepted. No spaces. Got #{value.inspect}"
498
+ end
499
+ value = value.to_sym
500
+ end
501
+ elsif !%w(name parent aliases).include?(name.to_s)
502
+ fail ArgumentError, "Undefined property '#{name}' in #{@name}"
503
+ end
504
+ value
505
+ end
506
+ end
507
+ end