onoma 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +93 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/config/locales/arb.yml +5711 -0
- data/config/locales/eng.yml +5704 -0
- data/config/locales/fra.yml +5869 -0
- data/config/locales/jpn.yml +5713 -0
- data/config/locales/por.yml +5702 -0
- data/config/locales/spa.yml +5708 -0
- data/db/migrate/20150813022423_add_data.xml +5740 -0
- data/db/migrate/20150813224145_fix_zea.xml +14 -0
- data/db/migrate/20150813224155_fix_hordeum.xml +175 -0
- data/db/migrate/20150814104907_rename_charts_of_accounts.xml +4 -0
- data/db/migrate/20150817202608_rename_abaci.xml +19 -0
- data/db/migrate/20150818132801_add_vinifera_product_exchangers.xml +4 -0
- data/db/migrate/20150818152001_add_wine_bottles.xml +13 -0
- data/db/migrate/20150818232501_add_vinifera_sale_exchangers.xml +4 -0
- data/db/migrate/20150820212931_remove_abaci.xml +19 -0
- data/db/migrate/20150821082601_add_grain_variants.xml +4 -0
- data/db/migrate/20150821230800_rename_invalid_document_natures.xml +12 -0
- data/db/migrate/20150825180658_add_production_usages.xml +5 -0
- data/db/migrate/20150826091319_add_leguminous_crop.xml +11 -0
- data/db/migrate/20150826092813_add_cannabis_varieties.xml +51 -0
- data/db/migrate/20150826100013_add_thicket_varieties.xml +9 -0
- data/db/migrate/20150826103353_add_eis_plant.xml +31 -0
- data/db/migrate/20150902081701_add_garden_varieties.xml +7 -0
- data/db/migrate/20150905094701_add_milklic_individual_production_exchangers.xml +4 -0
- data/db/migrate/20150914095928_add_meteorological_analysis_nature.xml +46 -0
- data/db/migrate/20150916110501_add_grain_analysis.xml +5 -0
- data/db/migrate/20150916151652_fix_product_nature_variant_units.xml +515 -0
- data/db/migrate/20150919123840_fix_plant_varieties.xml +36 -0
- data/db/migrate/20150919223801_change_walnut_nature.xml +6 -0
- data/db/migrate/20150920212201_add_fruit_harvest.xml +4 -0
- data/db/migrate/20150921104001_add_fuel_consumption_indicator.xml +7 -0
- data/db/migrate/20150921110601_add_crumbs_exchangers.xml +4 -0
- data/db/migrate/20150921175601_add_units_liter_per_hour.xml +4 -0
- data/db/migrate/20151001154701_add_missing_variants.xml +9 -0
- data/db/migrate/20151019090110_add_json_exchange_natures.xml +11 -0
- data/db/migrate/20151021172901_add_missing_indicator_on_animals.xml +12 -0
- data/db/migrate/20151027095601_add_missing_issue_natures.xml +6 -0
- data/db/migrate/20151102110723_add_daily_nitrogen_production_indicator.xml +4 -0
- data/db/migrate/20151107122501_add_cap_statements_exchangers.xml +4 -0
- data/db/migrate/20151111212501_add_missing_cap_productions.xml +15 -0
- data/db/migrate/20151117192943_change_procedure_nomenclatures.xml +159 -0
- data/db/migrate/20151122101101_add_missing_tropical_cap_varieties.xml +33 -0
- data/db/migrate/20151125163801_add_missing_varieties.xml +7 -0
- data/db/migrate/20151209000401_add_missing_crop_sets.xml +12 -0
- data/db/migrate/20151209011801_add_missing_varieties.xml +45 -0
- data/db/migrate/20151209094701_add_oleaginous_missing_varieties.xml +11 -0
- data/db/migrate/20151209103601_add_proteaginous_missing_varieties.xml +6 -0
- data/db/migrate/20151210150144_add_daucus_carota_varieties.xml +38 -0
- data/db/migrate/20151210163440_add_phaseolus_varieties.xml +11 -0
- data/db/migrate/20151210164511_add_allium_porrum_varieties.xml +13 -0
- data/db/migrate/20151210170103_add_allium_cepa_varieties.xml +6 -0
- data/db/migrate/20151211114316_add_beta_varieties.xml +4 -0
- data/db/migrate/20151211115500_add_brassica_varieties.xml +50 -0
- data/db/migrate/20151211124757_add_allium_schoenoprasum_varieties.xml +4 -0
- data/db/migrate/20151211132045_add_cucurbita_varieties.xml +13 -0
- data/db/migrate/20151211143806_add_spinacia_oleracea_varieties.xml +5 -0
- data/db/migrate/20151211151402_add_lactuca_varieties.xml +6 -0
- data/db/migrate/20151211153218_add_zea_mays_varieties.xml +46 -0
- data/db/migrate/20151214084817_add_hordeum_varieties.xml +5 -0
- data/db/migrate/20151214085342_add_pastinaca_sativa_varieties.xml +4 -0
- data/db/migrate/20151214085721_add_pisum_sativum_varieties.xml +8 -0
- data/db/migrate/20151214090420_add_solanum_tuberosum_varieties.xml +4 -0
- data/db/migrate/20151214091020_add_raphanus_varieties.xml +8 -0
- data/db/migrate/20151214092727_add_glycine_max_varieties.xml +4 -0
- data/db/migrate/20151215132825_add_abilities.xml +7 -0
- data/db/migrate/20151215133320_add_equipment_variants.xml +43 -0
- data/db/migrate/20151215214901_add_openwheatermap_identifiers.xml +5 -0
- data/db/migrate/20151216095351_add_ridger_equipment_variants.xml +4 -0
- data/db/migrate/20151216100708_add_lifter_equipment_variants.xml +4 -0
- data/db/migrate/20151216160914_add_raphanus_sativus_varieties.xml +4 -0
- data/db/migrate/20151216182551_add_more_units.xml +7 -0
- data/db/migrate/20151218081701_add_crops_issue_natures.xml +11 -0
- data/db/migrate/20151222162657_add_varieties.xml +18 -0
- data/db/migrate/20151222180021_remove_population.xml +402 -0
- data/db/reference.xml +5727 -0
- data/lib/onoma/database.rb +171 -0
- data/lib/onoma/item.rb +272 -0
- data/lib/onoma/migration/actions/base.rb +19 -0
- data/lib/onoma/migration/actions/item_change.rb +35 -0
- data/lib/onoma/migration/actions/item_creation.rb +39 -0
- data/lib/onoma/migration/actions/item_merging.rb +19 -0
- data/lib/onoma/migration/actions/item_removal.rb +18 -0
- data/lib/onoma/migration/actions/nomenclature_change.rb +26 -0
- data/lib/onoma/migration/actions/nomenclature_creation.rb +24 -0
- data/lib/onoma/migration/actions/nomenclature_removal.rb +24 -0
- data/lib/onoma/migration/actions/property_creation.rb +43 -0
- data/lib/onoma/migration/actions.rb +9 -0
- data/lib/onoma/migration/base.rb +45 -0
- data/lib/onoma/migration.rb +11 -0
- data/lib/onoma/migrator/reference.rb +77 -0
- data/lib/onoma/migrator/translation.rb +30 -0
- data/lib/onoma/migrator.rb +3 -0
- data/lib/onoma/nomenclature.rb +507 -0
- data/lib/onoma/property.rb +98 -0
- data/lib/onoma/reference.rb +17 -0
- data/lib/onoma/reflection.rb +34 -0
- data/lib/onoma/relation.rb +9 -0
- data/lib/onoma/version.rb +3 -0
- data/lib/onoma.rb +133 -0
- data/onoma.gemspec +28 -0
- 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,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,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
|