diff --git a/lib/goo.rb b/lib/goo.rb index 1d38a151..5d25e2e3 100644 --- a/lib/goo.rb +++ b/lib/goo.rb @@ -30,6 +30,10 @@ module Goo @@resource_options = Set.new([:persistent]).freeze + # Define the languages from which the properties values will be taken + # It choose the first language that match otherwise return all the values + @@main_languages = %w[en] + @@configure_flag = false @@sparql_backends = {} @@model_by_name = {} @@ -47,6 +51,19 @@ module Goo @@slice_loading_size = 500 + + def self.main_languages + @@main_languages + end + def self.main_languages=(lang) + @@main_languages = lang + end + + def self.language_includes(lang) + lang_str = lang.to_s + main_languages.index { |l| lang_str.downcase.eql?(l) || lang_str.upcase.eql?(l)} + end + def self.add_namespace(shortcut, namespace,default=false) if !(namespace.instance_of? RDF::Vocabulary) raise ArgumentError, "Namespace must be a RDF::Vocabulary object" diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index e82265d4..2b2d1d1d 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -42,9 +42,7 @@ def valid? self.class.attributes.each do |attr| inst_value = self.instance_variable_get("@#{attr}") attr_errors = Goo::Validators::Enforce.enforce(self,attr,inst_value) - unless attr_errors.nil? - validation_errors[attr] = attr_errors - end + validation_errors[attr] = attr_errors unless attr_errors.nil? end if !@persistent && validation_errors.length == 0 @@ -70,9 +68,7 @@ def valid? end def id=(new_id) - if !@id.nil? and @persistent - raise ArgumentError, "The id of a persistent object cannot be changed." - end + raise ArgumentError, "The id of a persistent object cannot be changed." if !@id.nil? and @persistent raise ArgumentError, "ID must be an RDF::URI" unless new_id.kind_of?(RDF::URI) @id = new_id end @@ -81,7 +77,7 @@ def id @id = generate_id if @id.nil? @id - end + end def persistent? return @persistent @@ -136,9 +132,7 @@ def unmmaped_to_array def delete(*args) if self.kind_of?(Goo::Base::Enum) - unless args[0] && args[0][:init_enum] - raise ArgumentError, "Enums cannot be deleted" - end + raise ArgumentError, "Enums cannot be deleted" unless args[0] && args[0][:init_enum] end raise ArgumentError, "This object is not persistent and cannot be deleted" if !@persistent @@ -146,9 +140,7 @@ def delete(*args) if !fully_loaded? missing = missing_load_attributes options_load = { models: [ self ], klass: self.class, :include => missing } - if self.class.collection_opts - options_load[:collection] = self.collection - end + options_load[:collection] = self.collection if self.class.collection_opts Goo::SPARQL::Queries.model_load(options_load) end @@ -164,9 +156,7 @@ def delete(*args) end @persistent = false @modified = true - if self.class.inmutable? && self.class.inm_instances - self.class.load_inmutable_instances - end + self.class.load_inmutable_instances if self.class.inmutable? && self.class.inm_instances return nil end @@ -174,15 +164,11 @@ def bring(*opts) opts.each do |k| if k.kind_of?(Hash) k.each do |k2,v| - if self.class.handler?(k2) - raise ArgumentError, "Unable to bring a method based attr #{k2}" - end + raise ArgumentError, "Unable to bring a method based attr #{k2}" if self.class.handler?(k2) self.instance_variable_set("@#{k2}",nil) end else - if self.class.handler?(k) - raise ArgumentError, "Unable to bring a method based attr #{k}" - end + raise ArgumentError, "Unable to bring a method based attr #{k}" if self.class.handler?(k) self.instance_variable_set("@#{k}",nil) end end @@ -197,9 +183,7 @@ def bring(*opts) def graph opts = self.class.collection_opts - if opts.nil? - return self.class.uri_type - end + return self.class.uri_type if opts.nil? col = collection if col.is_a?Array if col.length == 1 @@ -211,79 +195,14 @@ def graph return col ? col.id : nil end - def self.map_attributes(inst,equivalent_predicates=nil) - if (inst.kind_of?(Goo::Base::Resource) && inst.unmapped.nil?) || - (!inst.respond_to?(:unmapped) && inst[:unmapped].nil?) - raise ArgumentError, "Resource.map_attributes only works for :unmapped instances" - end - klass = inst.respond_to?(:klass) ? inst[:klass] : inst.class - unmapped = inst.respond_to?(:klass) ? inst[:unmapped] : inst.unmapped - list_attrs = klass.attributes(:list) - unmapped_string_keys = Hash.new - unmapped.each do |k,v| - unmapped_string_keys[k.to_s] = v - end - klass.attributes.each do |attr| - next if inst.class.collection?(attr) #collection is already there - next unless inst.respond_to?(attr) - attr_uri = klass.attribute_uri(attr,inst.collection).to_s - if unmapped_string_keys.include?(attr_uri.to_s) || - (equivalent_predicates && equivalent_predicates.include?(attr_uri)) - object = nil - if !unmapped_string_keys.include?(attr_uri) - equivalent_predicates[attr_uri].each do |eq_attr| - if object.nil? and !unmapped_string_keys[eq_attr].nil? - object = unmapped_string_keys[eq_attr].dup - else - if object.is_a?Array - if !unmapped_string_keys[eq_attr].nil? - object.concat(unmapped_string_keys[eq_attr]) - end - end - end - end - if object.nil? - inst.send("#{attr}=", - list_attrs.include?(attr) ? [] : nil, on_load: true) - next - end - else - object = unmapped_string_keys[attr_uri] - end - object = object.map { |o| o.is_a?(RDF::URI) ? o : o.object } - if klass.range(attr) - object = object.map { |o| - o.is_a?(RDF::URI) ? klass.range_object(attr,o) : o } - end - unless list_attrs.include?(attr) - object = object.first - end - if inst.respond_to?(:klass) - inst[attr] = object - else - inst.send("#{attr}=",object, on_load: true) - end - else - inst.send("#{attr}=", - list_attrs.include?(attr) ? [] : nil, on_load: true) - if inst.id.to_s == "http://purl.obolibrary.org/obo/IAO_0000415" - if attr == :definition - # binding.pry - end - end - end - end - end def collection opts = self.class.collection_opts if opts.instance_of?(Symbol) if self.class.attributes.include?(opts) value = self.send("#{opts}") - if value.nil? - raise ArgumentError, "Collection `#{opts}` is nil" - end + raise ArgumentError, "Collection `#{opts}` is nil" if value.nil? return value else raise ArgumentError, "Collection `#{opts}` is not an attribute" @@ -298,9 +217,7 @@ def add_aggregate(attribute,aggregate,value) def save(*opts) if self.kind_of?(Goo::Base::Enum) - unless opts[0] && opts[0][:init_enum] - raise ArgumentError, "Enums can only be created on initialization" - end + raise ArgumentError, "Enums can only be created on initialization" unless opts[0] && opts[0][:init_enum] end batch_file = nil if opts && opts.length > 0 @@ -310,9 +227,7 @@ def save(*opts) end if !batch_file - if not modified? - return self - end + return self if not modified? raise Goo::Base::NotValidException, "Object is not valid. Check errors." unless valid? end @@ -351,9 +266,7 @@ def save(*opts) @modified_attributes = Set.new @persistent = true - if self.class.inmutable? && self.class.inm_instances - self.class.load_inmutable_instances - end + self.class.load_inmutable_instances if self.class.inmutable? && self.class.inm_instances return self end @@ -391,9 +304,7 @@ def to_hash end end @unmapped.each do |attr,values| - unless all_attr_uris.include?(attr) - attr_hash[attr] = values.map { |v| v.to_s } - end + attr_hash[attr] = values.map { |v| v.to_s } unless all_attr_uris.include?(attr) end end attr_hash[:id] = @id @@ -413,13 +324,90 @@ def self.range_object(attr,id) return range_object end - def self.find(id, *options) - if !id.instance_of?(RDF::URI) && self.name_with == :id - id = RDF::URI.new(id) + + + def self.map_attributes(inst,equivalent_predicates=nil) + if (inst.kind_of?(Goo::Base::Resource) && inst.unmapped.nil?) || + (!inst.respond_to?(:unmapped) && inst[:unmapped].nil?) + raise ArgumentError, "Resource.map_attributes only works for :unmapped instances" end - unless id.instance_of?(RDF::URI) - id = id_from_unique_attribute(name_with(),id) + klass = inst.respond_to?(:klass) ? inst[:klass] : inst.class + unmapped = inst.respond_to?(:klass) ? inst[:unmapped] : inst.unmapped + list_attrs = klass.attributes(:list) + unmapped_string_keys = Hash.new + unmapped.each do |k,v| + unmapped_string_keys[k.to_s] = v + end + klass.attributes.each do |attr| + next if inst.class.collection?(attr) #collection is already there + next unless inst.respond_to?(attr) + attr_uri = klass.attribute_uri(attr,inst.collection).to_s + if unmapped_string_keys.include?(attr_uri.to_s) || + (equivalent_predicates && equivalent_predicates.include?(attr_uri)) + object = nil + if !unmapped_string_keys.include?(attr_uri) + equivalent_predicates[attr_uri].each do |eq_attr| + if object.nil? and !unmapped_string_keys[eq_attr].nil? + object = unmapped_string_keys[eq_attr].dup + else + if object.is_a?Array + object.concat(unmapped_string_keys[eq_attr]) if !unmapped_string_keys[eq_attr].nil? + end + end + end + if object.nil? + inst.send("#{attr}=", list_attrs.include?(attr) ? [] : nil, on_load: true) + next + end + else + object = unmapped_string_keys[attr_uri] + end + + lang_filter = Goo::SPARQL::Solution::LanguageFilter.new + + object = object.map do |o| + if o.is_a?(RDF::URI) + o + else + literal = o + index, lang_val = lang_filter.main_lang_filter inst.id.to_s, attr, literal, literal + lang_val.to_s if index.eql? :no_lang + end + end + + object = object.compact + + other_languages_values = lang_filter.other_languages_values + other_languages_values = other_languages_values[inst.id.to_s][attr] unless other_languages_values.empty? + unless other_languages_values.nil? + object = lang_filter.languages_values_to_set(other_languages_values, object) + end + + if klass.range(attr) + object = object.map { |o| + o.is_a?(RDF::URI) ? klass.range_object(attr,o) : o } + end + object = object.first unless list_attrs.include?(attr) + if inst.respond_to?(:klass) + inst[attr] = object + else + inst.send("#{attr}=",object, on_load: true) + end + else + inst.send("#{attr}=", + list_attrs.include?(attr) ? [] : nil, on_load: true) + if inst.id.to_s == "http://purl.obolibrary.org/obo/IAO_0000415" + if attr == :definition + # binding.pry + end + end + end + end + end + def self.find(id, *options) + id = RDF::URI.new(id) if !id.instance_of?(RDF::URI) && self.name_with == :id + id = id_from_unique_attribute(name_with(),id) unless id.instance_of?(RDF::URI) if self.inmutable? && self.inm_instances && self.inm_instances[id] w = Goo::Base::Where.new(self) w.instance_variable_set("@result", [self.inm_instances[id]]) diff --git a/lib/goo/sparql/loader.rb b/lib/goo/sparql/loader.rb index 548d61b6..21c18857 100644 --- a/lib/goo/sparql/loader.rb +++ b/lib/goo/sparql/loader.rb @@ -93,7 +93,8 @@ def self.model_load_sliced(*options) expand_equivalent_predicates(select, equivalent_predicates) solution_mapper = Goo::SPARQL::SolutionMapper.new aggregate_projections, bnode_extraction, embed_struct, incl_embed, klass_struct, models_by_id, - predicates_map, unmapped, variables, options + predicates_map, unmapped, variables, incl, options + solution_mapper.map_each_solutions(select) end @@ -148,10 +149,10 @@ def self.get_predicate_map(predicates) predicates_map = {} uniq_p.each do |p| i = 0 - key = ("var_" + p.last_part + i.to_s).to_sym + key = ("var_#{p.last_part}#{i.to_s}").to_sym while predicates_map.include?(key) i += 1 - key = ("var_" + p.last_part + i.to_s).to_sym + key = ("var_#{p.last_part}#{i.to_s}").to_sym break if i > 10 end predicates_map[key] = p diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb new file mode 100644 index 00000000..4fb12292 --- /dev/null +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -0,0 +1,89 @@ +module Goo + module SPARQL + module Solution + class LanguageFilter + + def initialize + @other_languages_values = {} + end + + def other_languages_values + @other_languages_values + end + + def main_lang_filter(id, attr, old_values, new_value) + index, value = lang_index old_values, new_value + save_other_lang_val(id, attr, index, new_value) unless index.eql? :no_lang + [index, value] + end + + def fill_models_with_other_languages(models_by_id, list_attributes) + @other_languages_values.each do |id, languages_values| + languages_values.each do |attr, index_values| + model_attribute_val = models_by_id[id].instance_variable_get("@#{attr.to_s}") + values = languages_values_to_set(index_values, model_attribute_val) + + if !values.nil? && list_attributes.include?(attr) + models_by_id[id].send("#{attr.to_s}=", values || [], on_load: true) + elsif !values.nil? + models_by_id[id].send("#{attr.to_s}=", values.first || nil, on_load: true) + end + end + end + end + + def languages_values_to_set(language_values, no_lang_values) + + values = nil + matched_lang, not_matched_lang = matched_languages(language_values, no_lang_values) + if !matched_lang.empty? + main_lang = Array(matched_lang[:'0']) + Array(matched_lang[:no_lang]) + if main_lang.empty? + secondary_languages = matched_lang.select { |key| key != :'0' && key != :no_lang }.sort.map { |x| x[1] } + values = secondary_languages.first + else + values = main_lang + end + elsif !not_matched_lang.empty? + values = not_matched_lang + end + values&.uniq + end + + private + + def lang_index(object, new_value) + lang = new_value.language + if lang.nil? + [:no_lang, object] + else + index = Goo.language_includes(lang) + index = index ? index.to_s.to_sym : :not_matched + [index, new_value] + end + end + + def save_other_lang_val(id, attr, index, value) + @other_languages_values[id] ||= {} + @other_languages_values[id][attr] ||= {} + @other_languages_values[id][attr][index] ||= [] + + unless @other_languages_values[id][attr][index].include?(value.to_s) + @other_languages_values[id][attr][index] += Array(value.to_s) + end + end + + + + def matched_languages(index_values, model_attribute_val) + not_matched_lang = index_values[:not_matched] + matched_lang = index_values.reject { |key| key == :not_matched } + unless model_attribute_val.nil? || Array(model_attribute_val).empty? + matched_lang[:no_lang] = Array(model_attribute_val) + end + [matched_lang, not_matched_lang] + end + end + end + end +end diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 4fbc7dba..1aa56b91 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -6,7 +6,7 @@ class SolutionMapper def initialize(aggregate_projections, bnode_extraction, embed_struct, incl_embed, klass_struct, models_by_id, - predicates_map, unmapped, variables, options) + predicates_map, unmapped, variables, incl, options) @aggregate_projections = aggregate_projections @bnode_extraction = bnode_extraction @@ -17,8 +17,10 @@ def initialize(aggregate_projections, bnode_extraction, embed_struct, @predicates_map = predicates_map @unmapped = unmapped @variables = variables + @incl = incl @options = options + end def map_each_solutions(select) @@ -27,13 +29,13 @@ def map_each_solutions(select) klass = @options[:klass] read_only = @options[:read_only] collection = @options[:collection] - incl = @options[:include] + found = Set.new objects_new = {} - var_set_hash = {} list_attributes = Set.new(klass.attributes(:list)) all_attributes = Set.new(klass.attributes(:all)) + @lang_filter = Goo::SPARQL::Solution::LanguageFilter.new if @options[:page] # for using prefixes before queries @@ -91,7 +93,7 @@ def map_each_solutions(select) object = sol[v] || nil #bnodes - if object.kind_of?(RDF::Node) && object.anonymous? && incl.include?(v) + if object.kind_of?(RDF::Node) && object.anonymous? && @incl.include?(v) initialize_object(id, klass, object, objects_new, v) next end @@ -106,11 +108,13 @@ def map_each_solutions(select) model_map_attributes_values(id, var_set_hash, @models_by_id, object, sol, v) end end + @lang_filter.fill_models_with_other_languages(@models_by_id, list_attributes) # for troubleshooting specific queries (write 3 of 3) # File.write(debug_file, "\n\n", mode: 'a') if select.to_s =~ /OFFSET \d+ LIMIT 2500$/ return @models_by_id if @bnode_extraction + model_set_collection_attributes(collection, klass, @models_by_id, objects_new) #remove from models_by_id elements that were not touched @@ -121,6 +125,7 @@ def map_each_solutions(select) #next level of embed attributes include_embed_attributes(collection, @incl_embed, klass, objects_new) if @incl_embed && !@incl_embed.empty? + #bnodes bnodes = objects_new.select { |id, obj| id.is_a?(RDF::Node) && id.anonymous? } include_bnodes(bnodes, collection, klass, @models_by_id) unless bnodes.empty? @@ -142,7 +147,6 @@ def model_set_unmapped(models_by_id, sol) end end - def create_struct(bnode_extraction, klass, models_by_id, sol, variables) list_attributes = Set.new(klass.attributes(:list)) struct = klass.range(bnode_extraction).new @@ -203,7 +207,7 @@ def include_embed_attributes(collection, incl_embed, klass, objects_new) }.values unless range_objs.empty? range_objs.uniq! - attr_range.where().models(range_objs).in(collection).include(*next_attrs).all + attr_range.where.models(range_objs).in(collection).include(*next_attrs).all end end end @@ -239,39 +243,32 @@ def model_set_collection_attributes(collection, klass, models_by_id, objects_new def get_collection_value(collection, klass) collection_value = nil if klass.collection_opts.instance_of?(Symbol) - if collection.is_a?(Array) && (collection.length == 1) - collection_value = collection.first - end - if collection.respond_to? :id - collection_value = collection - end + collection_value = collection.first if collection.is_a?(Array) && (collection.length == 1) + collection_value = collection if collection.respond_to? :id end collection_value end - def model_map_attributes_values(id, var_set_hash, models_by_id, object, sol, v) - if models_by_id[id].respond_to?(:klass) - models_by_id[id][v] = object if models_by_id[id][v].nil? + def model_map_attributes_values(id, object, sol, v) + #binding.pry if v.eql? :programs + if @models_by_id[id].respond_to?(:klass) + @models_by_id[id][v] = object if @models_by_id[id][v].nil? else - unless models_by_id[id].class.handler?(v) - unless object.nil? && !models_by_id[id].instance_variable_get("@#{v.to_s}").nil? - if v != :id - # if multiple language values are included for a given property, set the - # corresponding model attribute to the English language value - NCBO-1662 - if sol[v].kind_of?(RDF::Literal) - key = "#{v}#__#{id.to_s}" - models_by_id[id].send("#{v}=", object, on_load: true) unless var_set_hash[key] - lang = sol[v].language - var_set_hash[key] = true if lang == :EN || lang == :en - else - models_by_id[id].send("#{v}=", object, on_load: true) - end - end + model_attribute_val = @models_by_id[id].instance_variable_get("@#{v.to_s}") + if (!@models_by_id[id].class.handler?(v) || model_attribute_val.nil?) && v != :id + # if multiple language values are included for a given property, set the + # corresponding model attribute to the English language value - NCBO-1662 + if sol[v].kind_of?(RDF::Literal) + index, value = @lang_filter.main_lang_filter id, v, object, sol[v] + @models_by_id[id].send("#{v}=", value, on_load: true) if index.eql? :no_lang + elsif model_attribute_val.nil? || !object.nil? + @models_by_id[id].send("#{v}=", object, on_load: true) end end end end + def object_to_array(id, klass_struct, models_by_id, object, v) pre = klass_struct ? models_by_id[id][v] : models_by_id[id].instance_variable_get("@#{v}") @@ -328,11 +325,11 @@ def get_pre_val(id, models_by_id, object, v, read_only) if models_by_id[id] && ((models_by_id[id].respond_to?(:klass) && models_by_id[id]) || models_by_id[id].loaded_attributes.include?(v)) - if !read_only - pre_val = models_by_id[id].instance_variable_get("@#{v}") + pre_val = if !read_only + models_by_id[id].instance_variable_get("@#{v}") else - pre_val = models_by_id[id][v] - end + models_by_id[id][v] + end pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) end diff --git a/lib/goo/sparql/sparql.rb b/lib/goo/sparql/sparql.rb index dfd3d0a6..6fa1d582 100644 --- a/lib/goo/sparql/sparql.rb +++ b/lib/goo/sparql/sparql.rb @@ -1,6 +1,7 @@ require "sparql/client" require_relative "mixins/query_pattern" +require_relative "mixins/solution_lang_filter" require_relative "query_builder" require_relative "solutions_mapper" require_relative "client"