From f20479fc49c694f06ca2ca91d388d9ba9875bc2d Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Mon, 27 Feb 2023 02:46:13 +0100 Subject: [PATCH 001/110] add tests for the new dsl to write property data types --- test/test_dsl_settings.rb | 54 +++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/test/test_dsl_settings.rb b/test/test_dsl_settings.rb index c444e829..a4de17e2 100644 --- a/test/test_dsl_settings.rb +++ b/test/test_dsl_settings.rb @@ -2,6 +2,22 @@ GooTest.configure_goo +class NewPersonModel < Goo::Base::Resource + model :person_model_new, name_with: :name + attribute :name, type: :string, enforce: [ :existence, :unique] + attribute :multiple_values, type: [:list, :integer], enforce: [ :existence, :min_3, :max_5 ] + attribute :one_number, type: :integer,enforce: [ :existence ] #by default not a list + attribute :birth_date, type: :date_time, enforce: [ :existence ] + + attribute :created, type: DateTime , + default: lambda { |record| DateTime.now }, + namespace: :omv + + attribute :friends, type: NewPersonModel , enforce: [ :existence] + attribute :status, type: :status, enforce: [ :existence], + default: lambda { |record| StatusModel.find("single") } +end + class StatusModel < Goo::Base::Resource model :status_model, name_with: :name attribute :description, enforce: [ :existence, :unique] @@ -37,8 +53,25 @@ def initialize(*args) super(*args) end + def test_data_type_dsl + _test_attributes_enforce NewPersonModel + end + def test_attributes_set_get + _test_attributes_enforce PersonModel + end + + def test_default_value + #default is on save ... returns` person = PersonModel.new + assert_equal nil, person.created + end + + + private + def _test_attributes_enforce(model) + person = model.new + model_key_name = model.model_name assert(person.respond_to? :id) assert(person.kind_of? Goo::Base::Resource) assert !person.valid? @@ -67,7 +100,7 @@ def test_attributes_set_get assert !person.valid? assert !person.errors[:birth_date] - person.birth_date = "X" + person.birth_date = "X" assert !person.valid? assert person.errors[:birth_date][:date_time] @@ -103,17 +136,17 @@ def test_attributes_set_get person.multiple_values << 99 end - friends = [PersonModel.new , PersonModel.new] + friends = [model.new , model.new] person.friends = friends assert !person.valid? assert person.errors[:friends][:no_list] - person.friends = PersonModel.new + person.friends = model.new assert !person.valid? - assert person.errors[:friends][:person_model] + assert person.errors[:friends][model_key_name] person.friends = "some one" assert !person.valid? - assert person.errors[:friends][:person_model] - person.friends = PersonModel.new + assert person.errors[:friends][model_key_name] + person.friends = model.new person.one_number = 99 assert !person.valid? @@ -127,7 +160,7 @@ def test_attributes_set_get assert !person.valid? assert person.errors[:one_number][:no_list] - person.one_number = 99 + person.one_number = 99 assert_equal(99, person.one_number) assert !person.valid? assert !person.errors[:one_number] @@ -138,11 +171,4 @@ def test_attributes_set_get #there are assigned objects that are not saved assert !person.valid? end - - def test_default_value - #default is on save ... returns` - person = PersonModel.new - assert_equal nil, person.created - end - end From b6cbb1ab185a96ec961bdb08ee1f36665e2d0518 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Mon, 27 Feb 2023 02:46:51 +0100 Subject: [PATCH 002/110] append the property :type values to the :enforce array --- lib/goo/base/settings/settings.rb | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/goo/base/settings/settings.rb b/lib/goo/base/settings/settings.rb index ce3e9a21..e1937396 100644 --- a/lib/goo/base/settings/settings.rb +++ b/lib/goo/base/settings/settings.rb @@ -185,9 +185,12 @@ def attribute(*args) attr_name = attr_name.to_sym options = options.pop options = {} if options.nil? - if options[:enforce].nil? or !options[:enforce].include?(:list) - options[:enforce] = options[:enforce] ? (options[:enforce] << :no_list) : [:no_list] - end + + options[:enforce] ||= [] + + set_data_type(options) + set_no_list_by_default(options) + @model_settings[:attributes][attr_name] = options shape_attribute(attr_name) namespace = attribute_namespace(attr_name) @@ -372,6 +375,20 @@ def read_only(attributes) instance end + private + + def set_no_list_by_default(options) + if options[:enforce].nil? or !options[:enforce].include?(:list) + options[:enforce] = options[:enforce] ? (options[:enforce] << :no_list) : [:no_list] + end + end + def set_data_type(options) + if options[:type] + options[:enforce] += Array(options[:type]) + options[:enforce].uniq! + options.delete :type + end + end end end end From 209ea16d3d64e6183b77e93b0d09958300d22c45 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Mon, 27 Feb 2023 10:50:14 +0100 Subject: [PATCH 003/110] update solution mapper to support multilingual --- lib/goo/sparql/solutions_mapper.rb | 33 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 77b20ae0..0544c2dd 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -72,13 +72,12 @@ def map_each_solutions(select) next end - # if multiple language values are included for a given property, set the - # corresponding model attribute to the English language value - NCBO-1662 - language, object = get_object_language(id, object, predicate) - object, objects_new = get_value_object(id, objects_new, object, list_attributes, predicate) - add_object_to_model(id, object, predicate, language) + lang = object_language(object) + + objects, objects_new = get_value_object(id, objects_new, object, list_attributes, predicate) + add_object_to_model(id, objects, predicate, lang, :EN) end - @lang_filter.fill_models_with_other_languages(@models_by_id, list_attributes) + # @lang_filter.fill_models_with_other_languages(@models_by_id, list_attributes) init_unloaded_attributes(found, list_attributes) return @models_by_id if @bnode_extraction @@ -175,17 +174,25 @@ def get_value_object(id, objects_new, object, list_attributes, predicate) [object, objects_new] end - def add_object_to_model(id, object, predicate, lang) + def object_language(new_value) + new_value.language || :no_lang if new_value.is_a?(RDF::Literal) + end + + def add_object_to_model(id, object, predicate, language, requested_lang = nil) if @models_by_id[id].respond_to?(:klass) @models_by_id[id][predicate] = object unless object.nil? && !@models_by_id[id][predicate].nil? elsif !@models_by_id[id].class.handler?(predicate) && !(object.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && predicate != :id - if (lang&.eql?(:no_lang)) || !lang - @models_by_id[id].send("#{predicate}=", object, on_load: true) - end - + if language.nil? + @models_by_id[id].send("#{predicate}=", object, on_load: true) + else + if language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil? + @models_by_id[id].send("#{predicate}=", object, on_load: true) + end + end + end end @@ -215,6 +222,7 @@ def preloaded_or_new_struct(object, objects_new, pre_val, predicate) def preloaded_value(id, predicate) if !@read_only @models_by_id[id].instance_variable_get("@#{predicate}") + else @models_by_id[id][predicate] end @@ -457,5 +465,4 @@ def add_aggregations_to_model(sol) end end end -end - +end \ No newline at end of file From 207a4835ed8ee6d8746415deef6bc423891bb924 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Mon, 27 Feb 2023 11:12:25 +0100 Subject: [PATCH 004/110] update solution mapper to support multilingual --- lib/goo/sparql/solutions_mapper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 0544c2dd..09b0983a 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -465,4 +465,5 @@ def add_aggregations_to_model(sol) end end end -end \ No newline at end of file +end + From 1a7a6bf14021a2a78b3c2416dc89ed30baec7b35 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Mon, 27 Feb 2023 14:40:46 +0100 Subject: [PATCH 005/110] fix typo ( name ) --- lib/goo/sparql/solutions_mapper.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 09b0983a..7416a78c 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -178,18 +178,18 @@ def object_language(new_value) new_value.language || :no_lang if new_value.is_a?(RDF::Literal) end - def add_object_to_model(id, object, predicate, language, requested_lang = nil) + def add_object_to_model(id, objects, predicate, language, requested_lang = nil) if @models_by_id[id].respond_to?(:klass) - @models_by_id[id][predicate] = object unless object.nil? && !@models_by_id[id][predicate].nil? + @models_by_id[id][predicate] = objects unless objects.nil? && !@models_by_id[id][predicate].nil? elsif !@models_by_id[id].class.handler?(predicate) && - !(object.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && + !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && predicate != :id if language.nil? - @models_by_id[id].send("#{predicate}=", object, on_load: true) + @models_by_id[id].send("#{predicate}=", objects, on_load: true) else if language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil? - @models_by_id[id].send("#{predicate}=", object, on_load: true) + @models_by_id[id].send("#{predicate}=", objects, on_load: true) end end From 8cfd2f7fe135c4feba169ccc23f9cbd1dd89739c Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:46:40 +0100 Subject: [PATCH 006/110] add validators tests file --- test/test_validators.rb | 184 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 test/test_validators.rb diff --git a/test/test_validators.rb b/test/test_validators.rb new file mode 100644 index 00000000..d81410a4 --- /dev/null +++ b/test/test_validators.rb @@ -0,0 +1,184 @@ +require_relative 'test_case' + +GooTest.configure_goo +require_relative 'models' + +class Person < Goo::Base::Resource + model :person_model_validators, name_with: :name + attribute :name, enforce: [:string, :existence] + attribute :last_name, enforce: [:string] + attribute :multiple_values, enforce: [ :list, :integer] + attribute :one_number, enforce: [ :integer ] + attribute :birth_date, enforce: [ :date_time ] + attribute :male, enforce: [:boolean] + attribute :social, enforce: [:uri] + attribute :weight, enforce: [:float] + attribute :friends, enforce: [Person, :list] +end + + +class RangeTestModel < Goo::Base::Resource + model :range_test_model, name_with: :name + attribute :name, enforce: [:string, :existence, :min_3, :max_5] + attribute :multiple_values, enforce: [ :list, :integer, :min_3, :max_5 ] + attribute :one_number, enforce: [ :integer, :min_3, :max_5] + attribute :weight, enforce: [:float, :min_3, :max_5] +end + + + +class TestValidators < MiniTest::Unit::TestCase + + def self.before_suite + begin + GooTestData.create_test_case_data + rescue Exception => e + puts e.message + end + end + + def self.after_suite + GooTestData.delete_test_case_data + end + + + def test_unique_validator + + s = Student.new + s.birth_date = DateTime.parse('1978-01-01') + + s.name = "Susan" + + refute s.valid? + + s.name = "new" + + assert s.valid? + end + + def test_existence_validator + s = Student.new + + refute s.valid? + + assert s.errors[:name][:existence] + assert s.errors[:birth_date][:existence] + + + s.name = '' + s.birth_date = '' + assert s.errors[:name][:existence] + assert s.errors[:birth_date][:existence] + + + s.name = 'new' + s.birth_date = DateTime.parse('1978-01-01') + + assert s.valid? + end + + def test_datatype_validators + p = Person.new + p.name = 'test' + #nil values are valid + assert p.valid? + + p.last_name = false + p.multiple_values = "hello" + p.one_number = "hello" + p.birth_date = 100 + p.male = "ok" + p.social = 100 + p.weight = 100 + + + #wrong types are not valid + refute p.valid? + assert p.errors[:last_name][:string] + assert p.errors[:multiple_values][:list] + assert p.errors[:multiple_values][:integer] + assert p.errors[:one_number][:integer] + assert p.errors[:birth_date][:date_time] + assert p.errors[:male][:boolean] + assert p.errors[:social][:uri] + + p.last_name = "hello" + p.multiple_values = [22,11] + p.one_number = 12 + p.birth_date = DateTime.parse('1978-01-01') + p.male = true + p.social = RDF::URI.new('https://test.com/') + p.weight = 100.0 + #good types are valid + assert p.valid? + end + + def test_uri_datatype_validator + p = Person.new + p.name = 'test' + + assert p.valid? + + p.social = RDF::URI.new('') #empty uri + refute p.valid? + + p.social = RDF::URI.new('wrong/uri') + refute p.valid? + + p.social = RDF::URI.new('https://test.com/') + assert p.valid? + end + + def test_object_type_validator + p = Person.new + p.name = 'test' + p.friends = [1] + + refute p.valid? + + new_person = Person.new + p.friends = [new_person] + + refute p.valid? + + new_person.persistent = true + p.friends = [new_person] + + assert p.valid? + end + + def test_value_range_validator + p = RangeTestModel.new + + p.name = "h" + p.multiple_values = [22,11] + p.one_number = 1 + p.weight = 1.1 + + refute p.valid? + assert p.errors[:name][:min] + assert p.errors[:multiple_values][:min] + assert p.errors[:one_number][:min] + assert p.errors[:weight][:min] + + p.name = "hello hello" + p.multiple_values = [22,11,11,33,44, 55, 66] + p.one_number = 12 + p.weight = 12.1 + + refute p.valid? + assert p.errors[:name][:max] + assert p.errors[:multiple_values][:max] + assert p.errors[:one_number][:max] + assert p.errors[:weight][:max] + + p.name = "hello" + p.multiple_values = [22,11,11,3] + p.one_number = 4 + p.weight = 3.1 + + assert p.valid? + + end + +end From 85ca3a535138c8a59d5ac1980fc7388570efbbb0 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:47:15 +0100 Subject: [PATCH 007/110] add validator interface module --- lib/goo/validators/validator.rb | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/goo/validators/validator.rb diff --git a/lib/goo/validators/validator.rb b/lib/goo/validators/validator.rb new file mode 100644 index 00000000..3eae58c5 --- /dev/null +++ b/lib/goo/validators/validator.rb @@ -0,0 +1,62 @@ +module Goo + module Validators + + class ValidatorBase + + def initialize(inst, attr, value) + @inst = inst + @attr = attr + @value = value + end + + def valid? + self.instance_eval(&self.class.validator_settings[:check]) + end + + def error + message = self.class.validator_settings[:message] + if message.is_a? Proc + self.instance_eval(&message) + else + message + end + end + + end + + module Validator + + def self.included(base) + base.extend(ClassMethods) + end + + + module ClassMethods + + def key(id) + validator_settings[:id] = id + end + + def keys(ids) + key ids + end + + def validity_check(block) + validator_settings[:check] = block + end + + def error_message(message) + validator_settings[:message] = message + end + + def validator_settings + @validator_settings ||= {} + end + end + + + + end + end +end + From 56adaf6b0e7c87d00d2e0e7a95867c7054f3f0e9 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:50:34 +0100 Subject: [PATCH 008/110] implement data_type validator --- .../validators/implementations/data_type.rb | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 lib/goo/validators/implementations/data_type.rb diff --git a/lib/goo/validators/implementations/data_type.rb b/lib/goo/validators/implementations/data_type.rb new file mode 100644 index 00000000..419ab37a --- /dev/null +++ b/lib/goo/validators/implementations/data_type.rb @@ -0,0 +1,66 @@ +module Goo + module Validators + class DataType < ValidatorBase + include Validator + + keys [:list, :uri, :string, :integer, :boolean, :date_time, :float] + + error_message ->(obj) { + if @value.kind_of? Array + return "All values in attribute `#{@attr}` must be `#{@type}`" + else + return "Attribute `#{@attr}` with the value `#{@value}` must be `#{@type}`" + + end + } + + validity_check -> (obj) do + self.class.enforce_type(@type, @value) + end + + def initialize(inst, attr, value, type) + super(inst, attr, value) + @type = type + end + + + + def self.enforce_type(type, value) + return true if value.nil? + + if type == :boolean + return self.enforce_type_boolean(value) + elsif type.eql?(:uri) || type.eql?(RDF::URI) + return self.enforce_type_uri(value) + elsif type.eql?(:uri) || type.eql?(Array) + return value.is_a? Array + else + if value.is_a? Array + return value.select{|x| !x.is_a?(type)}.empty? + else + return value.is_a? type + end + end + + end + + def self.enforce_type_uri(value) + return true if value.nil? + + value.is_a?(RDF::URI) && value.valid? + end + + def self.enforce_type_boolean(value) + if value.kind_of? Array + return value.select { |x| !is_a_boolean?(x) }.empty? + else + return is_a_boolean?(value) + end + end + + def self.is_a_boolean?(value) + return (value.class == TrueClass) || (value.class == FalseClass) + end + end + end +end \ No newline at end of file From 27fffc1b1c8774166a02e98fe227c164ded213b1 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:50:56 +0100 Subject: [PATCH 009/110] migrate existence validator to the new DSL --- .../validators/implementations/existence.rb | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 lib/goo/validators/implementations/existence.rb diff --git a/lib/goo/validators/implementations/existence.rb b/lib/goo/validators/implementations/existence.rb new file mode 100644 index 00000000..e133fb99 --- /dev/null +++ b/lib/goo/validators/implementations/existence.rb @@ -0,0 +1,23 @@ +module Goo + module Validators + class Existence < ValidatorBase + include Validator + + key :existence + + error_message ->(obj) { "`#{@value}` value cannot be nil"} + + validity_check -> (obj) do + not (@value.nil? || self.class.empty_string?(@value) || self.class.empty_array?(@value)) + end + + def self.empty_string?(string) + string.is_a?(String) && string.strip.empty? + end + + def self.empty_array?(array) + array.is_a?(Array) && array && array.reject{|x| x.nil? || empty_string?(x)}.empty? + end + end + end +end \ No newline at end of file From cd67a526d2f14c2c2d74ee5bce8e473321c17cb1 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:51:10 +0100 Subject: [PATCH 010/110] migrate uniqueness validator to the new DSL --- lib/goo/validators/implementations/unique.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 lib/goo/validators/implementations/unique.rb diff --git a/lib/goo/validators/implementations/unique.rb b/lib/goo/validators/implementations/unique.rb new file mode 100644 index 00000000..feb13a4b --- /dev/null +++ b/lib/goo/validators/implementations/unique.rb @@ -0,0 +1,20 @@ +module Goo + module Validators + class Unique < ValidatorBase + include Validator + + key :unique + + error_message ->(obj) { "`#{@attr}` must be unique. " + + "There are other model instances with the same attribute value `#{@value}`."} + + validity_check -> (obj) do + return true if @value.nil? + + !Goo::SPARQL::Queries.duplicate_attribute_value?(@inst,@attr) + end + + + end + end +end \ No newline at end of file From 887653c208f6552ff959d0319101835cd61f1421 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:51:50 +0100 Subject: [PATCH 011/110] implement object_type validator with the new DSL --- .../validators/implementations/object_type.rb | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 lib/goo/validators/implementations/object_type.rb diff --git a/lib/goo/validators/implementations/object_type.rb b/lib/goo/validators/implementations/object_type.rb new file mode 100644 index 00000000..41f0349c --- /dev/null +++ b/lib/goo/validators/implementations/object_type.rb @@ -0,0 +1,46 @@ +module Goo + module Validators + class ObjectType < ValidatorBase + include Validator + + key :object_type + + error_message ->(obj) { + if @error.eql?(:persistence) + "`#{@attr}` contains non persistent models. It will not save." + else + "`#{@attr}` contains values that are not instance of `#{@model_range.model_name}`" + end + } + + validity_check -> (obj) do + values = Array(@value) + + unless values.select { |v| !self.class.is_a_model?(v, @model_range) }.empty? + @error = :no_range + return false + end + + unless values.select { |v| !self.class.persistent?(v) }.empty? + @error = :persistence + return false + end + + return true + end + + def initialize(inst, attr, value, model_range) + super(inst, attr, value) + @model_range = model_range + end + + def self.is_a_model?(value, model_range) + value.is_a?(model_range) || (value.respond_to?(:klass) && value[:klass] == model_range) + end + + def self.persistent?(value) + value.respond_to?(:klass) || value.persistent? + end + end + end +end From 9227510c023a2ae48721cb28ac246e02cecb4519 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:52:06 +0100 Subject: [PATCH 012/110] migrate range validator to the new DSL --- .../validators/implementations/value_range.rb | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/goo/validators/implementations/value_range.rb diff --git a/lib/goo/validators/implementations/value_range.rb b/lib/goo/validators/implementations/value_range.rb new file mode 100644 index 00000000..64e85341 --- /dev/null +++ b/lib/goo/validators/implementations/value_range.rb @@ -0,0 +1,49 @@ +module Goo + module Validators + class ValueRange < ValidatorBase + include Validator + + keys [:min_, :max_] + + error_message ->(obj) { + value = self.class.value_length(@value) + if @type == :min + "#{@attr} value has length `#{value}` and the min length is `#{@range}`" + else + "#{@attr} value has length `#{value}` and the max length is `#{@range}`" + end + } + + validity_check -> (obj) do + self.class.enforce_range_length(@type, @range, @value) + end + + def initialize(inst, attr, value, type) + super(inst, attr, value) + @type = type.index("max_") ? :max : :min + @range = self.class.range(type) + end + + def self.enforce_range_length(type_range, range, value) + return false if value.nil? + value_length = self.value_length(value) + + (type_range.eql?(:min) && (value_length >= range)) || (type_range.eql?(:max) && (value_length <= range)) + end + + def self.range(opt) + opt[4..opt.length].to_i + end + + def self.value_length(value) + return 0 if value.nil? + + if value.is_a?(String) || value.is_a?(Array) + value.length + else + value + end + end + end + end +end From 2d1457d0f92eaf3fc646ae4682ae54204700aaf2 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:54:04 +0100 Subject: [PATCH 013/110] refactor the enforce module to use the new validators implementation --- lib/goo/validators/enforce.rb | 182 ++++++++++++++-------------------- 1 file changed, 74 insertions(+), 108 deletions(-) diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index 2e1b9e56..9283c070 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -3,129 +3,95 @@ module Goo module Validators module Enforce - def self.enforce_by_attribute(model,attr) - return model.model_settings[:attributes][attr][:enforce] - end + class EnforceInstance + attr_reader :errors_by_opt + def initialize + @errors_by_opt = {} + end - def self.enforce_type_boolean(attr,value) - if value.kind_of? Array - if (value.select {|x| !((x.class == TrueClass) || (x.class == FalseClass))} ).length > 0 - return "All values in attribute `#{attr}` must be `Boolean`" - end - else - if !((value.class == TrueClass) || (value.class == FalseClass)) - return "Attribute `#{attr}` value `#{value}` must be a `Boolean`" + def enforce(inst,attr,value) + enforce_opts = enforce_by_attribute(inst.class,attr) + return nil if enforce_opts.nil? or enforce_opts.length == 0 + + enforce_opts.each do |opt| + case opt + when :unique + check Goo::Validators::Unique, inst, attr, value, opt + when :no_list + validator = Goo::Validators::DataType.new(inst, attr, value, Array) + if validator.valid? && !value.nil? + add_error(opt, + "`#{attr}` is defined as non Array - it cannot hold multiple values") + end + when :existence + check Goo::Validators::Existence, inst, attr, value, opt + when :list, Array + check Goo::Validators::DataType, inst, attr, value,opt, Array + when :uri, RDF::URI + check Goo::Validators::DataType, inst, attr, value,opt, RDF::URI + when :string, String + check Goo::Validators::DataType, inst, attr, value,opt, String + when :integer, Integer + check Goo::Validators::DataType, inst, attr, value,opt, Integer + when :boolean + check Goo::Validators::DataType, inst, attr, value, opt,:boolean + when :date_time, DateTime + check Goo::Validators::DataType, inst, attr, value, opt, DateTime + when :float, Float + check Goo::Validators::DataType, inst, attr, value, opt, Float + when Proc + call_proc(opt, inst, attr) + when /^max_/, /^min_/ + type = opt.to_s.index("max_") ? :max : :min + check Goo::Validators::ValueRange, inst, attr, value, type, opt.to_s + else + model_range = object_type(opt) + check_object_type inst, attr, value, model_range + end end + + errors_by_opt.length > 0 ? errors_by_opt : nil end - end - def self.enforce_type(attr,type,value) - if type == :boolean - return self.enforce_type_boolean(attr,value) + private + + def object_type(opt) + opt.respond_to?(:shape_attribute) ? opt : Goo.model_by_name(opt) end - if value.kind_of? Array - if (value.select {|x| !(x.kind_of? type)} ).length > 0 - return "All values in attribute `#{attr}` must be `#{type.name}`" - end - else - if !(value.kind_of? type) - return "Attribute `#{attr}` value `#{value}` must be a `#{type.name}`" + + def check_object_type(inst, attr, value, model_range) + + if model_range && !value.nil? + check Goo::Validators::ObjectType, inst, attr, value, model_range.model_name, model_range end end - end - def self.enforce_range_length(type_range,attr,opt_s,value) - if !value.nil? && !(value.kind_of?(Array) || value.kind_of?(String)) - return "#{attr} value (#{value}) must be an Array or String - it has range length constraints" + def check(validator_class, inst, attr, value, opt, *options) + validator = validator_class.new(inst, attr, value, *options) + add_error(opt, validator.error) unless validator.valid? end - range = opt_s[4..opt_s.length].to_i - if type_range == :min - if !value.nil? && (value.length < range) - return "#{attr} value has length `#{value.length}` and the min length is `#{range}`" - end - else - if !value.nil? && (value.length > range) - return "#{attr} value has length `#{value.length}` and the max length is `#{range}`" - end + def enforce_by_attribute(model, attr) + model.model_settings[:attributes][attr][:enforce] end - end - def self.enforce(inst,attr,value) - enforce_opts = enforce_by_attribute(inst.class,attr) - return nil if enforce_opts.nil? or enforce_opts.length == 0 - errors_by_opt = {} - enforce_opts.each do |opt| - case opt - when :class - nil - when :unique - unless value.nil? - dup = Goo::SPARQL::Queries.duplicate_attribute_value?(inst,attr) - if dup - add_error(opt, errors_by_opt, - "`#{attr}` must be unique. " + - "There are other model instances with the same attribute value `#{value}`.") - end - end - when :no_list - if value.kind_of? Array - add_error(opt, errors_by_opt, - "`#{attr}` is defined as non Array - it cannot hold multiple values") - end - when :existence - add_error(opt, errors_by_opt, "`#{attr}` value cannot be nil") if value.nil? - when :list, Array - if !value.nil? && !(value.kind_of? Array) - add_error(opt, errors_by_opt, "`#{attr}` value must be an Array") - end - when :uri, RDF::URI - add_error(opt, errors_by_opt, enforce_type(attr,RDF::URI,value)) unless value.nil? - when :string, String - add_error(opt, errors_by_opt, enforce_type(attr,String,value)) unless value.nil? - when :integer, Integer - add_error(opt, errors_by_opt, enforce_type(attr,Integer,value)) unless value.nil? - when :boolean - add_error(opt, errors_by_opt, enforce_type(attr,:boolean,value)) unless value.nil? - when :date_time, DateTime - add_error(opt, errors_by_opt, enforce_type(attr,DateTime,value)) unless value.nil? - when Proc - # This should return an array like [:name_of_error1, "Error message 1", :name_of_error2, "Error message 2"] - errors = opt.call(inst, attr) - errors.each_slice(2) do |e| - next if e.nil? || e.compact.empty? - add_error(e[0].to_sym, errors_by_opt, e[1]) rescue binding.pry - end - else - model_range = opt.respond_to?(:shape_attribute) ? opt : Goo.model_by_name(opt) - if model_range and !value.nil? - values = value.kind_of?(Array) ? value : [value] - values.each do |v| - if (!v.kind_of?(model_range)) && !(v.respond_to?(:klass) && v[:klass] == model_range) - add_error(model_range.model_name, errors_by_opt, - "`#{attr}` contains values that are not instance of `#{model_range.model_name}`") - else - if !v.respond_to?(:klass) && !v.persistent? - add_error(model_range.model_name, errors_by_opt, - "`#{attr}` contains non persistent models. It will not save.") - end - end - end - end - opt_s = opt.to_s - if opt_s.index("max_") == 0 - add_error(:max, errors_by_opt, enforce_range_length(:max,attr,opt_s,value)) unless value.nil? - end - if opt_s.index("min_") == 0 - add_error(:min, errors_by_opt, enforce_range_length(:min,attr,opt_s,value)) unless value.nil? - end + def call_proc(opt,inst, attr) + # This should return an array like [:name_of_error1, "Error message 1", :name_of_error2, "Error message 2"] + errors = opt.call(inst, attr) + errors.each_slice(2) do |e| + next if e.nil? || e.compact.empty? + add_error(e[0].to_sym, e[1]) end end - return errors_by_opt.length > 0 ? errors_by_opt : nil + + def add_error(opt, err) + return if err.nil? + @errors_by_opt[opt] = err + end end - def self.add_error(opt, h, err) - return if err.nil? - h[opt] = err + + def self.enforce(inst,attr,value) + EnforceInstance.new.enforce(inst,attr,value) end end end From 6091f1a15e3ec1454187a65f9e3649af8f0b0994 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 02:57:33 +0100 Subject: [PATCH 014/110] force to regenerate the id when we update related attribute (named_with) --- lib/goo/base/resource.rb | 54 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index c12f6203..ac4dcadc 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -78,25 +78,10 @@ def id=(new_id) end def id - if @id.nil? - if self.class.name_with == :id - raise IDGenerationError, ":id must be set if configured in name_with" - end - custom_name = self.class.name_with - if custom_name.instance_of?(Symbol) - @id = id_from_attribute() - elsif custom_name - begin - @id = custom_name.call(self) - rescue => e - raise IDGenerationError, "Problem with custom id generation: #{e.message}" - end - else - raise IDGenerationError, "custom_name is nil. settings for this model are incorrect." + @id = generate_id if @id.nil? + + @id end - end - return @id - end def persistent? return @persistent @@ -110,14 +95,9 @@ def modified? return modified_attributes.length > 0 end - def exist?(from_valid=false) - #generate id with proc - begin - id() unless self.class.name_with.kind_of?(Symbol) - rescue IDGenerationError - end + def exist?(from_valid = false) - _id = @id + _id = generate_id if _id.nil? && !from_valid && self.class.name_with.is_a?(Symbol) begin _id = id_from_attribute() @@ -125,7 +105,7 @@ def exist?(from_valid=false) end end return false unless _id - return Goo::SPARQL::Queries.model_exist(self,id=_id) + return Goo::SPARQL::Queries.model_exist(self, id = _id) end def fully_loaded? @@ -462,10 +442,30 @@ def self.all end protected + def id_from_attribute() uattr = self.class.name_with uvalue = self.send("#{uattr}") - return self.class.id_from_unique_attribute(uattr,uvalue) + return self.class.id_from_unique_attribute(uattr, uvalue) + end + + def generate_id + return nil unless self.class.name_with + + raise IDGenerationError, ":id must be set if configured in name_with" if self.class.name_with == :id + custom_name = self.class.name_with + if custom_name.instance_of?(Symbol) + id = id_from_attribute + elsif custom_name + begin + id = custom_name.call(self) + rescue => e + raise IDGenerationError, "Problem with custom id generation: #{e.message}" + end + else + raise IDGenerationError, "custom_name is nil. settings for this model are incorrect." + end + id end end From 24c99395f26b7bfc8ab86649b1eb448082ccac98 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 03:02:20 +0100 Subject: [PATCH 015/110] require the validators implementation --- lib/goo.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/goo.rb b/lib/goo.rb index 71df0691..34b824d2 100644 --- a/lib/goo.rb +++ b/lib/goo.rb @@ -16,6 +16,10 @@ require_relative "goo/search/search" require_relative "goo/base/base" require_relative "goo/validators/enforce" +require_relative "goo/validators/validator" +project_root = File.dirname(File.absolute_path(__FILE__)) +Dir.glob("#{project_root}/goo/validators/implementations/*", &method(:require)) + require_relative "goo/utils/utils" require_relative "goo/mixins/sparql_client" From 46d1b9f7be2a9fc1c9857cdca099359c18878820 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 03:25:28 +0100 Subject: [PATCH 016/110] update existence validator to not accept empty to_s objects --- lib/goo/validators/implementations/existence.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/goo/validators/implementations/existence.rb b/lib/goo/validators/implementations/existence.rb index e133fb99..a551f35d 100644 --- a/lib/goo/validators/implementations/existence.rb +++ b/lib/goo/validators/implementations/existence.rb @@ -8,15 +8,22 @@ class Existence < ValidatorBase error_message ->(obj) { "`#{@value}` value cannot be nil"} validity_check -> (obj) do - not (@value.nil? || self.class.empty_string?(@value) || self.class.empty_array?(@value)) + not (@value.nil? || self.class.empty?(@value) || self.class.empty_array?(@value)) end + def self.empty?(value) + empty_string?(value) || empty_to_s?(value) + end def self.empty_string?(string) string.is_a?(String) && string.strip.empty? end + def self.empty_to_s?(object) + object && object.to_s&.strip.empty? + end + def self.empty_array?(array) - array.is_a?(Array) && array && array.reject{|x| x.nil? || empty_string?(x)}.empty? + array.is_a?(Array) && array && array.reject{|x| x.nil? || empty?(x)}.empty? end end end From a998a4e02e7edfdc0f69b877f9b5a79aa0131b8d Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 04:29:41 +0100 Subject: [PATCH 017/110] update exist? test --- lib/goo/base/resource.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index ac4dcadc..e82265d4 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -96,16 +96,19 @@ def modified? end def exist?(from_valid = false) - - _id = generate_id - if _id.nil? && !from_valid && self.class.name_with.is_a?(Symbol) begin - _id = id_from_attribute() + id unless self.class.name_with.kind_of?(Symbol) rescue IDGenerationError + # Ignored end + + _id = @id + if from_valid || _id.nil? + _id = generate_id rescue _id = nil end + return false unless _id - return Goo::SPARQL::Queries.model_exist(self, id = _id) + Goo::SPARQL::Queries.model_exist(self, id = _id) end def fully_loaded? From 5168edf1004031b107bce369fab51f279b4a1956 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 06:23:24 +0100 Subject: [PATCH 018/110] add symmetric validator tests for no_list and list cases --- test/models.rb | 5 ++- test/test_validators.rb | 82 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/test/models.rb b/test/models.rb index cd606eed..7d490a4a 100644 --- a/test/models.rb +++ b/test/models.rb @@ -101,7 +101,10 @@ def self.create_test_case_data end def self.delete_test_case_data - objects = [Student, University, Program, Category, Address] + delete_all [Student, University, Program, Category, Address] + end + + def self.delete_all(objects) objects.each do |obj| obj.where.include(obj.attributes).each do |i| i.delete diff --git a/test/test_validators.rb b/test/test_validators.rb index d81410a4..9be94798 100644 --- a/test/test_validators.rb +++ b/test/test_validators.rb @@ -25,6 +25,13 @@ class RangeTestModel < Goo::Base::Resource attribute :weight, enforce: [:float, :min_3, :max_5] end +class SymmetricTestModel < Goo::Base::Resource + model :symmetric_test_model, name_with: :name + attribute :name, enforce: [:unique, :existence] + attribute :friend, enforce: [SymmetricTestModel, :symmetric] + attribute :friends, enforce: [SymmetricTestModel, :symmetric, :list] +end + class TestValidators < MiniTest::Unit::TestCase @@ -39,6 +46,7 @@ def self.before_suite def self.after_suite GooTestData.delete_test_case_data + GooTestData.delete_all [SymmetricTestModel] end @@ -181,4 +189,78 @@ def test_value_range_validator end + def test_symmetric_validator_no_list + p1 = SymmetricTestModel.new + p2 = SymmetricTestModel.new + p3 = SymmetricTestModel.new + p1.name = "p1" + p2.name = "p2" + p3.name = "p3" + + p2.save + p3.save + + p1.friend = p2 + + refute p1.valid? + assert p1.errors[:friend][:symmetric] + + p3.friend = p1 + + refute p1.valid? + + p2.friend = p1 + p1.friend = p2 + + assert p1.valid? + + p1.save + + assert p2.valid? + + end + + def test_symmetric_validator_list + p1 = SymmetricTestModel.new + p2 = SymmetricTestModel.new + p3 = SymmetricTestModel.new + p4 = SymmetricTestModel.new + p1.name = "p1" + p2.name = "p2" + p3.name = "p3" + p4.name = "p4" + + p2.save + p3.save + p4.save + + p1.friends = [p2, p3] + + refute p1.valid? + assert p1.errors[:friends][:symmetric] + + p2.friends = [p1, p3, p4] + p3.friends = [p2] + p4.friends = [p2] + + refute p1.valid? + refute p2.valid? + + + p3.friends = [p2, p1] + + assert p1.valid? + p1.save + + assert p3.valid? + p3.save + + + assert p2.valid? + + p2.save + + assert p4.valid? + + end end From 810771a1c550ba765baef6ee0b7065a67f32e749 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 06:24:26 +0100 Subject: [PATCH 019/110] implement symmetric validator --- lib/goo/validators/enforce.rb | 2 + .../validators/implementations/existence.rb | 5 ++- .../validators/implementations/symmetric.rb | 44 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 lib/goo/validators/implementations/symmetric.rb diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index 9283c070..70953645 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -39,6 +39,8 @@ def enforce(inst,attr,value) check Goo::Validators::DataType, inst, attr, value, opt, DateTime when :float, Float check Goo::Validators::DataType, inst, attr, value, opt, Float + when :symmetric + check Goo::Validators::Symmetric, inst, attr, value, opt when Proc call_proc(opt, inst, attr) when /^max_/, /^min_/ diff --git a/lib/goo/validators/implementations/existence.rb b/lib/goo/validators/implementations/existence.rb index a551f35d..6e759154 100644 --- a/lib/goo/validators/implementations/existence.rb +++ b/lib/goo/validators/implementations/existence.rb @@ -8,9 +8,12 @@ class Existence < ValidatorBase error_message ->(obj) { "`#{@value}` value cannot be nil"} validity_check -> (obj) do - not (@value.nil? || self.class.empty?(@value) || self.class.empty_array?(@value)) + not self.class.empty_value?(@value) end + def self.empty_value?(value) + value.nil? || self.empty?(value) || self.empty_array?(value) + end def self.empty?(value) empty_string?(value) || empty_to_s?(value) end diff --git a/lib/goo/validators/implementations/symmetric.rb b/lib/goo/validators/implementations/symmetric.rb new file mode 100644 index 00000000..c1c9055c --- /dev/null +++ b/lib/goo/validators/implementations/symmetric.rb @@ -0,0 +1,44 @@ +module Goo + module Validators + class Symmetric < ValidatorBase + include Validator + + key :symmetric + + error_message ->(obj) { + "symmetric error" + } + + validity_check -> (obj) do + return true if Existence.empty_value?(@value) + + return Array(@value).select{|x| not self.class.symmetric?(@attr,x, @inst)}.empty? + end + + def self.symmetric?(attr, value, source_object) + if self.respond_to?(attr, value) + target_values = self.attr_value(attr, value) + return target_values.any?{ |target_object| self.equivalent?(target_object, source_object)} + end + + return false + end + + def self.respond_to?(attr, object) + object && object.respond_to?(attr) + end + + def self.attr_value(attr, object) + Array(object.send(attr)) + end + + def self.equivalent?(object1, object2) + if object1.respond_to?(:id) && object2.respond_to?(:id) + object1.id.eql?(object2.id) + else + object2 == object1 + end + end + end + end +end \ No newline at end of file From 0f20d7a25f7b8e7964120a1ebdc764f00d4c815b Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 08:31:42 +0100 Subject: [PATCH 020/110] move re used methods to the parent class --- .../validators/implementations/data_type.rb | 10 +++--- .../validators/implementations/existence.rb | 16 --------- .../validators/implementations/object_type.rb | 8 ++--- .../validators/implementations/value_range.rb | 12 +++---- lib/goo/validators/validator.rb | 36 +++++++++++++++++++ 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/lib/goo/validators/implementations/data_type.rb b/lib/goo/validators/implementations/data_type.rb index 419ab37a..0ea65ab3 100644 --- a/lib/goo/validators/implementations/data_type.rb +++ b/lib/goo/validators/implementations/data_type.rb @@ -15,7 +15,7 @@ class DataType < ValidatorBase } validity_check -> (obj) do - self.class.enforce_type(@type, @value) + self.enforce_type(@type, @value) end def initialize(inst, attr, value, type) @@ -25,7 +25,7 @@ def initialize(inst, attr, value, type) - def self.enforce_type(type, value) + def enforce_type(type, value) return true if value.nil? if type == :boolean @@ -44,13 +44,13 @@ def self.enforce_type(type, value) end - def self.enforce_type_uri(value) + def enforce_type_uri(value) return true if value.nil? value.is_a?(RDF::URI) && value.valid? end - def self.enforce_type_boolean(value) + def enforce_type_boolean(value) if value.kind_of? Array return value.select { |x| !is_a_boolean?(x) }.empty? else @@ -58,7 +58,7 @@ def self.enforce_type_boolean(value) end end - def self.is_a_boolean?(value) + def is_a_boolean?(value) return (value.class == TrueClass) || (value.class == FalseClass) end end diff --git a/lib/goo/validators/implementations/existence.rb b/lib/goo/validators/implementations/existence.rb index 6e759154..fcf04d61 100644 --- a/lib/goo/validators/implementations/existence.rb +++ b/lib/goo/validators/implementations/existence.rb @@ -11,23 +11,7 @@ class Existence < ValidatorBase not self.class.empty_value?(@value) end - def self.empty_value?(value) - value.nil? || self.empty?(value) || self.empty_array?(value) - end - def self.empty?(value) - empty_string?(value) || empty_to_s?(value) - end - def self.empty_string?(string) - string.is_a?(String) && string.strip.empty? - end - def self.empty_to_s?(object) - object && object.to_s&.strip.empty? - end - - def self.empty_array?(array) - array.is_a?(Array) && array && array.reject{|x| x.nil? || empty?(x)}.empty? - end end end end \ No newline at end of file diff --git a/lib/goo/validators/implementations/object_type.rb b/lib/goo/validators/implementations/object_type.rb index 41f0349c..3af97b41 100644 --- a/lib/goo/validators/implementations/object_type.rb +++ b/lib/goo/validators/implementations/object_type.rb @@ -16,12 +16,12 @@ class ObjectType < ValidatorBase validity_check -> (obj) do values = Array(@value) - unless values.select { |v| !self.class.is_a_model?(v, @model_range) }.empty? + unless values.select { |v| !self.is_a_model?(v, @model_range) }.empty? @error = :no_range return false end - unless values.select { |v| !self.class.persistent?(v) }.empty? + unless values.select { |v| !self.persistent?(v) }.empty? @error = :persistence return false end @@ -34,11 +34,11 @@ def initialize(inst, attr, value, model_range) @model_range = model_range end - def self.is_a_model?(value, model_range) + def is_a_model?(value, model_range) value.is_a?(model_range) || (value.respond_to?(:klass) && value[:klass] == model_range) end - def self.persistent?(value) + def persistent?(value) value.respond_to?(:klass) || value.persistent? end end diff --git a/lib/goo/validators/implementations/value_range.rb b/lib/goo/validators/implementations/value_range.rb index 64e85341..71440bcf 100644 --- a/lib/goo/validators/implementations/value_range.rb +++ b/lib/goo/validators/implementations/value_range.rb @@ -6,7 +6,7 @@ class ValueRange < ValidatorBase keys [:min_, :max_] error_message ->(obj) { - value = self.class.value_length(@value) + value = self.value_length(@value) if @type == :min "#{@attr} value has length `#{value}` and the min length is `#{@range}`" else @@ -15,27 +15,27 @@ class ValueRange < ValidatorBase } validity_check -> (obj) do - self.class.enforce_range_length(@type, @range, @value) + self.enforce_range_length(@type, @range, @value) end def initialize(inst, attr, value, type) super(inst, attr, value) @type = type.index("max_") ? :max : :min - @range = self.class.range(type) + @range = self.range(type) end - def self.enforce_range_length(type_range, range, value) + def enforce_range_length(type_range, range, value) return false if value.nil? value_length = self.value_length(value) (type_range.eql?(:min) && (value_length >= range)) || (type_range.eql?(:max) && (value_length <= range)) end - def self.range(opt) + def range(opt) opt[4..opt.length].to_i end - def self.value_length(value) + def value_length(value) return 0 if value.nil? if value.is_a?(String) || value.is_a?(Array) diff --git a/lib/goo/validators/validator.rb b/lib/goo/validators/validator.rb index 3eae58c5..0655c0df 100644 --- a/lib/goo/validators/validator.rb +++ b/lib/goo/validators/validator.rb @@ -52,10 +52,46 @@ def error_message(message) def validator_settings @validator_settings ||= {} end + + def ids + validator_settings[:id] + end + + def equivalent_value?(object1, object2) + if object1.respond_to?(:id) && object2.respond_to?(:id) + object1.id.eql?(object2.id) + else + object2 == object1 + end + end + + def attr_value(attr, object) + Array(object.send(attr)) + end + + def empty_value?(value) + value.nil? || empty?(value) || empty_array?(value) + end + def empty?(value) + empty_string?(value) || empty_to_s?(value) + end + def empty_string?(string) + string.is_a?(String) && string.strip.empty? + end + + def empty_to_s?(object) + object && object.to_s&.strip.empty? + end + + def empty_array?(array) + array.is_a?(Array) && array && array.reject{|x| x.nil? || empty?(x)}.empty? + end end + + end end end From 9e4739c8cd177367df4d7e02d373a956db780e00 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 08:33:19 +0100 Subject: [PATCH 021/110] update symmetric code and error message --- .../validators/implementations/symmetric.rb | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/goo/validators/implementations/symmetric.rb b/lib/goo/validators/implementations/symmetric.rb index c1c9055c..e9ceb3f4 100644 --- a/lib/goo/validators/implementations/symmetric.rb +++ b/lib/goo/validators/implementations/symmetric.rb @@ -6,39 +6,28 @@ class Symmetric < ValidatorBase key :symmetric error_message ->(obj) { - "symmetric error" + "`#{@attr}` must be symmetric" } validity_check -> (obj) do - return true if Existence.empty_value?(@value) + return true if self.class.empty_value?(@value) - return Array(@value).select{|x| not self.class.symmetric?(@attr,x, @inst)}.empty? + return Array(@value).select{|x| not symmetric?(@attr,x, @inst)}.empty? end - def self.symmetric?(attr, value, source_object) - if self.respond_to?(attr, value) - target_values = self.attr_value(attr, value) - return target_values.any?{ |target_object| self.equivalent?(target_object, source_object)} + def symmetric?(attr, value, source_object) + if respond_to?(attr, value) + target_values = self.class.attr_value(attr, value) + return target_values.any?{ |target_object| self.class.equivalent_value?(target_object, source_object)} end return false end - def self.respond_to?(attr, object) + def respond_to?(attr, object) object && object.respond_to?(attr) end - def self.attr_value(attr, object) - Array(object.send(attr)) - end - - def self.equivalent?(object1, object2) - if object1.respond_to?(:id) && object2.respond_to?(:id) - object1.id.eql?(object2.id) - else - object2 == object1 - end - end end end end \ No newline at end of file From 05bb6cbafb727ea6fc1990c4440f5927f9ef9f76 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 08:34:02 +0100 Subject: [PATCH 022/110] add distinct of validator tests --- test/test_validators.rb | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/test_validators.rb b/test/test_validators.rb index 9be94798..02b5ed8a 100644 --- a/test/test_validators.rb +++ b/test/test_validators.rb @@ -32,6 +32,13 @@ class SymmetricTestModel < Goo::Base::Resource attribute :friends, enforce: [SymmetricTestModel, :symmetric, :list] end +class DistinctOfTestModel < Goo::Base::Resource + model :symmetric_test_model, name_with: :name + attribute :name, enforce: [:unique, :existence, :string] + attribute :last_name, enforce: [:distinct_of_name, :string] + attribute :names, enforce: [:list, :string] + attribute :last_names, enforce: [:list, :distinct_of_names, :string] +end class TestValidators < MiniTest::Unit::TestCase @@ -217,7 +224,7 @@ def test_symmetric_validator_no_list p1.save assert p2.valid? - + GooTestData.delete_all [SymmetricTestModel] end def test_symmetric_validator_list @@ -261,6 +268,32 @@ def test_symmetric_validator_list p2.save assert p4.valid? + GooTestData.delete_all [SymmetricTestModel] + end + + def test_distinct_of_validator + p = DistinctOfTestModel.new + p.name = "p1" + p.last_name = "p1" + p.names = ["p1", "p2"] + p.last_names = ["p1", "p2"] + + + refute p.valid? + + p.last_name = "last name" + p.last_names = ["last name 1", "last name 2"] + + assert p.valid? + + p.last_name = "last name" + p.last_names = ["last name 1", "p2"] + refute p.valid? + + p.last_name = "" + p.last_names = [] + + assert p.valid? end end From fb62419689644a3058c245a7511d61b31528106b Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 08:34:23 +0100 Subject: [PATCH 023/110] implement distinct_of validator --- lib/goo/validators/enforce.rb | 2 ++ .../validators/implementations/distinct_of.rb | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 lib/goo/validators/implementations/distinct_of.rb diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index 70953645..bc0fd4c1 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -41,6 +41,8 @@ def enforce(inst,attr,value) check Goo::Validators::DataType, inst, attr, value, opt, Float when :symmetric check Goo::Validators::Symmetric, inst, attr, value, opt + when /^distinct_of_/ + check Goo::Validators::DistinctOf, inst, attr, value, opt, opt when Proc call_proc(opt, inst, attr) when /^max_/, /^min_/ diff --git a/lib/goo/validators/implementations/distinct_of.rb b/lib/goo/validators/implementations/distinct_of.rb new file mode 100644 index 00000000..820f5125 --- /dev/null +++ b/lib/goo/validators/implementations/distinct_of.rb @@ -0,0 +1,36 @@ +module Goo + module Validators + class DistinctOf < ValidatorBase + include Validator + + key :distinct_of_ + + error_message ->(obj) { "`#{@attr}` must be distinct of `#{@property}`"} + + validity_check -> (obj) do + return true if self.class.empty_value?(@value) + + self.distinct?(@inst, @property, @value) + end + + def initialize(inst, attr, value, key) + super(inst, attr, value) + @property = property(key) + end + + def property(opt) + opt[self.class.ids.size..opt.length].to_sym + end + + def distinct?(inst, property, value) + target_values = self.class.attr_value(property, inst) + current_values = Array(value) + + !current_values.any?{ |x| self.find_any?(target_values, x)} + end + def find_any?(array, value) + array.any?{ |x| self.class.equivalent_value?(value, x)} + end + end + end +end \ No newline at end of file From 5cd980d6a7014975a95d893fa32cdb5209f17859 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 09:05:12 +0100 Subject: [PATCH 024/110] add superior_equal_to validator tests --- test/test_validators.rb | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/test/test_validators.rb b/test/test_validators.rb index 02b5ed8a..c0989180 100644 --- a/test/test_validators.rb +++ b/test/test_validators.rb @@ -33,13 +33,20 @@ class SymmetricTestModel < Goo::Base::Resource end class DistinctOfTestModel < Goo::Base::Resource - model :symmetric_test_model, name_with: :name + model :distinct_of_test_model, name_with: :name attribute :name, enforce: [:unique, :existence, :string] attribute :last_name, enforce: [:distinct_of_name, :string] attribute :names, enforce: [:list, :string] attribute :last_names, enforce: [:list, :distinct_of_names, :string] end +class SuperiorToTestModel < Goo::Base::Resource + model :superior_to_test_model, name_with: :name + attribute :name, enforce: [:unique, :existence, :string] + attribute :birth_date, enforce: [:date_time] + attribute :death_date, enforce: [:superior_equal_to_birth_date, :date_time] +end + class TestValidators < MiniTest::Unit::TestCase @@ -296,4 +303,22 @@ def test_distinct_of_validator assert p.valid? end + + def test_superior_equal_to_validator + p = SuperiorToTestModel.new + p.name = "p" + p.birth_date = DateTime.parse('1998-12-02') + p.death_date = DateTime.parse('1995-12-02') + + refute p.valid? + assert p.errors[:death_date][:superior_equal_to_birth_date] + + p.death_date = DateTime.parse('2023-12-02') + + assert p.valid? + + p.birth_date = nil + + assert p.valid? + end end From 0d17a84da13c304ab20993d953186f93fa2b3423 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 09:06:02 +0100 Subject: [PATCH 025/110] extract property method to ValidatorBase class --- lib/goo/validators/implementations/distinct_of.rb | 4 +--- lib/goo/validators/validator.rb | 6 +++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/goo/validators/implementations/distinct_of.rb b/lib/goo/validators/implementations/distinct_of.rb index 820f5125..289a9de2 100644 --- a/lib/goo/validators/implementations/distinct_of.rb +++ b/lib/goo/validators/implementations/distinct_of.rb @@ -18,9 +18,7 @@ def initialize(inst, attr, value, key) @property = property(key) end - def property(opt) - opt[self.class.ids.size..opt.length].to_sym - end + def distinct?(inst, property, value) target_values = self.class.attr_value(property, inst) diff --git a/lib/goo/validators/validator.rb b/lib/goo/validators/validator.rb index 0655c0df..fcb056ea 100644 --- a/lib/goo/validators/validator.rb +++ b/lib/goo/validators/validator.rb @@ -54,7 +54,11 @@ def validator_settings end def ids - validator_settings[:id] + Array(validator_settings[:id]) + end + + def property(key) + key[ids.first.size..key.size].to_sym end def equivalent_value?(object1, object2) From 5740a6b1c5da58decef197261b5532bc4b23fd72 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 09:06:29 +0100 Subject: [PATCH 026/110] implement superior_equal_to validator --- lib/goo/validators/enforce.rb | 2 ++ .../implementations/superior_equal_to.rb | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 lib/goo/validators/implementations/superior_equal_to.rb diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index bc0fd4c1..7c4b8345 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -43,6 +43,8 @@ def enforce(inst,attr,value) check Goo::Validators::Symmetric, inst, attr, value, opt when /^distinct_of_/ check Goo::Validators::DistinctOf, inst, attr, value, opt, opt + when /^superior_equal_to_/ + check Goo::Validators::SuperiorEqualTo, inst, attr, value, opt, opt when Proc call_proc(opt, inst, attr) when /^max_/, /^min_/ diff --git a/lib/goo/validators/implementations/superior_equal_to.rb b/lib/goo/validators/implementations/superior_equal_to.rb new file mode 100644 index 00000000..91508f30 --- /dev/null +++ b/lib/goo/validators/implementations/superior_equal_to.rb @@ -0,0 +1,26 @@ +module Goo + module Validators + class SuperiorEqualTo < ValidatorBase + include Validator + + key :superior_equal_to_ + + error_message ->(obj) { + "`#{@attr}` must be superior or equal to `#{@property}`" + } + + validity_check -> (obj) do + target_values = self.class.attr_value(@property, @inst) + + return true if target_values.empty? + + return @value >= target_values.first + end + + def initialize(inst, attr, value, key) + super(inst, attr, value) + @property = self.class.property(key) + end + end + end +end From 9707004cb2a55d60449dde6221fef4e44514f765 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 10:08:22 +0100 Subject: [PATCH 027/110] add inverse of validator tests --- test/test_validators.rb | 66 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/test/test_validators.rb b/test/test_validators.rb index c0989180..c01a543b 100644 --- a/test/test_validators.rb +++ b/test/test_validators.rb @@ -47,6 +47,15 @@ class SuperiorToTestModel < Goo::Base::Resource attribute :death_date, enforce: [:superior_equal_to_birth_date, :date_time] end +class InverseOfTestModel < Goo::Base::Resource + model :inverse_test_model_one, name_with: :name + attribute :name, enforce: [:unique, :existence, :string] + attribute :state, enforce: [InverseOfTestModel] + attribute :city, enforce: [:inverse_of_state, InverseOfTestModel] + attribute :states, enforce: [InverseOfTestModel, :list] + attribute :cities, enforce: [:inverse_of_states, InverseOfTestModel, :list] +end + class TestValidators < MiniTest::Unit::TestCase @@ -60,7 +69,7 @@ def self.before_suite def self.after_suite GooTestData.delete_test_case_data - GooTestData.delete_all [SymmetricTestModel] + GooTestData.delete_all [SymmetricTestModel, InverseOfTestModel] end @@ -321,4 +330,59 @@ def test_superior_equal_to_validator assert p.valid? end + + def test_inverse_of_validator_no_list + GooTestData.delete_all [InverseOfTestModel] + p1 = InverseOfTestModel.new + p2 = InverseOfTestModel.new + + p1.name = 'p1' + p2.name = 'p2' + + + p2.save + + p1.city = p2 + + refute p1.valid? + assert p1.errors[:city][:inverse_of_state] + + + p2.state = p1 + + assert p1.valid? + + end + + def test_inverse_of_validator_list + GooTestData.delete_all [InverseOfTestModel] + p1 = InverseOfTestModel.new + p2 = InverseOfTestModel.new + p3 = InverseOfTestModel.new + p4 = InverseOfTestModel.new + + p1.name = 'p1' + p2.name = 'p2' + p3.name = 'p3' + p4.name = 'p4' + + p2.save + p3.save + + p1.cities = [p2,p3] + + refute p1.valid? + assert p1.errors[:cities][:inverse_of_states] + + p2.states = [p1, p4] + p3.states = [p2, p4] + + refute p1.valid? + assert p1.errors[:cities][:inverse_of_states] + + p3.states = [p2, p4, p1] + + assert p1.valid? + + end end From 9ea0be87010b734d363d0c891ed73eedae518d97 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 10:09:19 +0100 Subject: [PATCH 028/110] implement inverse_of validator --- lib/goo/validators/enforce.rb | 2 ++ .../validators/implementations/inverse_of.rb | 35 +++++++++++++++++++ lib/goo/validators/validator.rb | 5 +++ 3 files changed, 42 insertions(+) create mode 100644 lib/goo/validators/implementations/inverse_of.rb diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index 7c4b8345..e9efe249 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -45,6 +45,8 @@ def enforce(inst,attr,value) check Goo::Validators::DistinctOf, inst, attr, value, opt, opt when /^superior_equal_to_/ check Goo::Validators::SuperiorEqualTo, inst, attr, value, opt, opt + when /^inverse_of_/ + check Goo::Validators::InverseOf, inst, attr, value, opt, opt when Proc call_proc(opt, inst, attr) when /^max_/, /^min_/ diff --git a/lib/goo/validators/implementations/inverse_of.rb b/lib/goo/validators/implementations/inverse_of.rb new file mode 100644 index 00000000..60518af3 --- /dev/null +++ b/lib/goo/validators/implementations/inverse_of.rb @@ -0,0 +1,35 @@ +module Goo + module Validators + class InverseOf < ValidatorBase + include Validator + + key :inverse_of_ + + error_message ->(obj) { + "`#{@attr}` must be the inverse of ``#{@property}``" + } + + validity_check -> (obj) do + return true if self.class.empty_value?(@value) + + return Array(@value).select{|x| not inverse?(@property,x, @inst)}.empty? + end + + def initialize(inst, attr, value, key) + super(inst, attr, value) + @property = self.class.property(key) + end + + def inverse?(attr, value, source_object) + if self.class.respond_to?(attr, value) + target_values = self.class.attr_value(attr, value) + return target_values.any?{ |target_object| self.class.equivalent_value?(target_object, source_object)} + end + + false + end + + + end + end +end \ No newline at end of file diff --git a/lib/goo/validators/validator.rb b/lib/goo/validators/validator.rb index fcb056ea..c133c33e 100644 --- a/lib/goo/validators/validator.rb +++ b/lib/goo/validators/validator.rb @@ -61,6 +61,11 @@ def property(key) key[ids.first.size..key.size].to_sym end + def respond_to?(attr, object) + object && object.respond_to?(attr) + end + + def equivalent_value?(object1, object2) if object1.respond_to?(:id) && object2.respond_to?(:id) object1.id.eql?(object2.id) From ba8bf8af538c464f3f65c5ec702fb8d830ac4b4c Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 10:24:53 +0100 Subject: [PATCH 029/110] use the class method property in distinct of --- lib/goo/validators/implementations/distinct_of.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/goo/validators/implementations/distinct_of.rb b/lib/goo/validators/implementations/distinct_of.rb index 289a9de2..2e93313b 100644 --- a/lib/goo/validators/implementations/distinct_of.rb +++ b/lib/goo/validators/implementations/distinct_of.rb @@ -15,7 +15,7 @@ class DistinctOf < ValidatorBase def initialize(inst, attr, value, key) super(inst, attr, value) - @property = property(key) + @property = self.class.property(key) end From f061e51612f07555680907a7809faa03f3566c1e Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 20:48:16 +0100 Subject: [PATCH 030/110] add proc validator tests --- test/test_validators.rb | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/test_validators.rb b/test/test_validators.rb index c01a543b..8795fccf 100644 --- a/test/test_validators.rb +++ b/test/test_validators.rb @@ -57,6 +57,29 @@ class InverseOfTestModel < Goo::Base::Resource end +class ProcValidatorsTestModel < Goo::Base::Resource + model :proc_validator_test_model, name_with: :name + attribute :name, enforce: [:unique, :equal_to_test] + attribute :last_name, enforce: [:unique, ->(inst, attr) { equal_to_test_2(inst, attr)}] + + + def self.equal_to_test_2(inst, attr) + value = inst.send(attr) + + return nil if value && value.eql?('test 2') + + [:equal_to_test_2, "#{attr} need to be equal to `test 2`"] + end + + def equal_to_test(inst, attr) + value = inst.send(attr) + + return nil if value && value.eql?('test') + + [:equal_to_test, "#{attr} need to be equal to `test`"] + end +end + class TestValidators < MiniTest::Unit::TestCase def self.before_suite @@ -385,4 +408,20 @@ def test_inverse_of_validator_list assert p1.valid? end + + + def test_proc_validators + p = ProcValidatorsTestModel.new + p.name = "hi" + p.last_name = "hi" + + refute p.valid? + assert p.errors[:name][:equal_to_test] + assert p.errors[:last_name][:equal_to_test_2] + + p.name = "test" + p.last_name = "test 2" + + assert p.valid? + end end From b7cccab30510c3b4e428be66996dbbf84b52c892 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 20:50:01 +0100 Subject: [PATCH 031/110] add instance proc validators --- lib/goo/validators/enforce.rb | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index e9efe249..8860effc 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -53,8 +53,11 @@ def enforce(inst,attr,value) type = opt.to_s.index("max_") ? :max : :min check Goo::Validators::ValueRange, inst, attr, value, type, opt.to_s else - model_range = object_type(opt) - check_object_type inst, attr, value, model_range + if object_type?(opt) + check_object_type inst, attr, value, opt + elsif instance_proc?(inst, opt) + call_proc(inst.method(opt), inst, attr) + end end end @@ -67,8 +70,16 @@ def object_type(opt) opt.respond_to?(:shape_attribute) ? opt : Goo.model_by_name(opt) end - def check_object_type(inst, attr, value, model_range) + def object_type?(opt) + opt.respond_to?(:shape_attribute) ? opt : Goo.model_by_name(opt) + end + + def instance_proc?(inst, opt) + inst.respond_to? opt + end + def check_object_type(inst, attr, value, opt) + model_range = object_type(opt) if model_range && !value.nil? check Goo::Validators::ObjectType, inst, attr, value, model_range.model_name, model_range end @@ -82,9 +93,9 @@ def enforce_by_attribute(model, attr) model.model_settings[:attributes][attr][:enforce] end - def call_proc(opt,inst, attr) + def call_proc(proc,inst, attr) # This should return an array like [:name_of_error1, "Error message 1", :name_of_error2, "Error message 2"] - errors = opt.call(inst, attr) + errors = proc.call(inst, attr) || [] errors.each_slice(2) do |e| next if e.nil? || e.compact.empty? add_error(e[0].to_sym, e[1]) From 73def7491f36ac49e945fb3480a9d20893b56f52 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 28 Feb 2023 21:11:23 +0100 Subject: [PATCH 032/110] fix call_proc validator to test if the returned values are correct --- lib/goo/validators/enforce.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index 8860effc..1a53eaf0 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -75,7 +75,7 @@ def object_type?(opt) end def instance_proc?(inst, opt) - inst.respond_to? opt + opt && (opt.is_a?(Symbol) || opt.is_a?(String)) && inst.respond_to?(opt) end def check_object_type(inst, attr, value, opt) @@ -95,7 +95,10 @@ def enforce_by_attribute(model, attr) def call_proc(proc,inst, attr) # This should return an array like [:name_of_error1, "Error message 1", :name_of_error2, "Error message 2"] - errors = proc.call(inst, attr) || [] + errors = proc.call(inst, attr) + + return unless !errors.nil? && errors.is_a?(Array) + errors.each_slice(2) do |e| next if e.nil? || e.compact.empty? add_error(e[0].to_sym, e[1]) From fe85a2bbc38af344a5088eb913288c5b93a513d1 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Wed, 1 Mar 2023 00:22:11 +0100 Subject: [PATCH 033/110] add model_with_yaml_scheme test --- test/data/yaml_scheme_model_test.yml | 11 ++++++++ test/test_dsl_settings.rb | 38 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 test/data/yaml_scheme_model_test.yml diff --git a/test/data/yaml_scheme_model_test.yml b/test/data/yaml_scheme_model_test.yml new file mode 100644 index 00000000..fd8c4921 --- /dev/null +++ b/test/data/yaml_scheme_model_test.yml @@ -0,0 +1,11 @@ +name: + label: 'Name' + description: 'Person name' + equivalents: ['test:name' , 'test2:name', 'test3:person_name'] + help: 'Put the person name as string' + example: 'John' +nationality: + label: 'Person nationality' + enforcedValues: {'fr': 'france', 'us': 'USA'} + + diff --git a/test/test_dsl_settings.rb b/test/test_dsl_settings.rb index a4de17e2..9a8f03df 100644 --- a/test/test_dsl_settings.rb +++ b/test/test_dsl_settings.rb @@ -48,6 +48,22 @@ def initialize(attributes = {}) end end + +class YamlSchemeModelTest < Goo::Base::Resource + model :yaml_scheme_model_test, name_with: :name, scheme: 'test/data/yaml_scheme_model_test.yml' + attribute :name, enforce: [ :existence, :string, :unique] + attribute :last_name, enforce: [ :existence, :string, :unique] + attribute :birth_date, enforce: [ :existence, :date_time ] + attribute :nationality, enforce: [ :existence, :string ] + attribute :created, enforce: [ DateTime ], + default: lambda { |record| DateTime.now }, + namespace: :omv + attribute :friends, enforce: [ :existence , PersonModel] + attribute :status, enforce: [ :existence, :status ], + default: lambda { |record| StatusModel.find("single") } +end + + class TestDSLSeeting < MiniTest::Unit::TestCase def initialize(*args) super(*args) @@ -171,4 +187,26 @@ def _test_attributes_enforce(model) #there are assigned objects that are not saved assert !person.valid? end + + def test_model_with_yaml_scheme + + settings = YamlSchemeModelTest.model_settings + attributes_settings = settings[:attributes] + + + assert_equal "test/data/yaml_scheme_model_test.yml", settings[:scheme] + + assert_equal 'Name', attributes_settings[:name][:label] + assert_equal 'Person name', attributes_settings[:name][:description] + assert_equal %w[test:name test2:name test3:person_name], attributes_settings[:name][:equivalents] + assert_equal 'Put the person name as string', attributes_settings[:name][:help] + assert_equal 'John', attributes_settings[:name][:example] + + + assert_equal 'Person nationality', attributes_settings[:nationality][:label] + hash = {fr: 'france', us: 'USA'} + assert_equal hash, attributes_settings[:nationality][:enforcedValues] + + end + end From 4255a098890dd8f391cb520ff96c533da37022ae Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Wed, 1 Mar 2023 00:23:05 +0100 Subject: [PATCH 034/110] implement YAMLScheme module --- lib/goo/base/settings/yaml_settings.rb | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 lib/goo/base/settings/yaml_settings.rb diff --git a/lib/goo/base/settings/yaml_settings.rb b/lib/goo/base/settings/yaml_settings.rb new file mode 100644 index 00000000..8a931b3a --- /dev/null +++ b/lib/goo/base/settings/yaml_settings.rb @@ -0,0 +1,45 @@ +require 'yaml' + +module Goo + module Base + module Settings + module YAMLScheme + attr_reader :yaml_settings + + def init_yaml_scheme_settings + scheme_file_path = @model_settings[:scheme] + @yaml_settings = read_yaml_settings_file(scheme_file_path) + end + + def attribute_yaml_settings(attr) + + return {} if yaml_settings.nil? + + yaml_settings[attr.to_sym] + end + + + + private + + def load_yaml_scheme_options(attr) + settings = attribute_settings(attr) + yaml_settings = attribute_yaml_settings(attr) + settings.merge! yaml_settings unless yaml_settings.nil? || yaml_settings.empty? + end + + def read_yaml_settings_file(scheme_file_path) + return if scheme_file_path.nil? + + yaml_contents = File.read(scheme_file_path) rescue return + + YAML.safe_load(yaml_contents, symbolize_names: true) + end + end + end + end +end + + + + From 0d467ad4ed923e3b06a74b728413b76db4a80ffc Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Wed, 1 Mar 2023 00:23:32 +0100 Subject: [PATCH 035/110] use YAMLScheme module in Settings module --- lib/goo/base/settings/settings.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/goo/base/settings/settings.rb b/lib/goo/base/settings/settings.rb index e1937396..3d343d5d 100644 --- a/lib/goo/base/settings/settings.rb +++ b/lib/goo/base/settings/settings.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/string' +require_relative 'yaml_settings' module Goo module Base @@ -12,8 +13,10 @@ module ClassMethods attr_reader :model_name attr_reader :attribute_uris + include YAMLScheme + def default_model_options - return {} + {} end def model(*args) @@ -34,7 +37,9 @@ def model(*args) @model_settings = default_model_options.merge(options || {}) - unless options.include?:name_with + init_yaml_scheme_settings + + unless options.include? :name_with raise ArgumentError, "The model `#{model_name}` definition should include the :name_with option" end Goo.add_model(@model_name,self) @@ -192,6 +197,7 @@ def attribute(*args) set_no_list_by_default(options) @model_settings[:attributes][attr_name] = options + load_yaml_scheme_options(attr_name) shape_attribute(attr_name) namespace = attribute_namespace(attr_name) namespace = namespace || @model_settings[:namespace] From a903826a9d6893a5ff5c84972b941966c35b9258 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Wed, 1 Mar 2023 14:02:28 +0100 Subject: [PATCH 036/110] use platform lang and code refacto --- lib/goo/base/resource.rb | 3 +- lib/goo/sparql/mixins/solution_lang_filter.rb | 48 ++-- lib/goo/sparql/solutions_mapper.rb | 208 ++++++++++-------- 3 files changed, 143 insertions(+), 116 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index 13c4b61a..e6981c0b 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -134,7 +134,8 @@ def missing_load_attributes def unmapped_set(attribute,value) @unmapped ||= {} - (@unmapped[attribute] ||= Set.new) << value + @unmapped[attribute] ||= Set.new + @unmapped[attribute] << value unless value.nil? end def unmmaped_to_array diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index b5254786..eb9e754c 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -15,26 +15,36 @@ def main_lang_filter(id, attr, value) [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) - m = models_by_id[id] - value = nil - is_struct = m.respond_to?(:klass) - if !values.nil? && list_attributes.include?(attr) - value = values || [] - - elsif !values.nil? - value = values.first || nil - end + def find_model_objects_by_lang(objects_by_lang, lang, model_id, predicate) + objects_by_lang[lang]&.find { |obj| obj[:id].eql?(model_id) && obj[:predicate].eql?(predicate) } + end + + def fill_models_with_other_languages(models_by_id, objects_by_lang, list_attributes, attributes,klass) + + other_platform_languages = [:EN, :FR] + + unless other_platform_languages.empty? + + models_by_id&.each do |id, model| + + attributes&.each do |attr| + + other_platform_languages.each do |lang| + + model_attribute_val = model.instance_variable_get("@#{attr}") + model_objects_by_lang = find_model_objects_by_lang(objects_by_lang, lang, id, attr) || [] + + next if model_objects_by_lang.empty? - if value - if is_struct - m[attr] = value - else - m.send("#{attr}=", value, on_load: true) + if list_attributes.include?(attr) + model_attribute_val ||= [] + if model_attribute_val.empty? + model.send("#{attr}=", model_attribute_val + model_objects_by_lang[:objects], on_load: true) + end + elsif !model_attribute_val + model.send("#{attr}=", model_objects_by_lang[:objects][0] , on_load: true) + end + end end end diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 7416a78c..e6157fcf 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -1,7 +1,6 @@ module Goo module SPARQL class SolutionMapper - BNODES_TUPLES = Struct.new(:id, :attribute) def initialize(aggregate_projections, bnode_extraction, embed_struct, @@ -19,18 +18,16 @@ def initialize(aggregate_projections, bnode_extraction, embed_struct, @variables = variables @ids = ids @klass = options[:klass] - @klass = options[:klass] @read_only = options[:read_only] @incl = options[:include] @count = options[:count] @collection = options[:collection] + @objects_by_lang = {} end - - def map_each_solutions(select) - found = Set.new + fill_models_with_platform_languages = false objects_new = {} list_attributes = Set.new(@klass.attributes(:list)) all_attributes = Set.new(@klass.attributes(:all)) @@ -66,34 +63,45 @@ def map_each_solutions(select) object = sol[:attributeObject] - #bnodes + # bnodes if bnode_id?(object, predicate) objects_new = bnode_id_tuple(id, object, objects_new, predicate) next end - lang = object_language(object) + lang = object_language(object) # if lang is nil, it means that the object is not a literal + + requested_lang = nil + + if requested_lang.nil? + requested_lang = :ES # Goo.main_languages[0] || :EN + fill_models_with_platform_languages = true + end objects, objects_new = get_value_object(id, objects_new, object, list_attributes, predicate) - add_object_to_model(id, objects, predicate, lang, :EN) + add_object_to_model(id, objects, object, predicate, lang, requested_lang) + end + + if fill_models_with_platform_languages + @lang_filter.fill_models_with_other_languages(@models_by_id, @objects_by_lang, list_attributes, @incl, @klass) end - # @lang_filter.fill_models_with_other_languages(@models_by_id, list_attributes) + init_unloaded_attributes(found, list_attributes) return @models_by_id if @bnode_extraction model_set_collection_attributes(@models_by_id, objects_new) - #remove from models_by_id elements that were not touched - @models_by_id.select! { |k, m| found.include?(k) } + # remove from models_by_id elements that were not touched + @models_by_id.select! { |k, _m| found.include?(k) } models_set_all_persistent(@models_by_id) unless @read_only - #next level of embed attributes + # next level of embed attributes include_embed_attributes(@incl_embed, objects_new) if @incl_embed && !@incl_embed.empty? - #bnodes - blank_nodes = objects_new.select { |id, obj| id.is_a?(RDF::Node) && id.anonymous? } + # bnodes + blank_nodes = objects_new.select { |id, _obj| id.is_a?(RDF::Node) && id.anonymous? } include_bnodes(blank_nodes, @models_by_id) unless blank_nodes.empty? models_unmapped_to_array(@models_by_id) if @unmapped @@ -103,8 +111,8 @@ def map_each_solutions(select) private - def get_object_language(id, object, predicate) - @lang_filter.main_lang_filter id, predicate, object + def get_object_language(id, new_value, predicate) + @lang_filter.main_lang_filter id, predicate, new_value end def init_unloaded_attributes(found, list_attributes) @@ -134,10 +142,10 @@ def init_unloaded_attributes(found, list_attributes) end def get_value_object(id, objects_new, object, list_attributes, predicate) - object = object.object if object && !(object.is_a? RDF::URI) + object = object.object if object.is_a?(RDF::Literal) range_for_v = @klass.range(predicate) - #binding.pry if v.eql?(:enrolled) - #dependent model creation + # binding.pry if v.eql?(:enrolled) + # dependent model creation if object.is_a?(RDF::URI) && (predicate != :id) && !range_for_v.nil? if objects_new.include?(object) @@ -147,7 +155,7 @@ def get_value_object(id, objects_new, object, list_attributes, predicate) object, objects_new = if !@read_only preloaded_or_new_object(object, objects_new, pre_val, predicate) else - #depedent read only + # depedent read only preloaded_or_new_struct(object, objects_new, pre_val, predicate) end else @@ -156,20 +164,15 @@ def get_value_object(id, objects_new, object, list_attributes, predicate) end if list_attributes.include?(predicate) - # To handle attr that are lists - pre = if @klass_struct - @models_by_id[id][predicate] - else - @models_by_id[id].instance_variable_get("@#{predicate}") - end - if object.nil? && pre.nil? - object = [] - elsif object.nil? && !pre.nil? - object = pre - elsif object - object = !pre ? [object] : (pre.dup << object) - object.uniq! - end + pre = @klass_struct ? @models_by_id[id][predicate] : @models_by_id[id].instance_variable_get("@#{predicate}") + + object = [] if object.nil? && pre.nil? + + object = pre if object.nil? && !pre.nil? + + object = pre.nil? ? [object] : (pre.dup << object) + object.uniq + end [object, objects_new] end @@ -178,21 +181,39 @@ def object_language(new_value) new_value.language || :no_lang if new_value.is_a?(RDF::Literal) end - def add_object_to_model(id, objects, predicate, language, requested_lang = nil) + def language_match?(language, requested_lang = nil) + !language.nil? && (language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil?) + end + + def add_object_to_model(id, objects, current_obj, predicate, language, requested_lang = nil) if @models_by_id[id].respond_to?(:klass) @models_by_id[id][predicate] = objects unless objects.nil? && !@models_by_id[id][predicate].nil? elsif !@models_by_id[id].class.handler?(predicate) && !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && predicate != :id - if language.nil? - @models_by_id[id].send("#{predicate}=", objects, on_load: true) - else - if language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil? - @models_by_id[id].send("#{predicate}=", objects, on_load: true) - end - end - + if language.nil? # the object is a non-literal + return @models_by_id[id].send("#{predicate}=", objects, on_load: true) + end + + if language_match?(language, requested_lang) # the object is a literal and the language matches + return @models_by_id[id].send("#{predicate}=", objects, on_load: true) + end + + + # the object is a literal and the language does not match , so we store it in a hash + @objects_by_lang[language] ||= [] + item = @objects_by_lang[language].find { |obj| obj[:id] == id && obj[:predicate] == predicate } + + if item + # If an item with the matching id exists, update its attributes + item[:objects] << current_obj.object + item[:predicate] = predicate + else + # If an item with the matching id does not exist, add the new item to the array + @objects_by_lang[language] << { id: id, objects: [current_obj.object], predicate: predicate } + end + end end @@ -222,7 +243,7 @@ def preloaded_or_new_struct(object, objects_new, pre_val, predicate) def preloaded_value(id, predicate) if !@read_only @models_by_id[id].instance_variable_get("@#{predicate}") - + else @models_by_id[id][predicate] end @@ -239,9 +260,7 @@ def bnode_id?(object, predicate) def bnode_id_tuple(id, object, objects_new, predicate) range = @klass.range(predicate) - if range.respond_to?(:new) - objects_new[object] = BNODES_TUPLES.new(id, predicate) - end + objects_new[object] = BNODES_TUPLES.new(id, predicate) if range.respond_to?(:new) objects_new end @@ -255,11 +274,13 @@ def create_model(id) @models_by_id[id] = create_class_model(id, @klass, @klass_struct) unless @models_by_id.include?(id) end - def model_set_unmapped(id, predicate, value) + def model_set_unmapped(id, predicate, value, _requested_lang = nil) + value = nil if value.is_a?(RDF::Literal) && !language_match?(value.language, nil) - if @models_by_id[id].respond_to? :klass #struct + if @models_by_id[id].respond_to? :klass # struct @models_by_id[id][:unmapped] ||= {} - (@models_by_id[id][:unmapped][predicate] ||= []) << value + @models_by_id[id][:unmapped][predicate] ||= [] + @models_by_id[id][:unmapped][predicate] << value unless value.nil? else @models_by_id[id].unmapped_set(predicate, value) end @@ -270,6 +291,7 @@ def create_struct(bnode_extraction, models_by_id, sol, variables) struct = @klass.range(bnode_extraction).new variables.each do |v| next if v == :id + svalue = sol[v] struct[v] = svalue.is_a?(RDF::Node) ? svalue : svalue.object end @@ -290,25 +312,25 @@ def create_class_model(id, klass, klass_struct) end def models_unmapped_to_array(models_by_id) - models_by_id.each do |idm, m| + models_by_id.each do |_idm, m| m.unmmaped_to_array end end def include_bnodes(bnodes, models_by_id) - #group by attribute - attrs = bnodes.map { |x, y| y.attribute }.uniq + # group by attribute + attrs = bnodes.map { |_x, y| y.attribute }.uniq attrs.each do |attr| struct = @klass.range(attr) - #bnodes that are in a range of goo ground models - #for example parents and children in LD class models - #we skip this cases for the moment + # bnodes that are in a range of goo ground models + # for example parents and children in LD class models + # we skip this cases for the moment next if struct.respond_to?(:model_name) bnode_attrs = struct.new.to_h.keys - ids = bnodes.select { |x, y| y.attribute == attr }.map { |x, y| y.id } - @klass.where.models(models_by_id.select { |x, y| ids.include?(x) }.values) + ids = bnodes.select { |_x, y| y.attribute == attr }.map { |_x, y| y.id } + @klass.where.models(models_by_id.select { |x, _y| ids.include?(x) }.values) .in(@collection) .include(bnode: { attr => bnode_attrs }).all end @@ -316,44 +338,46 @@ def include_bnodes(bnodes, models_by_id) def include_embed_attributes(incl_embed, objects_new) incl_embed.each do |attr, next_attrs| - #anything to join ? + # anything to join ? attr_range = @klass.range(attr) next if attr_range.nil? - range_objs = objects_new.select { |id, obj| + + range_objs = objects_new.select do |_id, obj| obj.instance_of?(attr_range) || (obj.respond_to?(:klass) && obj[:klass] == attr_range) - }.values - unless range_objs.empty? - range_objs.uniq! - query = attr_range.where().models(range_objs).in(@collection).include(*next_attrs) - query = query.read_only if @read_only - query.all - end + end.values + next if range_objs.empty? + + range_objs.uniq! + query = attr_range.where.models(range_objs).in(@collection).include(*next_attrs) + query = query.read_only if @read_only + query.all end end def models_set_all_persistent(models_by_id) return unless @ids - models_by_id.each do |k, m| + + models_by_id.each do |_k, m| m.persistent = true end end def model_set_collection_attributes(models_by_id, objects_new) collection_value = get_collection_value - if collection_value - collection_attribute = @klass.collection_opts - models_by_id.each do |id, m| - m.send("#{collection_attribute}=", collection_value) - end - objects_new.each do |id, obj_new| - if obj_new.respond_to?(:klass) - collection_attribute = obj_new[:klass].collection_opts - obj_new[collection_attribute] = collection_value - elsif obj_new.class.respond_to?(:collection_opts) && - obj_new.class.collection_opts.instance_of?(Symbol) - collection_attribute = obj_new.class.collection_opts - obj_new.send("#{collection_attribute}=", collection_value) - end + return unless collection_value + + collection_attribute = @klass.collection_opts + models_by_id.each do |_id, m| + m.send("#{collection_attribute}=", collection_value) + end + objects_new.each do |_id, obj_new| + if obj_new.respond_to?(:klass) + collection_attribute = obj_new[:klass].collection_opts + obj_new[collection_attribute] = collection_value + elsif obj_new.class.respond_to?(:collection_opts) && + obj_new.class.collection_opts.instance_of?(Symbol) + collection_attribute = obj_new.class.collection_opts + obj_new.send("#{collection_attribute}=", collection_value) end end end @@ -361,17 +385,12 @@ def model_set_collection_attributes(models_by_id, objects_new) def get_collection_value 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 object_to_array(id, klass_struct, models_by_id, object, predicate) pre = if klass_struct models_by_id[id][predicate] @@ -390,7 +409,6 @@ def object_to_array(id, klass_struct, models_by_id, object, predicate) end def dependent_model_creation(embed_struct, id, models_by_id, object, objects_new, v, options) - read_only = options[:read_only] if object.is_a?(RDF::URI) && v != :id range_for_v = @klass.range(v) @@ -409,13 +427,12 @@ def dependent_model_creation(embed_struct, id, models_by_id, object, objects_new end def get_object_from_range(pre_val, embed_struct, object, objects_new, predicate) - range_for_v = @klass.range(predicate) if !@read_only object = pre_val || @klass.range_object(predicate, object) objects_new[object.id] = object else - #depedent read only + # depedent read only struct = pre_val || embed_struct[predicate].new struct.id = object struct.klass = range_for_v @@ -428,8 +445,8 @@ def get_object_from_range(pre_val, embed_struct, object, objects_new, predicate) def get_pre_val(id, models_by_id, object, predicate) pre_val = nil if models_by_id[id] && - ((models_by_id[id].respond_to?(:klass) && models_by_id[id]) || - models_by_id[id].loaded_attributes.include?(predicate)) + ((models_by_id[id].respond_to?(:klass) && models_by_id[id]) || + models_by_id[id].loaded_attributes.include?(predicate)) pre_val = if !@read_only models_by_id[id].instance_variable_get("@#{predicate}") else @@ -448,7 +465,7 @@ def add_unmapped_to_model(sol) id = sol[:id] value = sol[:attributeObject] - model_set_unmapped(id, @properties_to_include[predicate][:uri], value) + model_set_unmapped(id, @properties_to_include[predicate][:uri], value, :ES) end def add_aggregations_to_model(sol) @@ -466,4 +483,3 @@ def add_aggregations_to_model(sol) end end end - From ef7f5cf438a872ed456b7d86b5d0936b0a0e8af6 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Wed, 1 Mar 2023 14:49:07 +0100 Subject: [PATCH 037/110] filter by lang in properties --- lib/goo/sparql/solutions_mapper.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index e6157fcf..8acbdeb7 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -274,8 +274,8 @@ def create_model(id) @models_by_id[id] = create_class_model(id, @klass, @klass_struct) unless @models_by_id.include?(id) end - def model_set_unmapped(id, predicate, value, _requested_lang = nil) - value = nil if value.is_a?(RDF::Literal) && !language_match?(value.language, nil) + def model_set_unmapped(id, predicate, value, requested_lang = nil) + value = nil if value.is_a?(RDF::Literal) && !language_match?(value.language, requested_lang) if @models_by_id[id].respond_to? :klass # struct @models_by_id[id][:unmapped] ||= {} @@ -465,7 +465,9 @@ def add_unmapped_to_model(sol) id = sol[:id] value = sol[:attributeObject] - model_set_unmapped(id, @properties_to_include[predicate][:uri], value, :ES) + requested_lang = :FR + + model_set_unmapped(id, @properties_to_include[predicate][:uri], value, requested_lang) end def add_aggregations_to_model(sol) From 562065bff482f980a76a5f0e40e2b6091fe4c089 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Wed, 1 Mar 2023 16:25:17 +0100 Subject: [PATCH 038/110] do some refactoring --- lib/goo/sparql/mixins/solution_lang_filter.rb | 2 +- lib/goo/sparql/solutions_mapper.rb | 41 ++++++++----------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index eb9e754c..2a20a5bb 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -21,7 +21,7 @@ def find_model_objects_by_lang(objects_by_lang, lang, model_id, predicate) def fill_models_with_other_languages(models_by_id, objects_by_lang, list_attributes, attributes,klass) - other_platform_languages = [:EN, :FR] + other_platform_languages = Goo.main_languages[1..-1] unless other_platform_languages.empty? diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 8acbdeb7..f8ef20bc 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -48,7 +48,7 @@ def map_each_solutions(select) end if @unmapped - add_unmapped_to_model(sol) + add_unmapped_to_model(sol, requested_lang) next end @@ -74,7 +74,7 @@ def map_each_solutions(select) requested_lang = nil if requested_lang.nil? - requested_lang = :ES # Goo.main_languages[0] || :EN + requested_lang = Goo.main_languages[0] || :EN fill_models_with_platform_languages = true end @@ -111,10 +111,6 @@ def map_each_solutions(select) private - def get_object_language(id, new_value, predicate) - @lang_filter.main_lang_filter id, predicate, new_value - end - def init_unloaded_attributes(found, list_attributes) return if @incl.nil? @@ -142,7 +138,7 @@ def init_unloaded_attributes(found, list_attributes) end def get_value_object(id, objects_new, object, list_attributes, predicate) - object = object.object if object.is_a?(RDF::Literal) + object = object.object if object && !(object.is_a? RDF::URI) range_for_v = @klass.range(predicate) # binding.pry if v.eql?(:enrolled) # dependent model creation @@ -200,20 +196,21 @@ def add_object_to_model(id, objects, current_obj, predicate, language, requested return @models_by_id[id].send("#{predicate}=", objects, on_load: true) end + store_objects_by_lang(id, predicate, current_obj, language) + end + end - # the object is a literal and the language does not match , so we store it in a hash - @objects_by_lang[language] ||= [] - item = @objects_by_lang[language].find { |obj| obj[:id] == id && obj[:predicate] == predicate } - - if item - # If an item with the matching id exists, update its attributes - item[:objects] << current_obj.object - item[:predicate] = predicate - else - # If an item with the matching id does not exist, add the new item to the array - @objects_by_lang[language] << { id: id, objects: [current_obj.object], predicate: predicate } - end - + def store_objects_by_lang(id, predicate, object, language) + @objects_by_lang[language] ||= [] + item = @objects_by_lang[language].find { |obj| obj[:id] == id && obj[:predicate] == predicate } + + if item + # If an item with the matching id exists, update its attributes + item[:objects] << object.object + item[:predicate] = predicate + else + # If an item with the matching id does not exist, add the new item to the array + @objects_by_lang[language] << { id: id, objects: [object.object], predicate: predicate } end end @@ -458,15 +455,13 @@ def get_pre_val(id, models_by_id, object, predicate) pre_val end - def add_unmapped_to_model(sol) + def add_unmapped_to_model(sol, requested_lang) predicate = sol[:attributeProperty].to_s.to_sym return unless @properties_to_include[predicate] id = sol[:id] value = sol[:attributeObject] - requested_lang = :FR - model_set_unmapped(id, @properties_to_include[predicate][:uri], value, requested_lang) end From 6f9e73ddda35e0555c0fda1d4393e7b1c38d1bdd Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 3 Mar 2023 09:01:39 +0100 Subject: [PATCH 039/110] add unmapped_get to goo resources --- lib/goo/base/resource.rb | 926 ++++++++++++++++++++------------------- 1 file changed, 465 insertions(+), 461 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index e6981c0b..8f7da133 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -1,461 +1,465 @@ -require 'cgi' -require_relative "settings/settings" - -module Goo - module Base - AGGREGATE_VALUE = Struct.new(:attribute,:aggregate,:value) - - class IDGenerationError < StandardError; end; - - class Resource - include Goo::Base::Settings - include Goo::Search - - attr_reader :loaded_attributes - attr_reader :modified_attributes - attr_reader :errors - attr_reader :aggregates - attr_reader :unmapped - - attr_reader :id - - def initialize(*args) - @loaded_attributes = Set.new - @modified_attributes = Set.new - @previous_values = nil - @persistent = false - @aggregates = nil - @unmapped = nil - - attributes = args[0] || {} - opt_symbols = Goo.resource_options - attributes.each do |attr,value| - next if opt_symbols.include?(attr) - self.send("#{attr}=",value) - end - - @id = attributes.delete :id - end - - def valid? - validation_errors = {} - self.class.attributes.each do |attr| - inst_value = self.instance_variable_get("@#{attr}") - attr_errors = Goo::Validators::Enforce.enforce(self,attr,inst_value) - validation_errors[attr] = attr_errors unless attr_errors.nil? - end - - if !@persistent && validation_errors.length == 0 - uattr = self.class.name_with.instance_of?(Symbol) ? self.class.name_with : :proc_naming - if validation_errors[uattr].nil? - begin - if self.exist?(from_valid=true) - validation_errors[uattr] = validation_errors[uattr] || {} - validation_errors[uattr][:duplicate] = - "There is already a persistent resource with id `#{@id.to_s}`" - end - rescue ArgumentError => e - (validation_errors[uattr] ||= {})[:existence] = e.message - end - if self.class.name_with == :id && @id.nil? - (validation_errors[:id] ||= {})[:existence] = ":id must be set if configured in name_with" - end - end - end - - @errors = validation_errors - return @errors.length == 0 - end - - def id=(new_id) - 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 - - def id - if @id.nil? - raise IDGenerationError, ":id must be set if configured in name_with" if self.class.name_with == :id - custom_name = self.class.name_with - if custom_name.instance_of?(Symbol) - @id = id_from_attribute() - elsif custom_name - begin - @id = custom_name.call(self) - rescue => e - raise IDGenerationError, "Problem with custom id generation: #{e.message}" - end - else - raise IDGenerationError, "custom_name is nil. settings for this model are incorrect." - end - end - return @id - end - - def persistent? - return @persistent - end - - def persistent=(val) - @persistent=val - end - - def modified? - return modified_attributes.length > 0 - end - - def exist?(from_valid=false) - #generate id with proc - begin - id() unless self.class.name_with.kind_of?(Symbol) - rescue IDGenerationError - end - - _id = @id - if _id.nil? && !from_valid && self.class.name_with.is_a?(Symbol) - begin - _id = id_from_attribute() - rescue IDGenerationError - end - end - return false unless _id - return Goo::SPARQL::Queries.model_exist(self,id=_id) - end - - def fully_loaded? - #every declared attributed has been loaded - return @loaded_attributes == Set.new(self.class.attributes) - end - - def missing_load_attributes - #every declared attributed has been loaded - return Set.new(self.class.attributes) - @loaded_attributes - end - - def unmapped_set(attribute,value) - @unmapped ||= {} - @unmapped[attribute] ||= Set.new - @unmapped[attribute] << value unless value.nil? - end - - def unmmaped_to_array - cpy = {} - @unmapped.each do |attr,v| - cpy[attr] = v.to_a - end - @unmapped = cpy - end - - def delete(*args) - if self.kind_of?(Goo::Base::Enum) - 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 - - if !fully_loaded? - missing = missing_load_attributes - options_load = { models: [ self ], klass: self.class, :include => missing } - options_load[:collection] = self.collection if self.class.collection_opts - Goo::SPARQL::Queries.model_load(options_load) - end - - graph_delete,bnode_delete = Goo::SPARQL::Triples.model_delete_triples(self) - - begin - bnode_delete.each do |attr,delete_query| - Goo.sparql_update_client.update(delete_query) - end - Goo.sparql_update_client.delete_data(graph_delete, graph: self.graph) - rescue Exception => e - raise e - end - @persistent = false - @modified = true - self.class.load_inmutable_instances if self.class.inmutable? && self.class.inm_instances - return nil - end - - def bring(*opts) - opts.each do |k| - if k.kind_of?(Hash) - k.each do |k2,v| - raise ArgumentError, "Unable to bring a method based attr #{k2}" if self.class.handler?(k2) - self.instance_variable_set("@#{k2}",nil) - end - else - raise ArgumentError, "Unable to bring a method based attr #{k}" if self.class.handler?(k) - self.instance_variable_set("@#{k}",nil) - end - end - query = self.class.where.models([self]).include(*opts) - if self.class.collection_opts.instance_of?(Symbol) - collection_attribute = self.class.collection_opts - query.in(self.send("#{collection_attribute}")) - end - query.all - self - end - - def graph - opts = self.class.collection_opts - return self.class.uri_type if opts.nil? - col = collection - if col.is_a?Array - if col.length == 1 - col = col.first - else - raise Exception, "collection in save only can be len=1" - end - end - return col ? col.id : nil - end - - - - def collection - opts = self.class.collection_opts - if opts.instance_of?(Symbol) - if self.class.attributes.include?(opts) - value = self.send("#{opts}") - raise ArgumentError, "Collection `#{opts}` is nil" if value.nil? - return value - else - raise ArgumentError, "Collection `#{opts}` is not an attribute" - end - end - end - - def add_aggregate(attribute,aggregate,value) - (@aggregates ||= []) << AGGREGATE_VALUE.new(attribute,aggregate,value) - end - - def save(*opts) - - if self.kind_of?(Goo::Base::Enum) - 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 - if opts.first.is_a?(Hash) && opts.first[:batch] && opts.first[:batch].is_a?(File) - batch_file = opts.first[:batch] - end - end - - if !batch_file - return self if not modified? - raise Goo::Base::NotValidException, "Object is not valid. Check errors." unless valid? - end - - graph_insert, graph_delete = Goo::SPARQL::Triples.model_update_triples(self) - graph = self.graph() - if graph_delete and graph_delete.size > 0 - begin - Goo.sparql_update_client.delete_data(graph_delete, graph: graph) - rescue Exception => e - raise e - end - end - if graph_insert and graph_insert.size > 0 - begin - if batch_file - lines = [] - graph_insert.each do |t| - lines << [t.subject.to_ntriples, - t.predicate.to_ntriples, - t.object.to_ntriples, - graph.to_ntriples, - ".\n" ].join(' ') - end - batch_file.write(lines.join("")) - batch_file.flush() - else - Goo.sparql_update_client.insert_data(graph_insert, graph: graph) - end - rescue Exception => e - raise e - end - end - - #after save all attributes where loaded - @loaded_attributes = Set.new(self.class.attributes).union(@loaded_attributes) - - @modified_attributes = Set.new - @persistent = true - self.class.load_inmutable_instances if self.class.inmutable? && self.class.inm_instances - return self - end - - def bring?(attr) - return @persistent && - !@loaded_attributes.include?(attr) && - !@modified_attributes.include?(attr) - end - - def bring_remaining - to_bring = [] - self.class.attributes.each do |attr| - to_bring << attr if self.bring?(attr) - end - self.bring(*to_bring) - end - - def previous_values - return @previous_values - end - - def to_hash - attr_hash = {} - self.class.attributes.each do |attr| - v = self.instance_variable_get("@#{attr}") - attr_hash[attr]=v unless v.nil? - end - if @unmapped - all_attr_uris = Set.new - self.class.attributes.each do |attr| - if self.class.collection_opts - all_attr_uris << self.class.attribute_uri(attr,self.collection) - else - all_attr_uris << self.class.attribute_uri(attr) - end - end - @unmapped.each do |attr,values| - attr_hash[attr] = values.map { |v| v.to_s } unless all_attr_uris.include?(attr) - end - end - attr_hash[:id] = @id - return attr_hash - end - - ### - # Class level methods - # ## - - def self.range_object(attr,id) - klass_range = self.range(attr) - return nil if klass_range.nil? - range_object = klass_range.new - range_object.id = id - range_object.persistent = true - return range_object - 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 - 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 - 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]]) - return w - end - options_load = { ids: [id], klass: self }.merge(options[-1] || {}) - options_load[:find] = true - where = Goo::Base::Where.new(self) - where.where_options_load = options_load - return where - end - - def self.in(collection) - return where.in(collection) - end - - def self.where(*match) - return Goo::Base::Where.new(self,*match) - end - - def self.all - return self.where.all - end - - protected - def id_from_attribute() - uattr = self.class.name_with - uvalue = self.send("#{uattr}") - return self.class.id_from_unique_attribute(uattr,uvalue) - end - - end - - end -end +require 'cgi' +require_relative "settings/settings" + +module Goo + module Base + AGGREGATE_VALUE = Struct.new(:attribute,:aggregate,:value) + + class IDGenerationError < StandardError; end; + + class Resource + include Goo::Base::Settings + include Goo::Search + + attr_reader :loaded_attributes + attr_reader :modified_attributes + attr_reader :errors + attr_reader :aggregates + attr_reader :unmapped + + attr_reader :id + + def initialize(*args) + @loaded_attributes = Set.new + @modified_attributes = Set.new + @previous_values = nil + @persistent = false + @aggregates = nil + @unmapped = nil + + attributes = args[0] || {} + opt_symbols = Goo.resource_options + attributes.each do |attr,value| + next if opt_symbols.include?(attr) + self.send("#{attr}=",value) + end + + @id = attributes.delete :id + end + + def valid? + validation_errors = {} + self.class.attributes.each do |attr| + inst_value = self.instance_variable_get("@#{attr}") + attr_errors = Goo::Validators::Enforce.enforce(self,attr,inst_value) + validation_errors[attr] = attr_errors unless attr_errors.nil? + end + + if !@persistent && validation_errors.length == 0 + uattr = self.class.name_with.instance_of?(Symbol) ? self.class.name_with : :proc_naming + if validation_errors[uattr].nil? + begin + if self.exist?(from_valid=true) + validation_errors[uattr] = validation_errors[uattr] || {} + validation_errors[uattr][:duplicate] = + "There is already a persistent resource with id `#{@id.to_s}`" + end + rescue ArgumentError => e + (validation_errors[uattr] ||= {})[:existence] = e.message + end + if self.class.name_with == :id && @id.nil? + (validation_errors[:id] ||= {})[:existence] = ":id must be set if configured in name_with" + end + end + end + + @errors = validation_errors + return @errors.length == 0 + end + + def id=(new_id) + 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 + + def id + if @id.nil? + raise IDGenerationError, ":id must be set if configured in name_with" if self.class.name_with == :id + custom_name = self.class.name_with + if custom_name.instance_of?(Symbol) + @id = id_from_attribute() + elsif custom_name + begin + @id = custom_name.call(self) + rescue => e + raise IDGenerationError, "Problem with custom id generation: #{e.message}" + end + else + raise IDGenerationError, "custom_name is nil. settings for this model are incorrect." + end + end + return @id + end + + def persistent? + return @persistent + end + + def persistent=(val) + @persistent=val + end + + def modified? + return modified_attributes.length > 0 + end + + def exist?(from_valid=false) + #generate id with proc + begin + id() unless self.class.name_with.kind_of?(Symbol) + rescue IDGenerationError + end + + _id = @id + if _id.nil? && !from_valid && self.class.name_with.is_a?(Symbol) + begin + _id = id_from_attribute() + rescue IDGenerationError + end + end + return false unless _id + return Goo::SPARQL::Queries.model_exist(self,id=_id) + end + + def fully_loaded? + #every declared attributed has been loaded + return @loaded_attributes == Set.new(self.class.attributes) + end + + def missing_load_attributes + #every declared attributed has been loaded + return Set.new(self.class.attributes) - @loaded_attributes + end + + def unmapped_set(attribute,value) + @unmapped ||= {} + @unmapped[attribute] ||= Set.new + @unmapped[attribute] << value unless value.nil? + end + + def unmapped_get(attribute) + @unmapped[attribute] + end + + def unmmaped_to_array + cpy = {} + @unmapped.each do |attr,v| + cpy[attr] = v.to_a + end + @unmapped = cpy + end + + def delete(*args) + if self.kind_of?(Goo::Base::Enum) + 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 + + if !fully_loaded? + missing = missing_load_attributes + options_load = { models: [ self ], klass: self.class, :include => missing } + options_load[:collection] = self.collection if self.class.collection_opts + Goo::SPARQL::Queries.model_load(options_load) + end + + graph_delete,bnode_delete = Goo::SPARQL::Triples.model_delete_triples(self) + + begin + bnode_delete.each do |attr,delete_query| + Goo.sparql_update_client.update(delete_query) + end + Goo.sparql_update_client.delete_data(graph_delete, graph: self.graph) + rescue Exception => e + raise e + end + @persistent = false + @modified = true + self.class.load_inmutable_instances if self.class.inmutable? && self.class.inm_instances + return nil + end + + def bring(*opts) + opts.each do |k| + if k.kind_of?(Hash) + k.each do |k2,v| + raise ArgumentError, "Unable to bring a method based attr #{k2}" if self.class.handler?(k2) + self.instance_variable_set("@#{k2}",nil) + end + else + raise ArgumentError, "Unable to bring a method based attr #{k}" if self.class.handler?(k) + self.instance_variable_set("@#{k}",nil) + end + end + query = self.class.where.models([self]).include(*opts) + if self.class.collection_opts.instance_of?(Symbol) + collection_attribute = self.class.collection_opts + query.in(self.send("#{collection_attribute}")) + end + query.all + self + end + + def graph + opts = self.class.collection_opts + return self.class.uri_type if opts.nil? + col = collection + if col.is_a?Array + if col.length == 1 + col = col.first + else + raise Exception, "collection in save only can be len=1" + end + end + return col ? col.id : nil + end + + + + def collection + opts = self.class.collection_opts + if opts.instance_of?(Symbol) + if self.class.attributes.include?(opts) + value = self.send("#{opts}") + raise ArgumentError, "Collection `#{opts}` is nil" if value.nil? + return value + else + raise ArgumentError, "Collection `#{opts}` is not an attribute" + end + end + end + + def add_aggregate(attribute,aggregate,value) + (@aggregates ||= []) << AGGREGATE_VALUE.new(attribute,aggregate,value) + end + + def save(*opts) + + if self.kind_of?(Goo::Base::Enum) + 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 + if opts.first.is_a?(Hash) && opts.first[:batch] && opts.first[:batch].is_a?(File) + batch_file = opts.first[:batch] + end + end + + if !batch_file + return self if not modified? + raise Goo::Base::NotValidException, "Object is not valid. Check errors." unless valid? + end + + graph_insert, graph_delete = Goo::SPARQL::Triples.model_update_triples(self) + graph = self.graph() + if graph_delete and graph_delete.size > 0 + begin + Goo.sparql_update_client.delete_data(graph_delete, graph: graph) + rescue Exception => e + raise e + end + end + if graph_insert and graph_insert.size > 0 + begin + if batch_file + lines = [] + graph_insert.each do |t| + lines << [t.subject.to_ntriples, + t.predicate.to_ntriples, + t.object.to_ntriples, + graph.to_ntriples, + ".\n" ].join(' ') + end + batch_file.write(lines.join("")) + batch_file.flush() + else + Goo.sparql_update_client.insert_data(graph_insert, graph: graph) + end + rescue Exception => e + raise e + end + end + + #after save all attributes where loaded + @loaded_attributes = Set.new(self.class.attributes).union(@loaded_attributes) + + @modified_attributes = Set.new + @persistent = true + self.class.load_inmutable_instances if self.class.inmutable? && self.class.inm_instances + return self + end + + def bring?(attr) + return @persistent && + !@loaded_attributes.include?(attr) && + !@modified_attributes.include?(attr) + end + + def bring_remaining + to_bring = [] + self.class.attributes.each do |attr| + to_bring << attr if self.bring?(attr) + end + self.bring(*to_bring) + end + + def previous_values + return @previous_values + end + + def to_hash + attr_hash = {} + self.class.attributes.each do |attr| + v = self.instance_variable_get("@#{attr}") + attr_hash[attr]=v unless v.nil? + end + if @unmapped + all_attr_uris = Set.new + self.class.attributes.each do |attr| + if self.class.collection_opts + all_attr_uris << self.class.attribute_uri(attr,self.collection) + else + all_attr_uris << self.class.attribute_uri(attr) + end + end + @unmapped.each do |attr,values| + attr_hash[attr] = values.map { |v| v.to_s } unless all_attr_uris.include?(attr) + end + end + attr_hash[:id] = @id + return attr_hash + end + + ### + # Class level methods + # ## + + def self.range_object(attr,id) + klass_range = self.range(attr) + return nil if klass_range.nil? + range_object = klass_range.new + range_object.id = id + range_object.persistent = true + return range_object + 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 + 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 + 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]]) + return w + end + options_load = { ids: [id], klass: self }.merge(options[-1] || {}) + options_load[:find] = true + where = Goo::Base::Where.new(self) + where.where_options_load = options_load + return where + end + + def self.in(collection) + return where.in(collection) + end + + def self.where(*match) + return Goo::Base::Where.new(self,*match) + end + + def self.all + return self.where.all + end + + protected + def id_from_attribute() + uattr = self.class.name_with + uvalue = self.send("#{uattr}") + return self.class.id_from_unique_attribute(uattr,uvalue) + end + + end + + end +end From 9f91124afaabb426c62e5b1a5a7c4375c78e61e9 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 3 Mar 2023 09:05:18 +0100 Subject: [PATCH 040/110] update lang filter module to support requested_lang and portal_lang --- lib/goo/sparql/mixins/solution_lang_filter.rb | 226 +++++++++--------- lib/goo/sparql/solutions_mapper.rb | 36 +-- 2 files changed, 119 insertions(+), 143 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 2a20a5bb..b6333593 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -1,109 +1,117 @@ -module Goo - module SPARQL - module Solution - class LanguageFilter - - def initialize - @other_languages_values = {} - end - - attr_reader :other_languages_values - - def main_lang_filter(id, attr, value) - index, value = lang_index value - save_other_lang_val(id, attr, index, value) unless index.nil? ||index.eql?(:no_lang) - [index, value] - end - - def find_model_objects_by_lang(objects_by_lang, lang, model_id, predicate) - objects_by_lang[lang]&.find { |obj| obj[:id].eql?(model_id) && obj[:predicate].eql?(predicate) } - end - - def fill_models_with_other_languages(models_by_id, objects_by_lang, list_attributes, attributes,klass) - - other_platform_languages = Goo.main_languages[1..-1] - - unless other_platform_languages.empty? - - models_by_id&.each do |id, model| - - attributes&.each do |attr| - - other_platform_languages.each do |lang| - - model_attribute_val = model.instance_variable_get("@#{attr}") - model_objects_by_lang = find_model_objects_by_lang(objects_by_lang, lang, id, attr) || [] - - next if model_objects_by_lang.empty? - - if list_attributes.include?(attr) - model_attribute_val ||= [] - if model_attribute_val.empty? - model.send("#{attr}=", model_attribute_val + model_objects_by_lang[:objects], on_load: true) - end - elsif !model_attribute_val - model.send("#{attr}=", model_objects_by_lang[:objects][0] , on_load: true) - end - - end - 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) - return [nil, object] unless object.is_a?(RDF::Literal) - - lang = object.language - - if lang.nil? - [:no_lang, object] - else - index = Goo.language_includes(lang) - index = index ? index.to_s.to_sym : :not_matched - [index, object] - 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 +module Goo + module SPARQL + module Solution + class LanguageFilter + + attr_reader :requested_lang, :unmapped, :objects_by_lang + + def initialize(requested_lang: nil, unmapped: false, list_attributes: []) + @list_attributes = list_attributes + @objects_by_lang = {} + @unmapped = unmapped + @requested_lang = requested_lang + + end + + def object_language(new_value) + new_value.language || :no_lang if new_value.is_a?(RDF::Literal) + end + + def language_match?(language) + !language.nil? && (language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil?) + end + + def store_objects_by_lang(id, predicate, object, language) + # store objects in this format: [id][predicate][language] = [objects] + + objects_by_lang[id] ||= {} + objects_by_lang[id][predicate] ||= {} + objects_by_lang[id][predicate][language] ||= [] + + objects_by_lang[id][predicate][language] << object.object + + end + + def fill_models_with_other_languages(models_by_id) + + other_platform_languages = Goo.main_languages[1..] || [] + + objects_by_lang.each do |id, predicates| + model = models_by_id[id] + predicates.each do |predicate, languages| + model_attribute_val = get_model_attribute_value(model, predicate) + next unless model_attribute_val.nil? || model_attribute_val.empty? + + other_platform_languages.each do |platform_language| + if languages[platform_language] + save_value_to_model(model, languages[platform_language], predicate, unmapped) + break + end + end + model_attribute_val = get_model_attribute_value(model, predicate) + if model_attribute_val.nil? || model_attribute_val.empty? + save_value_to_model(model, languages.values.flatten.uniq, predicate, unmapped) + end + end + end + + end + + + + def model_set_unmapped(model, predicate, value, language) + if language.nil? || language_match?(language) + return add_unmapped_to_model(model, predicate, value) + end + + store_objects_by_lang(model.id, predicate, value, language) + end + + + private + + def get_model_attribute_value(model, predicate) + if unmapped + unmapped_get(model, predicate) + else + model.instance_variable_get("@#{predicate}") + end + end + + + def add_unmapped_to_model(model, predicate, value) + if model.respond_to? :klass # struct + model[:unmapped] ||= {} + model[:unmapped][predicate] ||= [] + model[:unmapped][predicate] << value unless value.nil? + else + model.unmapped_set(predicate, value) + end + end + + def save_value_to_model(model, value, predicate, unmapped) + if unmapped + add_unmapped_to_model(model, predicate, value) + else + value = Array(value).min unless list_attributes?(predicate) + model.send("#{predicate}=", value, on_load: true) + end + end + + def unmapped_get(model, predicate) + if model && model.respond_to?(:klass) # struct + model[:unmapped]&.dig(predicate) + else + model.unmapped_get(predicate) + end + + end + + def list_attributes?(predicate) + @list_attributes.include?(predicate) + end + + end + end + end +end diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index f8ef20bc..01ade699 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -173,14 +173,7 @@ def get_value_object(id, objects_new, object, list_attributes, predicate) [object, objects_new] end - def object_language(new_value) - new_value.language || :no_lang if new_value.is_a?(RDF::Literal) - end - - def language_match?(language, requested_lang = nil) - !language.nil? && (language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil?) - end - + def add_object_to_model(id, objects, current_obj, predicate, language) def add_object_to_model(id, objects, current_obj, predicate, language, requested_lang = nil) if @models_by_id[id].respond_to?(:klass) @models_by_id[id][predicate] = objects unless objects.nil? && !@models_by_id[id][predicate].nil? @@ -197,21 +190,7 @@ def add_object_to_model(id, objects, current_obj, predicate, language, requested end store_objects_by_lang(id, predicate, current_obj, language) - end - end - - def store_objects_by_lang(id, predicate, object, language) - @objects_by_lang[language] ||= [] - item = @objects_by_lang[language].find { |obj| obj[:id] == id && obj[:predicate] == predicate } - - if item - # If an item with the matching id exists, update its attributes - item[:objects] << object.object - item[:predicate] = predicate - else - # If an item with the matching id does not exist, add the new item to the array - @objects_by_lang[language] << { id: id, objects: [object.object], predicate: predicate } - end + end end def get_preload_value(id, object, predicate) @@ -271,17 +250,6 @@ def create_model(id) @models_by_id[id] = create_class_model(id, @klass, @klass_struct) unless @models_by_id.include?(id) end - def model_set_unmapped(id, predicate, value, requested_lang = nil) - value = nil if value.is_a?(RDF::Literal) && !language_match?(value.language, requested_lang) - - if @models_by_id[id].respond_to? :klass # struct - @models_by_id[id][:unmapped] ||= {} - @models_by_id[id][:unmapped][predicate] ||= [] - @models_by_id[id][:unmapped][predicate] << value unless value.nil? - else - @models_by_id[id].unmapped_set(predicate, value) - end - end def create_struct(bnode_extraction, models_by_id, sol, variables) list_attributes = Set.new(@klass.attributes(:list)) From 536e22fea70ec871ad33a5d9984d1c2c3ef1cff0 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 3 Mar 2023 09:36:37 +0100 Subject: [PATCH 041/110] use the new lang filter module in the solution_mapper --- lib/goo/sparql/solutions_mapper.rb | 900 +++++++++++++++-------------- 1 file changed, 452 insertions(+), 448 deletions(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 01ade699..ca830719 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -1,450 +1,454 @@ -module Goo - module SPARQL - class SolutionMapper - BNODES_TUPLES = Struct.new(:id, :attribute) - - def initialize(aggregate_projections, bnode_extraction, embed_struct, - incl_embed, klass_struct, models_by_id, - properties_to_include, unmapped, variables, ids, options) - - @aggregate_projections = aggregate_projections - @bnode_extraction = bnode_extraction - @embed_struct = embed_struct - @incl_embed = incl_embed - @klass_struct = klass_struct - @models_by_id = models_by_id - @properties_to_include = properties_to_include - @unmapped = unmapped - @variables = variables - @ids = ids - @klass = options[:klass] - @read_only = options[:read_only] - @incl = options[:include] - @count = options[:count] - @collection = options[:collection] - @objects_by_lang = {} - end - - def map_each_solutions(select) - found = Set.new - fill_models_with_platform_languages = false - objects_new = {} - list_attributes = Set.new(@klass.attributes(:list)) - all_attributes = Set.new(@klass.attributes(:all)) - @lang_filter = Goo::SPARQL::Solution::LanguageFilter.new - - select.each_solution do |sol| - next if sol[:some_type] && @klass.type_uri(@collection) != sol[:some_type] - return sol[:count_var].object if @count - - found.add(sol[:id]) - id = sol[:id] - - create_model(id) - - if @bnode_extraction - add_bnode_to_model(sol) - next - end - - if @unmapped - add_unmapped_to_model(sol, requested_lang) - next - end - - if @aggregate_projections - add_aggregations_to_model(sol) - next - end - - predicate = sol[:attributeProperty].to_s.to_sym - - next if predicate.nil? || !all_attributes.include?(predicate) - - object = sol[:attributeObject] - - # bnodes - if bnode_id?(object, predicate) - objects_new = bnode_id_tuple(id, object, objects_new, predicate) - next - end - - lang = object_language(object) # if lang is nil, it means that the object is not a literal - - requested_lang = nil - - if requested_lang.nil? - requested_lang = Goo.main_languages[0] || :EN - fill_models_with_platform_languages = true - end - - objects, objects_new = get_value_object(id, objects_new, object, list_attributes, predicate) - add_object_to_model(id, objects, object, predicate, lang, requested_lang) - end - - if fill_models_with_platform_languages - @lang_filter.fill_models_with_other_languages(@models_by_id, @objects_by_lang, list_attributes, @incl, @klass) - end - - init_unloaded_attributes(found, list_attributes) - - return @models_by_id if @bnode_extraction - - model_set_collection_attributes(@models_by_id, objects_new) - - # remove from models_by_id elements that were not touched - @models_by_id.select! { |k, _m| found.include?(k) } - - models_set_all_persistent(@models_by_id) unless @read_only - - # next level of embed attributes - include_embed_attributes(@incl_embed, objects_new) if @incl_embed && !@incl_embed.empty? - - # bnodes - blank_nodes = objects_new.select { |id, _obj| id.is_a?(RDF::Node) && id.anonymous? } - include_bnodes(blank_nodes, @models_by_id) unless blank_nodes.empty? - - models_unmapped_to_array(@models_by_id) if @unmapped - - @models_by_id - end - - private - - def init_unloaded_attributes(found, list_attributes) - return if @incl.nil? - - # Here we are setting to nil all attributes that have been included but not found in the triplestore - found.uniq.each do |model_id| - m = @models_by_id[model_id] - @incl.each do |attr_to_incl| - is_handler = m.respond_to?(:handler?) && m.class.handler?(attr_to_incl) - next if attr_to_incl.to_s.eql?('unmapped') || is_handler - - loaded = m.respond_to?('loaded_attributes') && m.loaded_attributes.include?(attr_to_incl) - is_list = list_attributes.include?(attr_to_incl) - is_struct = m.respond_to?(:klass) - - # Go through all models queried - if is_struct - m[attr_to_incl] = [] if is_list && m[attr_to_incl].nil? - elsif is_list && (!loaded || m.send(attr_to_incl.to_s).nil?) - m.send("#{attr_to_incl}=", [], on_load: true) - elsif !loaded && !is_list && m.respond_to?("#{attr_to_incl}=") - m.send("#{attr_to_incl}=", nil, on_load: true) - end - end - end - end - - def get_value_object(id, objects_new, object, list_attributes, predicate) - object = object.object if object && !(object.is_a? RDF::URI) - range_for_v = @klass.range(predicate) - # binding.pry if v.eql?(:enrolled) - # dependent model creation - - if object.is_a?(RDF::URI) && (predicate != :id) && !range_for_v.nil? - if objects_new.include?(object) - object = objects_new[object] - elsif !range_for_v.inmutable? - pre_val = get_preload_value(id, object, predicate) - object, objects_new = if !@read_only - preloaded_or_new_object(object, objects_new, pre_val, predicate) - else - # depedent read only - preloaded_or_new_struct(object, objects_new, pre_val, predicate) - end - else - object = range_for_v.find(object).first - end - end - - if list_attributes.include?(predicate) - pre = @klass_struct ? @models_by_id[id][predicate] : @models_by_id[id].instance_variable_get("@#{predicate}") - - object = [] if object.nil? && pre.nil? - - object = pre if object.nil? && !pre.nil? - - object = pre.nil? ? [object] : (pre.dup << object) - object.uniq - - end - [object, objects_new] - end - +module Goo + module SPARQL + class SolutionMapper + BNODES_TUPLES = Struct.new(:id, :attribute) + + def initialize(aggregate_projections, bnode_extraction, embed_struct, + incl_embed, klass_struct, models_by_id, + properties_to_include, unmapped, variables, ids, options) + + @aggregate_projections = aggregate_projections + @bnode_extraction = bnode_extraction + @embed_struct = embed_struct + @incl_embed = incl_embed + @klass_struct = klass_struct + @models_by_id = models_by_id + @properties_to_include = properties_to_include + @unmapped = unmapped + @variables = variables + @ids = ids + @klass = options[:klass] + @read_only = options[:read_only] + @incl = options[:include] + @count = options[:count] + @collection = options[:collection] + @objects_by_lang = {} + @requested_lang = :EN + Goo.main_languages = [:ES, :FR, :EN] + end + + def map_each_solutions(select) + found = Set.new + fill_models_with_platform_languages = init_requested_lang + objects_new = {} + list_attributes = Set.new(@klass.attributes(:list)) + all_attributes = Set.new(@klass.attributes(:all)) + @lang_filter = Goo::SPARQL::Solution::LanguageFilter.new(requested_lang: @requested_lang, unmapped: @unmapped, + list_attributes: list_attributes) + + + select.each_solution do |sol| + next if sol[:some_type] && @klass.type_uri(@collection) != sol[:some_type] + return sol[:count_var].object if @count + + found.add(sol[:id]) + id = sol[:id] + + create_model(id) + + if @bnode_extraction + add_bnode_to_model(sol) + next + end + + if @unmapped + add_unmapped_to_model(sol) + next + end + + if @aggregate_projections + add_aggregations_to_model(sol) + next + end + + predicate = sol[:attributeProperty].to_s.to_sym + + next if predicate.nil? || !all_attributes.include?(predicate) + + object = sol[:attributeObject] + + # bnodes + if bnode_id?(object, predicate) + objects_new = bnode_id_tuple(id, object, objects_new, predicate) + next + end + + lang = @lang_filter.object_language(object) # if lang is nil, it means that the object is not a literal + + + objects, objects_new = get_value_object(id, objects_new, object, list_attributes, predicate) + add_object_to_model(id, objects, object, predicate, lang) + end + + + if fill_models_with_platform_languages + @lang_filter.fill_models_with_other_languages(@models_by_id) + end + + init_unloaded_attributes(found, list_attributes) + + return @models_by_id if @bnode_extraction + + model_set_collection_attributes(@models_by_id, objects_new) + + # remove from models_by_id elements that were not touched + @models_by_id.select! { |k, _m| found.include?(k) } + + models_set_all_persistent(@models_by_id) unless @read_only + + # next level of embed attributes + include_embed_attributes(@incl_embed, objects_new) if @incl_embed && !@incl_embed.empty? + + # bnodes + blank_nodes = objects_new.select { |id, _obj| id.is_a?(RDF::Node) && id.anonymous? } + include_bnodes(blank_nodes, @models_by_id) unless blank_nodes.empty? + + models_unmapped_to_array(@models_by_id) if @unmapped + + @models_by_id + end + + private + + def init_requested_lang + if @requested_lang.nil? + @requested_lang = Goo.main_languages[0] || :EN + return true + end + + false + end + + def init_unloaded_attributes(found, list_attributes) + return if @incl.nil? + + # Here we are setting to nil all attributes that have been included but not found in the triplestore + found.uniq.each do |model_id| + m = @models_by_id[model_id] + @incl.each do |attr_to_incl| + is_handler = m.respond_to?(:handler?) && m.class.handler?(attr_to_incl) + next if attr_to_incl.to_s.eql?('unmapped') || is_handler + + loaded = m.respond_to?('loaded_attributes') && m.loaded_attributes.include?(attr_to_incl) + is_list = list_attributes.include?(attr_to_incl) + is_struct = m.respond_to?(:klass) + + # Go through all models queried + if is_struct + m[attr_to_incl] = [] if is_list && m[attr_to_incl].nil? + elsif is_list && (!loaded || m.send(attr_to_incl.to_s).nil?) + m.send("#{attr_to_incl}=", [], on_load: true) + elsif !loaded && !is_list && m.respond_to?("#{attr_to_incl}=") + m.send("#{attr_to_incl}=", nil, on_load: true) + end + end + end + end + + def get_value_object(id, objects_new, object, list_attributes, predicate) + object = object.object if object && !(object.is_a? RDF::URI) + range_for_v = @klass.range(predicate) + + + if object.is_a?(RDF::URI) && (predicate != :id) && !range_for_v.nil? + if objects_new.include?(object) + object = objects_new[object] + elsif !range_for_v.inmutable? + pre_val = get_preload_value(id, object, predicate) + object, objects_new = if !@read_only + preloaded_or_new_object(object, objects_new, pre_val, predicate) + else + # depedent read only + preloaded_or_new_struct(object, objects_new, pre_val, predicate) + end + else + object = range_for_v.find(object).first + end + end + + if list_attributes.include?(predicate) + pre = @klass_struct ? @models_by_id[id][predicate] : @models_by_id[id].instance_variable_get("@#{predicate}") + + if object.nil? + object = pre.nil? ? [] : pre + else + object = pre.nil? ? [object] : (pre.dup << object) + object.uniq! + end + + end + [object, objects_new] + end + def add_object_to_model(id, objects, current_obj, predicate, language) - def add_object_to_model(id, objects, current_obj, predicate, language, requested_lang = nil) - if @models_by_id[id].respond_to?(:klass) - @models_by_id[id][predicate] = objects unless objects.nil? && !@models_by_id[id][predicate].nil? - elsif !@models_by_id[id].class.handler?(predicate) && - !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && - predicate != :id - - if language.nil? # the object is a non-literal - return @models_by_id[id].send("#{predicate}=", objects, on_load: true) - end - - if language_match?(language, requested_lang) # the object is a literal and the language matches - return @models_by_id[id].send("#{predicate}=", objects, on_load: true) - end - - store_objects_by_lang(id, predicate, current_obj, language) + if @models_by_id[id].respond_to?(:klass) + @models_by_id[id][predicate] = objects unless objects.nil? && !@models_by_id[id][predicate].nil? + elsif !@models_by_id[id].class.handler?(predicate) && + !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && + predicate != :id + + if language.nil? || @lang_filter.language_match?(language) + return @models_by_id[id].send("#{predicate}=", objects, on_load: true) + end + + @lang_filter.store_objects_by_lang(id, predicate, current_obj, language) + end + end + + def get_preload_value(id, object, predicate) + pre_val = nil + if predicate_preloaded?(id, predicate) + pre_val = preloaded_value(id, predicate) + pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) + end + pre_val + end + + def preloaded_or_new_object(object, objects_new, pre_val, predicate) + object = pre_val || @klass.range_object(predicate, object) + objects_new[object.id] = object + [object, objects_new] + end + + def preloaded_or_new_struct(object, objects_new, pre_val, predicate) + struct = pre_val || @embed_struct[predicate].new + struct.id = object + struct.klass = @klass.range(predicate) + objects_new[struct.id] = struct + [struct, objects_new] + end + + def preloaded_value(id, predicate) + if !@read_only + @models_by_id[id].instance_variable_get("@#{predicate}") + + else + @models_by_id[id][predicate] + end + end + + def predicate_preloaded?(id, predicate) + @models_by_id[id] && + (@models_by_id[id].respond_to?(:klass) || @models_by_id[id].loaded_attributes.include?(predicate)) + end + + def bnode_id?(object, predicate) + object.is_a?(RDF::Node) && object.anonymous? && @incl.include?(predicate) + end + + def bnode_id_tuple(id, object, objects_new, predicate) + range = @klass.range(predicate) + objects_new[object] = BNODES_TUPLES.new(id, predicate) if range.respond_to?(:new) + objects_new + end + + def add_bnode_to_model(sol) + id = sol[:id] + struct = create_struct(@bnode_extraction, @models_by_id, sol, @variables) + @models_by_id[id].send("#{@bnode_extraction}=", struct) + end + + def create_model(id) + @models_by_id[id] = create_class_model(id, @klass, @klass_struct) unless @models_by_id.include?(id) + end + + + def create_struct(bnode_extraction, models_by_id, sol, variables) + list_attributes = Set.new(@klass.attributes(:list)) + struct = @klass.range(bnode_extraction).new + variables.each do |v| + next if v == :id + + svalue = sol[v] + struct[v] = svalue.is_a?(RDF::Node) ? svalue : svalue.object + end + if list_attributes.include?(bnode_extraction) + pre = models_by_id[sol[:id]].instance_variable_get("@#{bnode_extraction}") + pre = pre ? (pre.dup << struct) : [struct] + struct = pre + end + struct + end + + def create_class_model(id, klass, klass_struct) + klass_model = klass_struct ? klass_struct.new : klass.new + klass_model.id = id + klass_model.persistent = true unless klass_struct + klass_model.klass = klass if klass_struct + klass_model + end + + def models_unmapped_to_array(models_by_id) + models_by_id.each do |_idm, m| + m.unmmaped_to_array + end + end + + def include_bnodes(bnodes, models_by_id) + # group by attribute + attrs = bnodes.map { |_x, y| y.attribute }.uniq + attrs.each do |attr| + struct = @klass.range(attr) + + # bnodes that are in a range of goo ground models + # for example parents and children in LD class models + # we skip this cases for the moment + next if struct.respond_to?(:model_name) + + bnode_attrs = struct.new.to_h.keys + ids = bnodes.select { |_x, y| y.attribute == attr }.map { |_x, y| y.id } + @klass.where.models(models_by_id.select { |x, _y| ids.include?(x) }.values) + .in(@collection) + .include(bnode: { attr => bnode_attrs }).all + end + end + + def include_embed_attributes(incl_embed, objects_new) + incl_embed.each do |attr, next_attrs| + # anything to join ? + attr_range = @klass.range(attr) + next if attr_range.nil? + + range_objs = objects_new.select do |_id, obj| + obj.instance_of?(attr_range) || (obj.respond_to?(:klass) && obj[:klass] == attr_range) + end.values + next if range_objs.empty? + + range_objs.uniq! + query = attr_range.where.models(range_objs).in(@collection).include(*next_attrs) + query = query.read_only if @read_only + query.all + end + end + + def models_set_all_persistent(models_by_id) + return unless @ids + + models_by_id.each do |_k, m| + m.persistent = true + end + end + + def model_set_collection_attributes(models_by_id, objects_new) + collection_value = get_collection_value + return unless collection_value + + collection_attribute = @klass.collection_opts + models_by_id.each do |_id, m| + m.send("#{collection_attribute}=", collection_value) + end + objects_new.each do |_id, obj_new| + if obj_new.respond_to?(:klass) + collection_attribute = obj_new[:klass].collection_opts + obj_new[collection_attribute] = collection_value + elsif obj_new.class.respond_to?(:collection_opts) && + obj_new.class.collection_opts.instance_of?(Symbol) + collection_attribute = obj_new.class.collection_opts + obj_new.send("#{collection_attribute}=", collection_value) + end + end + end + + def get_collection_value + collection_value = nil + if @klass.collection_opts.instance_of?(Symbol) + 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 object_to_array(id, klass_struct, models_by_id, object, predicate) + pre = if klass_struct + models_by_id[id][predicate] + else + models_by_id[id].instance_variable_get("@#{predicate}") + end + if object.nil? && pre.nil? + object = [] + elsif object.nil? && !pre.nil? + object = pre + elsif object + object = !pre ? [object] : (pre.dup << object) + object.uniq! + end + object + end + + def dependent_model_creation(embed_struct, id, models_by_id, object, objects_new, v, options) + read_only = options[:read_only] + if object.is_a?(RDF::URI) && v != :id + range_for_v = @klass.range(v) + if range_for_v + if objects_new.include?(object) + object = objects_new[object] + elsif !range_for_v.inmutable? + pre_val = get_pre_val(id, models_by_id, object, v, read_only) + object = get_object_from_range(pre_val, embed_struct, object, objects_new, v, options) + else + object = range_for_v.find(object).first + end + end + end + object + end + + def get_object_from_range(pre_val, embed_struct, object, objects_new, predicate) + range_for_v = @klass.range(predicate) + if !@read_only + object = pre_val || @klass.range_object(predicate, object) + objects_new[object.id] = object + else + # depedent read only + struct = pre_val || embed_struct[predicate].new + struct.id = object + struct.klass = range_for_v + objects_new[struct.id] = struct + object = struct + end + object + end + + def get_pre_val(id, models_by_id, object, predicate) + pre_val = nil + if models_by_id[id] && + ((models_by_id[id].respond_to?(:klass) && models_by_id[id]) || + models_by_id[id].loaded_attributes.include?(predicate)) + pre_val = if !@read_only + models_by_id[id].instance_variable_get("@#{predicate}") + else + models_by_id[id][predicate] + end + + pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) + end + pre_val + end + + def add_unmapped_to_model(sol) + predicate = sol[:attributeProperty].to_s.to_sym + return unless @properties_to_include[predicate] + + id = sol[:id] + value = sol[:attributeObject] + + language = @lang_filter.object_language(value) + + @lang_filter.model_set_unmapped(@models_by_id[id], @properties_to_include[predicate][:uri], value, language) + end + + def add_aggregations_to_model(sol) + id = sol[:id] + @aggregate_projections&.each do |aggregate_key, aggregate_val| + if @models_by_id[id].respond_to?(:add_aggregate) + @models_by_id[id].add_aggregate(aggregate_val[1], aggregate_val[0], sol[aggregate_key].object) + else + (@models_by_id[id].aggregates ||= []) << Goo::Base::AGGREGATE_VALUE.new(aggregate_val[1], + aggregate_val[0], + sol[aggregate_key].object) + end end - end - - def get_preload_value(id, object, predicate) - pre_val = nil - if predicate_preloaded?(id, predicate) - pre_val = preloaded_value(id, predicate) - pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) - end - pre_val - end - - def preloaded_or_new_object(object, objects_new, pre_val, predicate) - object = pre_val || @klass.range_object(predicate, object) - objects_new[object.id] = object - [object, objects_new] - end - - def preloaded_or_new_struct(object, objects_new, pre_val, predicate) - struct = pre_val || @embed_struct[predicate].new - struct.id = object - struct.klass = @klass.range(predicate) - objects_new[struct.id] = struct - [struct, objects_new] - end - - def preloaded_value(id, predicate) - if !@read_only - @models_by_id[id].instance_variable_get("@#{predicate}") - - else - @models_by_id[id][predicate] - end - end - - def predicate_preloaded?(id, predicate) - @models_by_id[id] && - (@models_by_id[id].respond_to?(:klass) || @models_by_id[id].loaded_attributes.include?(predicate)) - end - - def bnode_id?(object, predicate) - object.is_a?(RDF::Node) && object.anonymous? && @incl.include?(predicate) - end - - def bnode_id_tuple(id, object, objects_new, predicate) - range = @klass.range(predicate) - objects_new[object] = BNODES_TUPLES.new(id, predicate) if range.respond_to?(:new) - objects_new - end - - def add_bnode_to_model(sol) - id = sol[:id] - struct = create_struct(@bnode_extraction, @models_by_id, sol, @variables) - @models_by_id[id].send("#{@bnode_extraction}=", struct) - end - - def create_model(id) - @models_by_id[id] = create_class_model(id, @klass, @klass_struct) unless @models_by_id.include?(id) - end - - - def create_struct(bnode_extraction, models_by_id, sol, variables) - list_attributes = Set.new(@klass.attributes(:list)) - struct = @klass.range(bnode_extraction).new - variables.each do |v| - next if v == :id - - svalue = sol[v] - struct[v] = svalue.is_a?(RDF::Node) ? svalue : svalue.object - end - if list_attributes.include?(bnode_extraction) - pre = models_by_id[sol[:id]].instance_variable_get("@#{bnode_extraction}") - pre = pre ? (pre.dup << struct) : [struct] - struct = pre - end - struct - end - - def create_class_model(id, klass, klass_struct) - klass_model = klass_struct ? klass_struct.new : klass.new - klass_model.id = id - klass_model.persistent = true unless klass_struct - klass_model.klass = klass if klass_struct - klass_model - end - - def models_unmapped_to_array(models_by_id) - models_by_id.each do |_idm, m| - m.unmmaped_to_array - end - end - - def include_bnodes(bnodes, models_by_id) - # group by attribute - attrs = bnodes.map { |_x, y| y.attribute }.uniq - attrs.each do |attr| - struct = @klass.range(attr) - - # bnodes that are in a range of goo ground models - # for example parents and children in LD class models - # we skip this cases for the moment - next if struct.respond_to?(:model_name) - - bnode_attrs = struct.new.to_h.keys - ids = bnodes.select { |_x, y| y.attribute == attr }.map { |_x, y| y.id } - @klass.where.models(models_by_id.select { |x, _y| ids.include?(x) }.values) - .in(@collection) - .include(bnode: { attr => bnode_attrs }).all - end - end - - def include_embed_attributes(incl_embed, objects_new) - incl_embed.each do |attr, next_attrs| - # anything to join ? - attr_range = @klass.range(attr) - next if attr_range.nil? - - range_objs = objects_new.select do |_id, obj| - obj.instance_of?(attr_range) || (obj.respond_to?(:klass) && obj[:klass] == attr_range) - end.values - next if range_objs.empty? - - range_objs.uniq! - query = attr_range.where.models(range_objs).in(@collection).include(*next_attrs) - query = query.read_only if @read_only - query.all - end - end - - def models_set_all_persistent(models_by_id) - return unless @ids - - models_by_id.each do |_k, m| - m.persistent = true - end - end - - def model_set_collection_attributes(models_by_id, objects_new) - collection_value = get_collection_value - return unless collection_value - - collection_attribute = @klass.collection_opts - models_by_id.each do |_id, m| - m.send("#{collection_attribute}=", collection_value) - end - objects_new.each do |_id, obj_new| - if obj_new.respond_to?(:klass) - collection_attribute = obj_new[:klass].collection_opts - obj_new[collection_attribute] = collection_value - elsif obj_new.class.respond_to?(:collection_opts) && - obj_new.class.collection_opts.instance_of?(Symbol) - collection_attribute = obj_new.class.collection_opts - obj_new.send("#{collection_attribute}=", collection_value) - end - end - end - - def get_collection_value - collection_value = nil - if @klass.collection_opts.instance_of?(Symbol) - 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 object_to_array(id, klass_struct, models_by_id, object, predicate) - pre = if klass_struct - models_by_id[id][predicate] - else - models_by_id[id].instance_variable_get("@#{predicate}") - end - if object.nil? && pre.nil? - object = [] - elsif object.nil? && !pre.nil? - object = pre - elsif object - object = !pre ? [object] : (pre.dup << object) - object.uniq! - end - object - end - - def dependent_model_creation(embed_struct, id, models_by_id, object, objects_new, v, options) - read_only = options[:read_only] - if object.is_a?(RDF::URI) && v != :id - range_for_v = @klass.range(v) - if range_for_v - if objects_new.include?(object) - object = objects_new[object] - elsif !range_for_v.inmutable? - pre_val = get_pre_val(id, models_by_id, object, v, read_only) - object = get_object_from_range(pre_val, embed_struct, object, objects_new, v, options) - else - object = range_for_v.find(object).first - end - end - end - object - end - - def get_object_from_range(pre_val, embed_struct, object, objects_new, predicate) - range_for_v = @klass.range(predicate) - if !@read_only - object = pre_val || @klass.range_object(predicate, object) - objects_new[object.id] = object - else - # depedent read only - struct = pre_val || embed_struct[predicate].new - struct.id = object - struct.klass = range_for_v - objects_new[struct.id] = struct - object = struct - end - object - end - - def get_pre_val(id, models_by_id, object, predicate) - pre_val = nil - if models_by_id[id] && - ((models_by_id[id].respond_to?(:klass) && models_by_id[id]) || - models_by_id[id].loaded_attributes.include?(predicate)) - pre_val = if !@read_only - models_by_id[id].instance_variable_get("@#{predicate}") - else - models_by_id[id][predicate] - end - - pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) - end - pre_val - end - - def add_unmapped_to_model(sol, requested_lang) - predicate = sol[:attributeProperty].to_s.to_sym - return unless @properties_to_include[predicate] - - id = sol[:id] - value = sol[:attributeObject] - - model_set_unmapped(id, @properties_to_include[predicate][:uri], value, requested_lang) - end - - def add_aggregations_to_model(sol) - id = sol[:id] - @aggregate_projections&.each do |aggregate_key, aggregate_val| - if @models_by_id[id].respond_to?(:add_aggregate) - @models_by_id[id].add_aggregate(aggregate_val[1], aggregate_val[0], sol[aggregate_key].object) - else - (@models_by_id[id].aggregates ||= []) << Goo::Base::AGGREGATE_VALUE.new(aggregate_val[1], - aggregate_val[0], - sol[aggregate_key].object) - end - end - end - end - end -end + end + end + end +end From 085eb41c101c3e924219ad1080bacb218ca52c47 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 3 Mar 2023 09:56:47 +0100 Subject: [PATCH 042/110] remove the usage of the old lang filter module in map_attributes --- lib/goo/base/resource.rb | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index 8f7da133..c17191a8 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -383,25 +383,7 @@ def self.map_attributes(inst,equivalent_predicates=nil) 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 - 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 + object = object.map {|o| o.is_a?(RDF::URI) ? o : o.object} if klass.range(attr) object = object.map { |o| From 48389a03cfd7d203c2f49c5797eee0fbb90d6779 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 15:52:29 +0100 Subject: [PATCH 043/110] add request language global variable --- lib/goo.rb | 9 +++++++++ lib/goo/sparql/solutions_mapper.rb | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/goo.rb b/lib/goo.rb index eb941d9d..ff0e6279 100644 --- a/lib/goo.rb +++ b/lib/goo.rb @@ -32,6 +32,7 @@ module Goo # 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] + @@requested_language = nil @@configure_flag = false @@sparql_backends = {} @@ -58,6 +59,14 @@ def self.main_languages=(lang) @@main_languages = lang end + def self.requested_language + @@requested_language + end + + def self.requested_language=(lang) + @@requested_language = 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)} diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index ca830719..d80aae67 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -23,8 +23,7 @@ def initialize(aggregate_projections, bnode_extraction, embed_struct, @count = options[:count] @collection = options[:collection] @objects_by_lang = {} - @requested_lang = :EN - Goo.main_languages = [:ES, :FR, :EN] + @requested_lang = Goo.requested_language end def map_each_solutions(select) From 08bdaa16735e60c04d9e2179c02c3775d985a01e Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 15:59:00 +0100 Subject: [PATCH 044/110] fix datatype check for list values --- .../validators/implementations/data_type.rb | 27 ++++++++++++------- test/test_validators.rb | 5 +++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/goo/validators/implementations/data_type.rb b/lib/goo/validators/implementations/data_type.rb index 0ea65ab3..04f46d0c 100644 --- a/lib/goo/validators/implementations/data_type.rb +++ b/lib/goo/validators/implementations/data_type.rb @@ -29,16 +29,16 @@ def enforce_type(type, value) return true if value.nil? if type == :boolean - return self.enforce_type_boolean(value) + self.enforce_type_boolean(value) elsif type.eql?(:uri) || type.eql?(RDF::URI) - return self.enforce_type_uri(value) + self.enforce_type_uri(value) elsif type.eql?(:uri) || type.eql?(Array) - return value.is_a? Array + value.is_a? Array else if value.is_a? Array - return value.select{|x| !x.is_a?(type)}.empty? + value.select{|x| !x.is_a?(type)}.empty? else - return value.is_a? type + value.is_a? type end end @@ -47,19 +47,28 @@ def enforce_type(type, value) def enforce_type_uri(value) return true if value.nil? - value.is_a?(RDF::URI) && value.valid? + if value.kind_of? Array + value.select { |x| !is_a_uri?(x) }.empty? + else + is_a_uri?(value) + end + end def enforce_type_boolean(value) if value.kind_of? Array - return value.select { |x| !is_a_boolean?(x) }.empty? + value.select { |x| !is_a_boolean?(x) }.empty? else - return is_a_boolean?(value) + is_a_boolean?(value) end end def is_a_boolean?(value) - return (value.class == TrueClass) || (value.class == FalseClass) + (value.class == TrueClass) || (value.class == FalseClass) + end + + def is_a_uri?(value) + value.is_a?(RDF::URI) && value.valid? end end end diff --git a/test/test_validators.rb b/test/test_validators.rb index 8795fccf..a8e69dbe 100644 --- a/test/test_validators.rb +++ b/test/test_validators.rb @@ -12,6 +12,7 @@ class Person < Goo::Base::Resource attribute :birth_date, enforce: [ :date_time ] attribute :male, enforce: [:boolean] attribute :social, enforce: [:uri] + attribute :socials, enforce: [:uri, :list] attribute :weight, enforce: [:float] attribute :friends, enforce: [Person, :list] end @@ -143,6 +144,7 @@ def test_datatype_validators p.birth_date = 100 p.male = "ok" p.social = 100 + p.socials = [100] p.weight = 100 @@ -161,7 +163,8 @@ def test_datatype_validators p.one_number = 12 p.birth_date = DateTime.parse('1978-01-01') p.male = true - p.social = RDF::URI.new('https://test.com/') + p.social = RDF::URI.new('https://test.com/') + p.socials = [RDF::URI.new('https://test.com/'), RDF::URI.new('https://test.com/')] p.weight = 100.0 #good types are valid assert p.valid? From 5e5c77252c8652ef06c190e51ee34eef35a9424d Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 16:03:10 +0100 Subject: [PATCH 045/110] remove old unused test if clause --- lib/goo/base/resource.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index fe11f89c..d4459bf4 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -383,11 +383,6 @@ def self.map_attributes(inst,equivalent_predicates=nil) 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 From 4cc7a7fb9f8591957cde3c592ef4c3a59090d3a5 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 17:11:24 +0100 Subject: [PATCH 046/110] for no unmapped values cast them to object before sending --- lib/goo/sparql/mixins/solution_lang_filter.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index b6333593..6f1a7aff 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -28,8 +28,7 @@ def store_objects_by_lang(id, predicate, object, language) objects_by_lang[id][predicate] ||= {} objects_by_lang[id][predicate][language] ||= [] - objects_by_lang[id][predicate][language] << object.object - + objects_by_lang[id][predicate][language] << object end def fill_models_with_other_languages(models_by_id) @@ -89,12 +88,13 @@ def add_unmapped_to_model(model, predicate, value) end end - def save_value_to_model(model, value, predicate, unmapped) + def save_value_to_model(model, values, predicate, unmapped) if unmapped - add_unmapped_to_model(model, predicate, value) + add_unmapped_to_model(model, predicate, values) else - value = Array(value).min unless list_attributes?(predicate) - model.send("#{predicate}=", value, on_load: true) + values = values.map(&:object) + values = Array(values).min unless list_attributes?(predicate) + model.send("#{predicate}=", values, on_load: true) end end From 660e8c525a3c7c992379876efc9dcb4649cfe59c Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 17:11:49 +0100 Subject: [PATCH 047/110] for resource unmapped_set merge new value if an array --- lib/goo/base/resource.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index d4459bf4..44cacdda 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -119,8 +119,8 @@ def missing_load_attributes def unmapped_set(attribute,value) @unmapped ||= {} - @unmapped[attribute] ||= Set.new - @unmapped[attribute] << value unless value.nil? + @unmapped[attribute] ||= Set.new + @unmapped[attribute].merge(Array(value)) unless value.nil? end def unmapped_get(attribute) From d30c5748f27c05d88d5db423f7c4b2355412483b Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 17:12:02 +0100 Subject: [PATCH 048/110] prevent add_object_to_model if no_lang and previous value exist --- lib/goo/sparql/solutions_mapper.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index d80aae67..d54e0418 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -186,7 +186,11 @@ def add_object_to_model(id, objects, current_obj, predicate, language) !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && predicate != :id - if language.nil? || @lang_filter.language_match?(language) + if language.nil? + return @models_by_id[id].send("#{predicate}=", objects, on_load: true) + elsif @lang_filter.language_match?(language) + return if language.eql?(:no_lang) && !@models_by_id[id].instance_variable_get("@#{predicate}").nil? && !objects.is_a?(Array) + return @models_by_id[id].send("#{predicate}=", objects, on_load: true) end From 96e57326ebdf454d92929cb4a27e6bfbb3e424e4 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 22:04:21 +0100 Subject: [PATCH 049/110] move from the mapper lang_filter related code to lang_filter module --- lib/goo/sparql/mixins/solution_lang_filter.rb | 33 ++++++++++++++- lib/goo/sparql/solutions_mapper.rb | 40 ++++--------------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 6f1a7aff..f624a8d2 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -10,7 +10,7 @@ def initialize(requested_lang: nil, unmapped: false, list_attributes: []) @objects_by_lang = {} @unmapped = unmapped @requested_lang = requested_lang - + @fill_other_languages = init_requested_lang end def object_language(new_value) @@ -33,6 +33,8 @@ def store_objects_by_lang(id, predicate, object, language) def fill_models_with_other_languages(models_by_id) + return unless fill_other_languages? + other_platform_languages = Goo.main_languages[1..] || [] objects_by_lang.each do |id, predicates| @@ -57,8 +59,21 @@ def fill_models_with_other_languages(models_by_id) end + def model_set_value(model, predicate, objects, object) + language = object_language(object) # if lang is nil, it means that the object is not a literal + if language.nil? + return model.send("#{predicate}=", objects, on_load: true) + elsif language_match?(language) + return if language.eql?(:no_lang) && !model.instance_variable_get("@#{predicate}").nil? && !objects.is_a?(Array) + + return model.send("#{predicate}=", objects, on_load: true) + end - def model_set_unmapped(model, predicate, value, language) + store_objects_by_lang(model.id, predicate, object, language) + end + + def model_set_unmapped(model, predicate, value) + language = object_language(value) if language.nil? || language_match?(language) return add_unmapped_to_model(model, predicate, value) end @@ -69,6 +84,15 @@ def model_set_unmapped(model, predicate, value, language) private + def init_requested_lang + if @requested_lang.nil? + @requested_lang = Goo.main_languages[0] || :EN + return true + end + + false + end + def get_model_attribute_value(model, predicate) if unmapped unmapped_get(model, predicate) @@ -111,6 +135,11 @@ def list_attributes?(predicate) @list_attributes.include?(predicate) end + + def fill_other_languages? + @fill_other_languages + end + end end end diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index d54e0418..b29814b2 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -22,13 +22,11 @@ def initialize(aggregate_projections, bnode_extraction, embed_struct, @incl = options[:include] @count = options[:count] @collection = options[:collection] - @objects_by_lang = {} @requested_lang = Goo.requested_language end def map_each_solutions(select) found = Set.new - fill_models_with_platform_languages = init_requested_lang objects_new = {} list_attributes = Set.new(@klass.attributes(:list)) all_attributes = Set.new(@klass.attributes(:all)) @@ -72,17 +70,12 @@ def map_each_solutions(select) next end - lang = @lang_filter.object_language(object) # if lang is nil, it means that the object is not a literal - - objects, objects_new = get_value_object(id, objects_new, object, list_attributes, predicate) - add_object_to_model(id, objects, object, predicate, lang) + add_object_to_model(id, objects, object, predicate) end - - if fill_models_with_platform_languages - @lang_filter.fill_models_with_other_languages(@models_by_id) - end + + @lang_filter.fill_models_with_other_languages(@models_by_id) init_unloaded_attributes(found, list_attributes) @@ -109,15 +102,6 @@ def map_each_solutions(select) private - def init_requested_lang - if @requested_lang.nil? - @requested_lang = Goo.main_languages[0] || :EN - return true - end - - false - end - def init_unloaded_attributes(found, list_attributes) return if @incl.nil? @@ -179,22 +163,14 @@ def get_value_object(id, objects_new, object, list_attributes, predicate) [object, objects_new] end - def add_object_to_model(id, objects, current_obj, predicate, language) + def add_object_to_model(id, objects, current_obj, predicate) + if @models_by_id[id].respond_to?(:klass) @models_by_id[id][predicate] = objects unless objects.nil? && !@models_by_id[id][predicate].nil? elsif !@models_by_id[id].class.handler?(predicate) && !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && predicate != :id - - if language.nil? - return @models_by_id[id].send("#{predicate}=", objects, on_load: true) - elsif @lang_filter.language_match?(language) - return if language.eql?(:no_lang) && !@models_by_id[id].instance_variable_get("@#{predicate}").nil? && !objects.is_a?(Array) - - return @models_by_id[id].send("#{predicate}=", objects, on_load: true) - end - - @lang_filter.store_objects_by_lang(id, predicate, current_obj, language) + @lang_filter.model_set_value(@models_by_id[id], predicate, objects, current_obj) end end @@ -435,9 +411,7 @@ def add_unmapped_to_model(sol) id = sol[:id] value = sol[:attributeObject] - language = @lang_filter.object_language(value) - - @lang_filter.model_set_unmapped(@models_by_id[id], @properties_to_include[predicate][:uri], value, language) + @lang_filter.model_set_unmapped(@models_by_id[id], @properties_to_include[predicate][:uri], value) end def add_aggregations_to_model(sol) From 92bed925de041eb5a40adbe8788a74c6c7cb1020 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 22:18:26 +0100 Subject: [PATCH 050/110] move internal lang filter module methods to private section --- lib/goo/sparql/mixins/solution_lang_filter.rb | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index f624a8d2..58bde592 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -13,23 +13,6 @@ def initialize(requested_lang: nil, unmapped: false, list_attributes: []) @fill_other_languages = init_requested_lang end - def object_language(new_value) - new_value.language || :no_lang if new_value.is_a?(RDF::Literal) - end - - def language_match?(language) - !language.nil? && (language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil?) - end - - def store_objects_by_lang(id, predicate, object, language) - # store objects in this format: [id][predicate][language] = [objects] - - objects_by_lang[id] ||= {} - objects_by_lang[id][predicate] ||= {} - objects_by_lang[id][predicate][language] ||= [] - - objects_by_lang[id][predicate][language] << object - end def fill_models_with_other_languages(models_by_id) @@ -84,6 +67,24 @@ def model_set_unmapped(model, predicate, value) private + def object_language(new_value) + new_value.language || :no_lang if new_value.is_a?(RDF::Literal) + end + + def language_match?(language) + !language.nil? && (language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil?) + end + + def store_objects_by_lang(id, predicate, object, language) + # store objects in this format: [id][predicate][language] = [objects] + + objects_by_lang[id] ||= {} + objects_by_lang[id][predicate] ||= {} + objects_by_lang[id][predicate][language] ||= [] + + objects_by_lang[id][predicate][language] << object + end + def init_requested_lang if @requested_lang.nil? @requested_lang = Goo.main_languages[0] || :EN From bcbf686d4373a41a214f2094beeedef920c33f6f Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 23:12:23 +0100 Subject: [PATCH 051/110] add request_store gem to save request language globally --- Gemfile | 1 + Gemfile.lock | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index edb00975..3564fe3b 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem "cube-ruby", require: "cube" gem "faraday", '~> 1.9' gem "rake" gem "uuid" +gem "request_store" group :test do gem "minitest", '< 5.0' diff --git a/Gemfile.lock b/Gemfile.lock index 34a6c39c..9fe7bd02 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -34,7 +34,7 @@ GEM public_suffix (>= 2.0.2, < 6.0) builder (3.2.4) coderay (1.1.3) - concurrent-ruby (1.1.10) + concurrent-ruby (1.2.2) connection_pool (2.3.0) cube-ruby (0.0.3) daemons (1.4.1) @@ -76,10 +76,10 @@ GEM method_source (1.0.0) mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2022.0105) + mime-types-data (3.2023.0218.1) minitest (4.7.5) multi_json (1.15.0) - multipart-post (2.2.3) + multipart-post (2.3.0) mustermann (3.0.0) ruby2_keywords (~> 0.0.1) net-http-persistent (2.9.4) @@ -88,7 +88,7 @@ GEM coderay (~> 1.1) method_source (~> 1.0) public_suffix (5.0.1) - rack (2.2.6.2) + rack (2.2.6.3) rack-accept (0.4.5) rack (>= 0.4) rack-post-body-to-params (0.1.8) @@ -100,8 +100,10 @@ GEM addressable (>= 2.2) redis (5.0.6) redis-client (>= 0.9.0) - redis-client (0.12.1) + redis-client (0.13.0) connection_pool + request_store (1.5.1) + rack (>= 1.4) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) @@ -132,7 +134,7 @@ GEM eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) thread_safe (0.3.6) - tilt (2.0.11) + tilt (2.1.0) tzinfo (0.3.61) unf (0.1.4) unf_ext @@ -155,6 +157,7 @@ DEPENDENCIES rack-accept rack-post-body-to-params rake + request_store simplecov simplecov-cobertura sinatra From f1a5845df36d70deaed54d146f38ee93b54976a3 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 4 Mar 2023 23:13:00 +0100 Subject: [PATCH 052/110] save requested language in model_load options --- lib/goo/sparql/loader.rb | 6 ++++++ lib/goo/sparql/solutions_mapper.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/goo/sparql/loader.rb b/lib/goo/sparql/loader.rb index 821aba26..094fbba2 100644 --- a/lib/goo/sparql/loader.rb +++ b/lib/goo/sparql/loader.rb @@ -1,3 +1,4 @@ +require 'request_store' module Goo module SPARQL module Loader @@ -6,8 +7,10 @@ class << self def model_load(*options) options = options.last + set_request_lang(options) if options[:models] && options[:models].is_a?(Array) && \ (options[:models].length > Goo.slice_loading_size) + options = options.dup models = options[:models] include_options = options[:include] @@ -96,6 +99,9 @@ def model_load_sliced(*options) private + def set_request_lang(options) + options[:requested_lang] = RequestStore.store[:requested_lang] + end def expand_equivalent_predicates(properties_to_include, eq_p) return unless eq_p && !eq_p.empty? diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index b29814b2..b9bf19d3 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -22,7 +22,7 @@ def initialize(aggregate_projections, bnode_extraction, embed_struct, @incl = options[:include] @count = options[:count] @collection = options[:collection] - @requested_lang = Goo.requested_language + @requested_lang = options[:requested_lang] end def map_each_solutions(select) From ac8709a86a4c4d2178d34de0e18367bcd131ebed Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sun, 5 Mar 2023 23:22:05 +0100 Subject: [PATCH 053/110] force requested_lang and portal_langs to be upcase and symbol --- lib/goo/sparql/mixins/solution_lang_filter.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 58bde592..192372d2 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -11,6 +11,7 @@ def initialize(requested_lang: nil, unmapped: false, list_attributes: []) @unmapped = unmapped @requested_lang = requested_lang @fill_other_languages = init_requested_lang + @requested_lang = @requested_lang.to_s.upcase.to_sym end @@ -27,7 +28,7 @@ def fill_models_with_other_languages(models_by_id) next unless model_attribute_val.nil? || model_attribute_val.empty? other_platform_languages.each do |platform_language| - if languages[platform_language] + if languages[platform_language.to_s.upcase.to_sym] save_value_to_model(model, languages[platform_language], predicate, unmapped) break end @@ -70,7 +71,7 @@ def model_set_unmapped(model, predicate, value) def object_language(new_value) new_value.language || :no_lang if new_value.is_a?(RDF::Literal) end - + def language_match?(language) !language.nil? && (language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil?) end From 8dbb74b57ab2286c865a001c0f61b4e5398cd979 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Mon, 6 Mar 2023 13:35:20 +0100 Subject: [PATCH 054/110] change methodes/vars names --- lib/goo/sparql/mixins/solution_lang_filter.rb | 11 +++++------ lib/goo/sparql/solutions_mapper.rb | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 58bde592..56e9a963 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -13,8 +13,7 @@ def initialize(requested_lang: nil, unmapped: false, list_attributes: []) @fill_other_languages = init_requested_lang end - - def fill_models_with_other_languages(models_by_id) + def enrich_models(models_by_id) return unless fill_other_languages? @@ -28,13 +27,13 @@ def fill_models_with_other_languages(models_by_id) other_platform_languages.each do |platform_language| if languages[platform_language] - save_value_to_model(model, languages[platform_language], predicate, unmapped) + save_model_values(model, languages[platform_language], predicate, unmapped) break end end model_attribute_val = get_model_attribute_value(model, predicate) if model_attribute_val.nil? || model_attribute_val.empty? - save_value_to_model(model, languages.values.flatten.uniq, predicate, unmapped) + save_model_values(model, languages.values.flatten.uniq, predicate, unmapped) end end end @@ -42,7 +41,7 @@ def fill_models_with_other_languages(models_by_id) end - def model_set_value(model, predicate, objects, object) + def set_model_value(model, predicate, objects, object) language = object_language(object) # if lang is nil, it means that the object is not a literal if language.nil? return model.send("#{predicate}=", objects, on_load: true) @@ -113,7 +112,7 @@ def add_unmapped_to_model(model, predicate, value) end end - def save_value_to_model(model, values, predicate, unmapped) + def save_model_values(model, values, predicate, unmapped) if unmapped add_unmapped_to_model(model, predicate, values) else diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index b9bf19d3..3117b39c 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -75,7 +75,7 @@ def map_each_solutions(select) end - @lang_filter.fill_models_with_other_languages(@models_by_id) + @lang_filter.enrich_models(@models_by_id) init_unloaded_attributes(found, list_attributes) @@ -170,7 +170,7 @@ def add_object_to_model(id, objects, current_obj, predicate) elsif !@models_by_id[id].class.handler?(predicate) && !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && predicate != :id - @lang_filter.model_set_value(@models_by_id[id], predicate, objects, current_obj) + @lang_filter.set_model_value(@models_by_id[id], predicate, objects, current_obj) end end From 996922a9dfae06da9a7214e1322e1d403d3f1b39 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Mon, 6 Mar 2023 13:50:19 +0100 Subject: [PATCH 055/110] get the last item in objects instead of passing the current object --- lib/goo/sparql/mixins/solution_lang_filter.rb | 8 +++++--- lib/goo/sparql/solutions_mapper.rb | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 1aec2b99..0ca882a8 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -42,8 +42,9 @@ def enrich_models(models_by_id) end - def set_model_value(model, predicate, objects, object) - language = object_language(object) # if lang is nil, it means that the object is not a literal + def set_model_value(model, predicate, objects) + new_value = Array(objects).last + language = object_language(new_value) # if lang is nil, it means that the object is not a literal if language.nil? return model.send("#{predicate}=", objects, on_load: true) elsif language_match?(language) @@ -52,7 +53,8 @@ def set_model_value(model, predicate, objects, object) return model.send("#{predicate}=", objects, on_load: true) end - store_objects_by_lang(model.id, predicate, object, language) + + store_objects_by_lang(model.id, predicate, new_value, language) end def model_set_unmapped(model, predicate, value) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 3117b39c..8a102aac 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -71,7 +71,7 @@ def map_each_solutions(select) end objects, objects_new = get_value_object(id, objects_new, object, list_attributes, predicate) - add_object_to_model(id, objects, object, predicate) + add_object_to_model(id, objects, predicate) end @@ -163,14 +163,14 @@ def get_value_object(id, objects_new, object, list_attributes, predicate) [object, objects_new] end - def add_object_to_model(id, objects, current_obj, predicate) + def add_object_to_model(id, objects, predicate) if @models_by_id[id].respond_to?(:klass) @models_by_id[id][predicate] = objects unless objects.nil? && !@models_by_id[id][predicate].nil? elsif !@models_by_id[id].class.handler?(predicate) && !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && predicate != :id - @lang_filter.set_model_value(@models_by_id[id], predicate, objects, current_obj) + @lang_filter.set_model_value(@models_by_id[id], predicate, objects) end end From b769c165906163e30a026dba511ae1069c4eed3d Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Mon, 6 Mar 2023 13:50:19 +0100 Subject: [PATCH 056/110] Revert "get the last item in objects instead of passing the current object" This reverts commit 996922a9dfae06da9a7214e1322e1d403d3f1b39. --- lib/goo/sparql/mixins/solution_lang_filter.rb | 8 +++----- lib/goo/sparql/solutions_mapper.rb | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 0ca882a8..1aec2b99 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -42,9 +42,8 @@ def enrich_models(models_by_id) end - def set_model_value(model, predicate, objects) - new_value = Array(objects).last - language = object_language(new_value) # if lang is nil, it means that the object is not a literal + def set_model_value(model, predicate, objects, object) + language = object_language(object) # if lang is nil, it means that the object is not a literal if language.nil? return model.send("#{predicate}=", objects, on_load: true) elsif language_match?(language) @@ -53,8 +52,7 @@ def set_model_value(model, predicate, objects) return model.send("#{predicate}=", objects, on_load: true) end - - store_objects_by_lang(model.id, predicate, new_value, language) + store_objects_by_lang(model.id, predicate, object, language) end def model_set_unmapped(model, predicate, value) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 8a102aac..3117b39c 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -71,7 +71,7 @@ def map_each_solutions(select) end objects, objects_new = get_value_object(id, objects_new, object, list_attributes, predicate) - add_object_to_model(id, objects, predicate) + add_object_to_model(id, objects, object, predicate) end @@ -163,14 +163,14 @@ def get_value_object(id, objects_new, object, list_attributes, predicate) [object, objects_new] end - def add_object_to_model(id, objects, predicate) + def add_object_to_model(id, objects, current_obj, predicate) if @models_by_id[id].respond_to?(:klass) @models_by_id[id][predicate] = objects unless objects.nil? && !@models_by_id[id][predicate].nil? elsif !@models_by_id[id].class.handler?(predicate) && !(objects.nil? && !@models_by_id[id].instance_variable_get("@#{predicate}").nil?) && predicate != :id - @lang_filter.set_model_value(@models_by_id[id], predicate, objects) + @lang_filter.set_model_value(@models_by_id[id], predicate, objects, current_obj) end end From 30ed9f9be4529e873d6cb728effe730a8f3c1670 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 7 Mar 2023 19:32:47 +0100 Subject: [PATCH 057/110] handle this case where values is nil in save_model_values --- lib/goo/sparql/mixins/solution_lang_filter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 1aec2b99..8102c82e 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -117,8 +117,8 @@ def save_model_values(model, values, predicate, unmapped) if unmapped add_unmapped_to_model(model, predicate, values) else - values = values.map(&:object) - values = Array(values).min unless list_attributes?(predicate) + values = Array(values).map(&:object) + values = values.min unless list_attributes?(predicate) model.send("#{predicate}=", values, on_load: true) end end From 07b06c31937dacb3bd0e7bcaf2e8a024d145bd31 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Tue, 14 Mar 2023 22:59:57 +0100 Subject: [PATCH 058/110] handle the casf of nil values for the SuperiorEqualTo validator --- lib/goo/validators/implementations/superior_equal_to.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/goo/validators/implementations/superior_equal_to.rb b/lib/goo/validators/implementations/superior_equal_to.rb index 91508f30..38012010 100644 --- a/lib/goo/validators/implementations/superior_equal_to.rb +++ b/lib/goo/validators/implementations/superior_equal_to.rb @@ -11,10 +11,10 @@ class SuperiorEqualTo < ValidatorBase validity_check -> (obj) do target_values = self.class.attr_value(@property, @inst) + target_value = target_values.first + return true if target_value.nil? || @value.nil? - return true if target_values.empty? - - return @value >= target_values.first + return @value >= target_value end def initialize(inst, attr, value, key) From c83687e9a65c25313df6d74be96e83ace9d939c7 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Thu, 16 Mar 2023 02:21:57 +0100 Subject: [PATCH 059/110] add onUpdate callback tests --- test/test_update_callbacks.rb | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test/test_update_callbacks.rb diff --git a/test/test_update_callbacks.rb b/test/test_update_callbacks.rb new file mode 100644 index 00000000..bef38a68 --- /dev/null +++ b/test/test_update_callbacks.rb @@ -0,0 +1,53 @@ +require_relative 'test_case' + + +require_relative 'models' + +class TestUpdateCallBack < Goo::Base::Resource + model :update_callback_model, name_with: :code + attribute :code, enforce: [:string, :existence] + attribute :name, enforce: [:string, :existence] + attribute :first_name, onUpdate: :update_name + attribute :last_name, onUpdate: :update_name + + + def update_name(inst, attr) + self.name = self.first_name + self.last_name + end +end + +class TestUpdateCallBacks < MiniTest::Unit::TestCase + + def self.before_suite + GooTestData.delete_all [TestUpdateCallBack] + end + + def self.after_suite + GooTestData.delete_all [TestUpdateCallBack] + end + + + def test_update_callback + p = TestUpdateCallBack.new + p.code = "1" + p.name = "name" + p.first_name = "first_name" + p.last_name = "last_name" + + assert p.valid? + p.save + + p.bring_remaining + + assert_equal p.first_name + p.last_name, p.name + + p.last_name = "last_name2" + p.save + + p.bring_remaining + assert_equal "last_name2", p.last_name + assert_equal p.first_name + p.last_name, p.name + end + +end + From e4979ffbb8e1d203d0db9368be1871ba172a5b69 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Thu, 16 Mar 2023 02:22:46 +0100 Subject: [PATCH 060/110] implement enforce_callback to run an attribute callback --- lib/goo/validators/enforce.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index 1a53eaf0..3c90e204 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -64,6 +64,17 @@ def enforce(inst,attr,value) errors_by_opt.length > 0 ? errors_by_opt : nil end + def enforce_callback(inst, attr) + callbacks = Array(inst.class.update_callbacks(attr)) + callbacks.each do |proc| + if instance_proc?(inst, proc) + call_proc(inst.method(proc), inst, attr) + elsif proc.is_a?(Proc) + call_proc(proc, inst, attr) + end + end + end + private def object_type(opt) @@ -115,6 +126,10 @@ def add_error(opt, err) def self.enforce(inst,attr,value) EnforceInstance.new.enforce(inst,attr,value) end + + def self.enforce_callbacks(inst, attr) + EnforceInstance.new.enforce_callback(inst, attr) + end end end end From c7f7092a228b563d4637533131446015d0f36957 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Thu, 16 Mar 2023 02:24:19 +0100 Subject: [PATCH 061/110] move the attribute default callback to the save method --- lib/goo/base/resource.rb | 11 +++++++++++ lib/goo/sparql/triples.rb | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index 44cacdda..cdd2d755 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -236,6 +236,17 @@ def save(*opts) raise Goo::Base::NotValidException, "Object is not valid. Check errors." unless valid? end + #set default values before saving + unless self.persistent? + self.class.attributes_with_defaults.each do |attr| + value = self.send("#{attr}") + if value.nil? + value = self.class.default(attr).call(self) + self.send("#{attr}=", value) + end + end + end + graph_insert, graph_delete = Goo::SPARQL::Triples.model_update_triples(self) graph = self.graph() if graph_delete and graph_delete.size > 0 diff --git a/lib/goo/sparql/triples.rb b/lib/goo/sparql/triples.rb index cb840df9..df3f9f1d 100644 --- a/lib/goo/sparql/triples.rb +++ b/lib/goo/sparql/triples.rb @@ -67,16 +67,6 @@ def self.model_update_triples(model) unless model.persistent? graph_insert << [subject, RDF.type, model.class.uri_type(model.collection)] end - #set default values before saving - if not model.persistent? - model.class.attributes_with_defaults.each do |attr| - value = model.send("#{attr}") - if value.nil? - value = model.class.default(attr).call(model) - model.send("#{attr}=",value) - end - end - end model.modified_attributes.each do |attr| next if model.class.collection?(attr) From f81b15fd04c10e563d40bf0ab724fa9d810a18ce Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Thu, 16 Mar 2023 02:25:06 +0100 Subject: [PATCH 062/110] implement onUpdate DSL in the ressource settings --- lib/goo/base/settings/settings.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/goo/base/settings/settings.rb b/lib/goo/base/settings/settings.rb index 3d343d5d..5f669d08 100644 --- a/lib/goo/base/settings/settings.rb +++ b/lib/goo/base/settings/settings.rb @@ -96,6 +96,16 @@ def attributes_with_defaults select{ |attr,opts| opts[:default] }).keys() end + def attributes_with_update_callbacks + (@model_settings[:attributes]. + select{ |attr,opts| opts[:onUpdate] }).keys + end + + + def update_callbacks(attr) + @model_settings[:attributes][attr][:onUpdate] + end + def default(attr) return @model_settings[:attributes][attr][:default] end From 714f085983a2695f4ea8caf7dea00b8883139cb3 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Thu, 16 Mar 2023 02:25:49 +0100 Subject: [PATCH 063/110] call to the attributes onUpdate callback in the save method --- lib/goo/base/resource.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index cdd2d755..eddbf273 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -225,10 +225,13 @@ def save(*opts) 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 - if opts.first.is_a?(Hash) && opts.first[:batch] && opts.first[:batch].is_a?(File) + callbacks = true + if opts && opts.length > 0 && opts.first.is_a?(Hash) + if opts.first[:batch] && opts.first[:batch].is_a?(File) batch_file = opts.first[:batch] end + + callbacks = opts.first[:callbacks] end if !batch_file @@ -247,8 +250,17 @@ def save(*opts) end end + #call update callback before saving + if callbacks + self.class.attributes_with_update_callbacks.each do |attr| + Goo::Validators::Enforce.enforce_callbacks(self, attr) + end + end + graph_insert, graph_delete = Goo::SPARQL::Triples.model_update_triples(self) - graph = self.graph() + graph = self.graph + + if graph_delete and graph_delete.size > 0 begin Goo.sparql_update_client.delete_data(graph_delete, graph: graph) From f8caff72119c592ce77adc42520c3ee1924d0ebd Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 17 Mar 2023 03:30:39 +0100 Subject: [PATCH 064/110] in validators bring attribute if needed --- lib/goo/validators/validator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/goo/validators/validator.rb b/lib/goo/validators/validator.rb index c133c33e..22d453ac 100644 --- a/lib/goo/validators/validator.rb +++ b/lib/goo/validators/validator.rb @@ -75,6 +75,8 @@ def equivalent_value?(object1, object2) end def attr_value(attr, object) + object.bring attr if object.respond_to?(:bring?) && object.bring?(attr) + Array(object.send(attr)) end From 7de77a0b000b61735857f2cc60e2ebda6e5c7878 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sun, 19 Mar 2023 09:16:58 +0100 Subject: [PATCH 065/110] make superior_equal_to works for list attributes --- lib/goo/validators/implementations/superior_equal_to.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/goo/validators/implementations/superior_equal_to.rb b/lib/goo/validators/implementations/superior_equal_to.rb index 38012010..46676794 100644 --- a/lib/goo/validators/implementations/superior_equal_to.rb +++ b/lib/goo/validators/implementations/superior_equal_to.rb @@ -11,10 +11,10 @@ class SuperiorEqualTo < ValidatorBase validity_check -> (obj) do target_values = self.class.attr_value(@property, @inst) - target_value = target_values.first - return true if target_value.nil? || @value.nil? - return @value >= target_value + return true if target_values.nil? || target_values.empty? + + return Array(@value).all? {|v| v.nil? || target_values.all?{|t_v| v >= t_v}} end def initialize(inst, attr, value, key) From 5ca8b16ab99d4d714d22359885f0b0755b6dbb84 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sun, 19 Mar 2023 14:44:31 +0100 Subject: [PATCH 066/110] add email validator test --- test/test_validators.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_validators.rb b/test/test_validators.rb index a8e69dbe..5110da80 100644 --- a/test/test_validators.rb +++ b/test/test_validators.rb @@ -12,6 +12,7 @@ class Person < Goo::Base::Resource attribute :birth_date, enforce: [ :date_time ] attribute :male, enforce: [:boolean] attribute :social, enforce: [:uri] + attribute :email, enforce: [:email] attribute :socials, enforce: [:uri, :list] attribute :weight, enforce: [:float] attribute :friends, enforce: [Person, :list] @@ -146,8 +147,7 @@ def test_datatype_validators p.social = 100 p.socials = [100] p.weight = 100 - - + p.email = "test@test" #wrong types are not valid refute p.valid? assert p.errors[:last_name][:string] @@ -157,6 +157,7 @@ def test_datatype_validators assert p.errors[:birth_date][:date_time] assert p.errors[:male][:boolean] assert p.errors[:social][:uri] + assert p.errors[:email][:email] p.last_name = "hello" p.multiple_values = [22,11] @@ -166,6 +167,7 @@ def test_datatype_validators p.social = RDF::URI.new('https://test.com/') p.socials = [RDF::URI.new('https://test.com/'), RDF::URI.new('https://test.com/')] p.weight = 100.0 + p.email = "test@test.hi.com" #good types are valid assert p.valid? end From 6f4f50f82a14f6e9d1ddbd0d7b80b32632d205ea Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sun, 19 Mar 2023 14:44:46 +0100 Subject: [PATCH 067/110] implement email validator --- lib/goo/validators/enforce.rb | 2 ++ lib/goo/validators/implementations/email.rb | 22 +++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 lib/goo/validators/implementations/email.rb diff --git a/lib/goo/validators/enforce.rb b/lib/goo/validators/enforce.rb index 1a53eaf0..4d0c09f4 100644 --- a/lib/goo/validators/enforce.rb +++ b/lib/goo/validators/enforce.rb @@ -41,6 +41,8 @@ def enforce(inst,attr,value) check Goo::Validators::DataType, inst, attr, value, opt, Float when :symmetric check Goo::Validators::Symmetric, inst, attr, value, opt + when :email + check Goo::Validators::Email, inst, attr, value, opt when /^distinct_of_/ check Goo::Validators::DistinctOf, inst, attr, value, opt, opt when /^superior_equal_to_/ diff --git a/lib/goo/validators/implementations/email.rb b/lib/goo/validators/implementations/email.rb new file mode 100644 index 00000000..f8405714 --- /dev/null +++ b/lib/goo/validators/implementations/email.rb @@ -0,0 +1,22 @@ +module Goo + module Validators + class Email < ValidatorBase + include Validator + EMAIL_REGEXP = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i + key :email + + error_message ->(obj) { + if @value.kind_of? Array + return "All values in attribute `#{@attr}` must be a valid emails" + else + return "Attribute `#{@attr}` with the value `#{@value}` must be a valid email" + + end + } + + validity_check -> (obj) do + @value.nil? || @value.match?(EMAIL_REGEXP) + end + end + end +end \ No newline at end of file From f78687914f1ceae3de6139ac131754a4ed1d9873 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 22 Apr 2023 22:03:37 +0100 Subject: [PATCH 068/110] add filters patterns to select variables --- lib/goo/sparql/query_builder.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index a6e4f634..d44944dd 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -33,13 +33,12 @@ def build_select_query(ids, variables, graphs, patterns, @order_by, variables, optional_patterns = init_order_by(@count, @klass, @order_by, optional_patterns, variables) variables, patterns = add_some_type_to_id(patterns, query_options, variables) - query_filter_str, patterns, optional_patterns = + query_filter_str, patterns, optional_patterns, filter_variables = filter_query_strings(@collection, graphs, internal_variables, @klass, optional_patterns, patterns, @query_filters) - variables = [] if @count variables.delete :some_type - select_distinct(variables, aggregate_projections) + select_distinct(variables, aggregate_projections, filter_variables) .from(graphs) .where(patterns) .union_bind_in_where(properties_to_include) @@ -135,10 +134,10 @@ def from(graphs) self end - def select_distinct(variables, aggregate_projections) - + def select_distinct(variables, aggregate_projections, filter_variables) select_vars = variables.dup reject_aggregations_from_vars(select_vars, aggregate_projections) if aggregate_projections + select_vars = (select_vars + filter_variables).uniq if @page # Fix for 4store pagination with a filter @query = @query.select(*select_vars).distinct(true) self end @@ -347,8 +346,8 @@ def filter_query_strings(collection, graphs, internal_variables, klass, optional_patterns, patterns, query_filters) query_filter_str = [] - filter_graphs = [] + filter_variables = [] inspected_patterns = {} query_filters&.each do |query_filter| filter_operations = [] @@ -365,9 +364,9 @@ def filter_query_strings(collection, graphs, internal_variables, klass, patterns.concat(filter_patterns) end end + filter_variables << inspected_patterns.values.last end - - [query_filter_str, patterns, optional_patterns, internal_variables] + [query_filter_str, patterns, optional_patterns, filter_variables] end def reject_aggregations_from_vars(variables, aggregate_projections) From a613c80e48e60f4f3389e8ee1ad6e45551916434 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 22 Apr 2023 22:05:55 +0100 Subject: [PATCH 069/110] make make regex filter no-case sensitive --- lib/goo/sparql/query_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index a6e4f634..3cca37c4 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -321,7 +321,7 @@ def query_filter_sparql(klass, filter, filter_patterns, filter_graphs, return :optional when :regex if filter_operation.value.is_a?(String) - filter_operations << "REGEX(STR(?#{filter_var.to_s}) , \"#{filter_operation.value.to_s}\")" + filter_operations << "REGEX(STR(?#{filter_var.to_s}) , \"#{filter_operation.value.to_s}\", \"i\")" end else From 88676a15dae6afd64059226141983b8e50d5d8d4 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Sun, 23 Apr 2023 17:30:31 +0200 Subject: [PATCH 070/110] if requested_lang = 'all' return all --- lib/goo/sparql/mixins/solution_lang_filter.rb | 30 ++++++++++++++----- lib/goo/sparql/solutions_mapper.rb | 8 ++--- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 8102c82e..21c0af09 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -9,13 +9,12 @@ def initialize(requested_lang: nil, unmapped: false, list_attributes: []) @list_attributes = list_attributes @objects_by_lang = {} @unmapped = unmapped - @requested_lang = requested_lang @fill_other_languages = init_requested_lang - @requested_lang = @requested_lang.to_s.upcase.to_sym + @requested_lang = requested_lang end def enrich_models(models_by_id) - + return unless fill_other_languages? other_platform_languages = Goo.main_languages[1..] || [] @@ -43,15 +42,27 @@ def enrich_models(models_by_id) def set_model_value(model, predicate, objects, object) - language = object_language(object) # if lang is nil, it means that the object is not a literal - if language.nil? + + if requested_lang.eql?(:ALL) + return model.send("#{predicate}=", objects, on_load: true) + end + + unless is_literal(object) return model.send("#{predicate}=", objects, on_load: true) - elsif language_match?(language) + end + + # here the object is a literal (but it could be with no language) + + language = object_language(object) + + if language_match?(language) return if language.eql?(:no_lang) && !model.instance_variable_get("@#{predicate}").nil? && !objects.is_a?(Array) return model.send("#{predicate}=", objects, on_load: true) end + # here the object is a literal with a different language , so we store it for to enrich the model later with the other platform languages + store_objects_by_lang(model.id, predicate, object, language) end @@ -72,7 +83,8 @@ def object_language(new_value) end def language_match?(language) - !language.nil? && (language.eql?(requested_lang) || language.eql?(:no_lang) || requested_lang.nil?) + # no_lang means that the object is not a literal + language.eql?(requested_lang) || language.eql?(:no_lang) end def store_objects_by_lang(id, predicate, object, language) @@ -141,6 +153,10 @@ def fill_other_languages? @fill_other_languages end + def is_literal(object) + return object_language(object).nil? ? false : true + end + end end end diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 3117b39c..119f4113 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -22,7 +22,7 @@ def initialize(aggregate_projections, bnode_extraction, embed_struct, @incl = options[:include] @count = options[:count] @collection = options[:collection] - @requested_lang = options[:requested_lang] + @requested_lang = options[:requested_lang] || :ALL end def map_each_solutions(select) @@ -30,9 +30,9 @@ def map_each_solutions(select) objects_new = {} list_attributes = Set.new(@klass.attributes(:list)) all_attributes = Set.new(@klass.attributes(:all)) + @lang_filter = Goo::SPARQL::Solution::LanguageFilter.new(requested_lang: @requested_lang, unmapped: @unmapped, list_attributes: list_attributes) - select.each_solution do |sol| next if sol[:some_type] && @klass.type_uri(@collection) != sol[:some_type] @@ -74,8 +74,8 @@ def map_each_solutions(select) add_object_to_model(id, objects, object, predicate) end - - @lang_filter.enrich_models(@models_by_id) + # for this moment we are not going to enrich models , maybe we will use it if the results are empty + # @lang_filter.enrich_models(@models_by_id) init_unloaded_attributes(found, list_attributes) From 688ec5e8bdcba6b1f3bfaca7e4c2fd15f299d924 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Mon, 24 Apr 2023 17:25:40 +0200 Subject: [PATCH 071/110] support select multilanguage --- lib/goo/sparql/mixins/solution_lang_filter.rb | 11 ++++++++++- lib/goo/sparql/solutions_mapper.rb | 11 +++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 21c0af09..c3f78889 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -84,7 +84,16 @@ def object_language(new_value) def language_match?(language) # no_lang means that the object is not a literal - language.eql?(requested_lang) || language.eql?(:no_lang) + if language.eql?(:no_lang) + return true + end + + if requested_lang.is_a?(Array) + return requested_lang.include?(language) + end + + return language.eql?(requested_lang) + end def store_objects_by_lang(id, predicate, object, language) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 119f4113..9c97032f 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -4,7 +4,7 @@ class SolutionMapper BNODES_TUPLES = Struct.new(:id, :attribute) def initialize(aggregate_projections, bnode_extraction, embed_struct, - incl_embed, klass_struct, models_by_id, + incl_embed, klass_struct, models_by_id, properties_to_include, unmapped, variables, ids, options) @aggregate_projections = aggregate_projections @@ -22,7 +22,7 @@ def initialize(aggregate_projections, bnode_extraction, embed_struct, @incl = options[:include] @count = options[:count] @collection = options[:collection] - @requested_lang = options[:requested_lang] || :ALL + @requested_lang = get_language(options[:requested_lang].to_s) end def map_each_solutions(select) @@ -102,6 +102,13 @@ def map_each_solutions(select) private + def get_language(languages) + languages = 'ALL' if languages.nil? || languages.empty? + lang = languages.split(',').map {|l| l.upcase.to_sym} + return lang.length == 1 ? lang.first : lang + end + + def init_unloaded_attributes(found, list_attributes) return if @incl.nil? From 7871af7ef5fab2b41158edb1ba989a9f22c4aee9 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Wed, 26 Apr 2023 15:33:06 +0200 Subject: [PATCH 072/110] show the values with their corresponding language --- lib/goo/sparql/mixins/solution_lang_filter.rb | 55 ++++++------------- lib/goo/sparql/solutions_mapper.rb | 2 +- 2 files changed, 19 insertions(+), 38 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index c3f78889..60eae002 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -15,55 +15,31 @@ def initialize(requested_lang: nil, unmapped: false, list_attributes: []) def enrich_models(models_by_id) - return unless fill_other_languages? - - other_platform_languages = Goo.main_languages[1..] || [] - + ## if the requested language is ALL, we can enrich the models with the objects by language objects_by_lang.each do |id, predicates| model = models_by_id[id] - predicates.each do |predicate, languages| - model_attribute_val = get_model_attribute_value(model, predicate) - next unless model_attribute_val.nil? || model_attribute_val.empty? - - other_platform_languages.each do |platform_language| - if languages[platform_language.to_s.upcase.to_sym] - save_model_values(model, languages[platform_language], predicate, unmapped) - break - end - end - model_attribute_val = get_model_attribute_value(model, predicate) - if model_attribute_val.nil? || model_attribute_val.empty? - save_model_values(model, languages.values.flatten.uniq, predicate, unmapped) + predicates.each do |predicate, values| + if predicate.eql?(:synonym) || predicate.eql?(:prefLabel) + save_model_values(model, values, predicate, unmapped) end end - end - + end end def set_model_value(model, predicate, objects, object) - if requested_lang.eql?(:ALL) - return model.send("#{predicate}=", objects, on_load: true) - end - - unless is_literal(object) - return model.send("#{predicate}=", objects, on_load: true) - end - - # here the object is a literal (but it could be with no language) - language = object_language(object) - if language_match?(language) - return if language.eql?(:no_lang) && !model.instance_variable_get("@#{predicate}").nil? && !objects.is_a?(Array) + if requested_lang.eql?(:ALL) || !is_literal(object) || language_match?(language) + model.send("#{predicate}=", objects, on_load: true) + end - return model.send("#{predicate}=", objects, on_load: true) + if requested_lang.eql?(:ALL) || requested_lang.is_a?(Array) + language = "@none" if language.nil? || language.eql?(:no_lang) + store_objects_by_lang(model.id, predicate, object, language) end - # here the object is a literal with a different language , so we store it for to enrich the model later with the other platform languages - - store_objects_by_lang(model.id, predicate, object, language) end def model_set_unmapped(model, predicate, value) @@ -99,6 +75,8 @@ def language_match?(language) def store_objects_by_lang(id, predicate, object, language) # store objects in this format: [id][predicate][language] = [objects] + return if requested_lang.is_a?(Array) && !requested_lang.include?(language) + objects_by_lang[id] ||= {} objects_by_lang[id][predicate] ||= {} objects_by_lang[id][predicate][language] ||= [] @@ -138,8 +116,11 @@ def save_model_values(model, values, predicate, unmapped) if unmapped add_unmapped_to_model(model, predicate, values) else - values = Array(values).map(&:object) - values = values.min unless list_attributes?(predicate) + + if !list_attributes?(predicate) + values = values.map { |k, v| [k, v.first] }.to_h + end + model.send("#{predicate}=", values, on_load: true) end end diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 9c97032f..17c00a24 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -75,7 +75,7 @@ def map_each_solutions(select) end # for this moment we are not going to enrich models , maybe we will use it if the results are empty - # @lang_filter.enrich_models(@models_by_id) + @lang_filter.enrich_models(@models_by_id) init_unloaded_attributes(found, list_attributes) From dd5f3ef1d05bf6b29d04ec0bea21f2f8fdf53b51 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Wed, 26 Apr 2023 15:39:14 +0200 Subject: [PATCH 073/110] use @attributes_to_translate --- lib/goo/sparql/mixins/solution_lang_filter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 60eae002..272f2cdc 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -6,6 +6,7 @@ class LanguageFilter attr_reader :requested_lang, :unmapped, :objects_by_lang def initialize(requested_lang: nil, unmapped: false, list_attributes: []) + @attributes_to_translate = [:synonym, :prefLabel] @list_attributes = list_attributes @objects_by_lang = {} @unmapped = unmapped @@ -19,7 +20,7 @@ def enrich_models(models_by_id) objects_by_lang.each do |id, predicates| model = models_by_id[id] predicates.each do |predicate, values| - if predicate.eql?(:synonym) || predicate.eql?(:prefLabel) + if @attributes_to_translate.any? { |attr| predicate.eql?(attr) } save_model_values(model, values, predicate, unmapped) end end From 5f81e7b624af2b1bbb09f9434ff86111862fb4bc Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Fri, 28 Apr 2023 14:17:21 +0200 Subject: [PATCH 074/110] change methode name --- lib/goo/sparql/mixins/solution_lang_filter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 272f2cdc..f705db78 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -32,7 +32,7 @@ def set_model_value(model, predicate, objects, object) language = object_language(object) - if requested_lang.eql?(:ALL) || !is_literal(object) || language_match?(language) + if requested_lang.eql?(:ALL) || !literal?(object) || language_match?(language) model.send("#{predicate}=", objects, on_load: true) end @@ -144,7 +144,7 @@ def fill_other_languages? @fill_other_languages end - def is_literal(object) + def literal?(object) return object_language(object).nil? ? false : true end From bb685a207e5b552fddd5c272076bb10984e1b6ae Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Fri, 28 Apr 2023 14:20:34 +0200 Subject: [PATCH 075/110] remove platform languages --- lib/goo/sparql/mixins/solution_lang_filter.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index f705db78..d801fb09 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -10,7 +10,6 @@ def initialize(requested_lang: nil, unmapped: false, list_attributes: []) @list_attributes = list_attributes @objects_by_lang = {} @unmapped = unmapped - @fill_other_languages = init_requested_lang @requested_lang = requested_lang end @@ -85,14 +84,6 @@ def store_objects_by_lang(id, predicate, object, language) objects_by_lang[id][predicate][language] << object end - def init_requested_lang - if @requested_lang.nil? - @requested_lang = Goo.main_languages[0] || :EN - return true - end - - false - end def get_model_attribute_value(model, predicate) if unmapped @@ -140,10 +131,6 @@ def list_attributes?(predicate) end - def fill_other_languages? - @fill_other_languages - end - def literal?(object) return object_language(object).nil? ? false : true end From 778b696c3dc193443b1d2ff08853bcbcd4709d0a Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Mon, 1 May 2023 18:33:51 +0200 Subject: [PATCH 076/110] add complex_order_by unit test --- test/test_where.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test_where.rb b/test/test_where.rb index bca4b2ea..30d933e3 100644 --- a/test/test_where.rb +++ b/test/test_where.rb @@ -600,4 +600,15 @@ def test_include_inverse_with_find end end + def test_complex_order_by + u = University.where.include(address: [:country]).order_by(address: {country: :asc}).all + countries = u.map {|x| x.address.map{|a| a.country}}.flatten + assert_equal countries.sort, countries + + + u = University.where.include(address: [:country]).order_by(address: {country: :desc}).all + countries = u.map {|x| x.address.map{|a| a.country}}.flatten + assert_equal countries.sort{|a,b| b<=>a }, countries + end + end From b7a4c56d17587cca1433c65bd74b17451a6475d3 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Mon, 1 May 2023 19:37:44 +0200 Subject: [PATCH 077/110] refactor query_builder to extract internal_variables as instance variable --- lib/goo/sparql/query_builder.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index 5227dac2..9cbb5636 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -17,24 +17,25 @@ def initialize(options) @model_query_options = options[:query_options] @enable_rules = options[:rules] @order_by = options[:order_by] - + @internal_variables_map = {} @query = get_client end def build_select_query(ids, variables, graphs, patterns, query_options, properties_to_include) - internal_variables = graph_match(@collection, @graph_match, graphs, @klass, patterns, query_options, @unions) + patterns = graph_match(@collection, @graph_match, graphs, @klass, patterns, query_options, @unions) aggregate_projections, aggregate_vars, variables, optional_patterns = get_aggregate_vars(@aggregate, @collection, graphs, - @klass, @unions, variables, internal_variables) + @klass, @unions, variables) @order_by, variables, optional_patterns = init_order_by(@count, @klass, @order_by, optional_patterns, variables) variables, patterns = add_some_type_to_id(patterns, query_options, variables) query_filter_str, patterns, optional_patterns, filter_variables = - filter_query_strings(@collection, graphs, internal_variables, @klass, optional_patterns, patterns, @query_filters) + filter_query_strings(@collection, graphs, @klass, optional_patterns, patterns, @query_filters) + variables = [] if @count variables.delete :some_type @@ -169,6 +170,7 @@ def patterns_for_match(klass, attr, value, graphs, patterns, unions, value = "#{attr}_agg_#{in_aggregate}".to_sym end internal_variables << value + @internal_variables_map[attr] = value end add_rules(attr, klass, query_options) @@ -209,7 +211,7 @@ def walk_pattern(klass, match_patterns, graphs, patterns, unions, end end - def get_aggregate_vars(aggregate, collection, graphs, klass, unions, variables, internal_variables) + def get_aggregate_vars(aggregate, collection, graphs, klass, unions, variables) # mdorf, 6/03/20 If aggregate projections (sub-SELECT within main SELECT) use an alias, that alias cannot appear in the main SELECT # https://github.com/ncbo/goo/issues/106 # See last sentence in https://www.w3.org/TR/sparql11-query/#aggregateExample @@ -240,8 +242,6 @@ def get_aggregate_vars(aggregate, collection, graphs, klass, unions, variables, end def graph_match(collection, graph_match, graphs, klass, patterns, query_options, unions) - internal_variables = [] - if graph_match #make it deterministic - for caching graph_match_iteration = Goo::Base::PatternIteration.new(graph_match) @@ -249,7 +249,7 @@ def graph_match(collection, graph_match, graphs, klass, patterns, query_options, internal_variables, in_aggregate = false, query_options, collection) graphs.uniq! end - internal_variables + patterns end def get_client @@ -342,7 +342,7 @@ def query_filter_sparql(klass, filter, filter_patterns, filter_graphs, end end - def filter_query_strings(collection, graphs, internal_variables, klass, + def filter_query_strings(collection, graphs, klass, optional_patterns, patterns, query_filters) query_filter_str = [] @@ -382,6 +382,9 @@ def add_some_type_to_id(patterns, query_options, variables) [variables, patterns] end + def internal_variables + @internal_variables_map.values + end end end end From 4df6681401ca332c4a8f91042782787dc0c36b8f Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Mon, 1 May 2023 19:38:37 +0200 Subject: [PATCH 078/110] update order_by to work for joined patterns (object attributes) --- lib/goo/sparql/query_builder.rb | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index 9cbb5636..40e888d0 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -30,7 +30,7 @@ def build_select_query(ids, variables, graphs, patterns, variables, optional_patterns = get_aggregate_vars(@aggregate, @collection, graphs, @klass, @unions, variables) - @order_by, variables, optional_patterns = init_order_by(@count, @klass, @order_by, optional_patterns, variables) + @order_by, variables, optional_patterns = init_order_by(@count, @klass, @order_by, optional_patterns, variables,patterns, query_options, graphs) variables, patterns = add_some_type_to_id(patterns, query_options, variables) query_filter_str, patterns, optional_patterns, filter_variables = @@ -55,7 +55,7 @@ def build_select_query(ids, variables, graphs, patterns, @query.union(*@unions) unless @unions.empty? ids_filter(ids) if ids - order_by if @order_by # TODO test if work + order_by if @order_by put_query_aggregate_vars(aggregate_vars) if aggregate_vars count if @count @@ -118,7 +118,13 @@ def put_query_aggregate_vars(aggregate_vars) end def order_by - order_by_str = @order_by.map { |attr, order| "#{order.to_s.upcase}(?#{attr})" } + order_by_str = @order_by.map do |attr, order| + if order.is_a?(Hash) + sub_attr, order = order.first + attr = @internal_variables_map[sub_attr] + end + "#{order.to_s.upcase}(?#{attr})" + end @query.order_by(*order_by_str) self end @@ -256,21 +262,31 @@ def get_client Goo.sparql_query_client(@store) end - def init_order_by(count, klass, order_by, optional_patterns, variables) + def init_order_by(count, klass, order_by, optional_patterns, variables, patterns, query_options, graphs) order_by = nil if count if order_by order_by = order_by.first #simple ordering ... needs to use pattern inspection order_by.each do |attr, direction| - quad = query_pattern(klass, attr) - optional_patterns << quad[1] + + if direction.is_a?(Hash) + sub_attr, direction = direction.first + graph_match_iteration = Goo::Base::PatternIteration.new(Goo::Base::Pattern.new({attr => [sub_attr]})) + old_internal = internal_variables.dup + walk_pattern(klass, graph_match_iteration, graphs, optional_patterns, @unions, internal_variables, in_aggregate = false, query_options, @collection) + variables << (internal_variables - old_internal).last + else + quad = query_pattern(klass, attr) + optional_patterns << quad[1] + variables << attr + end + #patterns << quad[1] #mdorf, 9/22/16 If an ORDER BY clause exists, the columns used in the ORDER BY should be present in the SPARQL select #variables << attr unless variables.include?(attr) end - variables = %i[id attributeProperty attributeObject] end - [order_by, variables, optional_patterns] + [order_by, variables, optional_patterns, patterns] end def sparql_op_string(op) From 7ffeccdf6b6c79611dc1a36639ad525fab2dbc95 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Tue, 9 May 2023 14:01:49 +0200 Subject: [PATCH 079/110] downcase lang key --- lib/goo/sparql/mixins/solution_lang_filter.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index d801fb09..4d5f2b24 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -6,7 +6,7 @@ class LanguageFilter attr_reader :requested_lang, :unmapped, :objects_by_lang def initialize(requested_lang: nil, unmapped: false, list_attributes: []) - @attributes_to_translate = [:synonym, :prefLabel] + @attributes_to_translate = [:synonym, :prefLabel, :definition, :cui, :semanticType, :obsolete, :inScheme, :memberOf, :created, :modified, :links] @list_attributes = list_attributes @objects_by_lang = {} @unmapped = unmapped @@ -19,8 +19,8 @@ def enrich_models(models_by_id) objects_by_lang.each do |id, predicates| model = models_by_id[id] predicates.each do |predicate, values| - if @attributes_to_translate.any? { |attr| predicate.eql?(attr) } - save_model_values(model, values, predicate, unmapped) + if !@attributes_to_translate.any? { |attr| predicate.eql?(attr) } + save_model_values(model, values, predicate, unmapped) end end end @@ -77,11 +77,13 @@ def store_objects_by_lang(id, predicate, object, language) return if requested_lang.is_a?(Array) && !requested_lang.include?(language) + language_key = language.downcase + objects_by_lang[id] ||= {} objects_by_lang[id][predicate] ||= {} - objects_by_lang[id][predicate][language] ||= [] + objects_by_lang[id][predicate][language_key] ||= [] - objects_by_lang[id][predicate][language] << object + objects_by_lang[id][predicate][language_key] << object end From 3967febae703a659a09725a5c5ccd59c4c1ff9f7 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Wed, 10 May 2023 06:27:45 +0200 Subject: [PATCH 080/110] Fix the issue of undefined 'id' of the language filter module --- lib/goo/sparql/mixins/solution_lang_filter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 4d5f2b24..0f2edec6 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -6,7 +6,7 @@ class LanguageFilter attr_reader :requested_lang, :unmapped, :objects_by_lang def initialize(requested_lang: nil, unmapped: false, list_attributes: []) - @attributes_to_translate = [:synonym, :prefLabel, :definition, :cui, :semanticType, :obsolete, :inScheme, :memberOf, :created, :modified, :links] + @attributes_to_translate = [:synonym, :prefLabel, :definition] @list_attributes = list_attributes @objects_by_lang = {} @unmapped = unmapped @@ -19,7 +19,7 @@ def enrich_models(models_by_id) objects_by_lang.each do |id, predicates| model = models_by_id[id] predicates.each do |predicate, values| - if !@attributes_to_translate.any? { |attr| predicate.eql?(attr) } + if @attributes_to_translate.any? { |attr| predicate.eql?(attr) } save_model_values(model, values, predicate, unmapped) end end From 78e6420388f671aff68b226410d56e8887e39425 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Wed, 10 May 2023 06:28:17 +0200 Subject: [PATCH 081/110] Show literal attribute if we requested all the languages --- lib/goo/sparql/mixins/solution_lang_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 0f2edec6..59a2c8bf 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -44,7 +44,7 @@ def set_model_value(model, predicate, objects, object) def model_set_unmapped(model, predicate, value) language = object_language(value) - if language.nil? || language_match?(language) + if requested_lang.eql?(:ALL) || language.nil? || language_match?(language) return add_unmapped_to_model(model, predicate, value) end From 7e1410c2bc349bc3eacca6882e9386d6db0b9e53 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Wed, 10 May 2023 06:36:43 +0200 Subject: [PATCH 082/110] Use portal language by default in the language filter module --- lib/goo/sparql/solutions_mapper.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 17c00a24..620e1182 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -103,11 +103,14 @@ def map_each_solutions(select) private def get_language(languages) - languages = 'ALL' if languages.nil? || languages.empty? + languages = portal_language if languages.nil? || languages.empty? lang = languages.split(',').map {|l| l.upcase.to_sym} - return lang.length == 1 ? lang.first : lang + lang.length == 1 ? lang.first : lang end + def portal_language + Goo.main_languages.first + end def init_unloaded_attributes(found, list_attributes) return if @incl.nil? From 40df9b009cd7a7a2124f59940887c0e346758ca9 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Thu, 1 Jun 2023 16:37:23 +0200 Subject: [PATCH 083/110] group unmapped properties by lang --- lib/goo/base/resource.rb | 3 ++- lib/goo/sparql/mixins/solution_lang_filter.rb | 15 +++++++++++++++ lib/goo/sparql/solutions_mapper.rb | 16 ++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index eddbf273..083ef27e 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -15,7 +15,7 @@ class Resource attr_reader :modified_attributes attr_reader :errors attr_reader :aggregates - attr_reader :unmapped + attr_accessor :unmapped attr_reader :id @@ -129,6 +129,7 @@ def unmapped_get(attribute) def unmmaped_to_array cpy = {} + @unmapped.each do |attr,v| cpy[attr] = v.to_a end diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 59a2c8bf..3d4a66b8 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -51,9 +51,24 @@ def model_set_unmapped(model, predicate, value) store_objects_by_lang(model.id, predicate, value, language) end + def model_group_by_lang(model, requested_lang) + unmapped = model.unmapped + cpy = {} + + unmapped.each do |attr, v| + cpy[attr] = is_a_uri?(v.first) ? v.to_a : v.group_by { |x| x.language.to_s } + end + + model.unmapped = cpy + end + private + def is_a_uri?(value) + value.is_a?(RDF::URI) && value.valid? + end + def object_language(new_value) new_value.language || :no_lang if new_value.is_a?(RDF::Literal) end diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 620e1182..6361eb9c 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -35,6 +35,7 @@ def map_each_solutions(select) list_attributes: list_attributes) select.each_solution do |sol| + next if sol[:some_type] && @klass.type_uri(@collection) != sol[:some_type] return sol[:count_var].object if @count @@ -96,7 +97,8 @@ def map_each_solutions(select) include_bnodes(blank_nodes, @models_by_id) unless blank_nodes.empty? models_unmapped_to_array(@models_by_id) if @unmapped - + + @models_by_id end @@ -269,10 +271,20 @@ def create_class_model(id, klass, klass_struct) def models_unmapped_to_array(models_by_id) models_by_id.each do |_idm, m| - m.unmmaped_to_array + if is_multiple_langs? + @lang_filter.model_group_by_lang(m, @requested_lang) + else + m.unmmaped_to_array + end end end + + def is_multiple_langs? + return true if @requested_lang.is_a?(Array) || @requested_lang.eql?(:ALL) + false + end + def include_bnodes(bnodes, models_by_id) # group by attribute attrs = bnodes.map { |_x, y| y.attribute }.uniq From cbd0186490813e968a62196982481a3afbf7f277 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Tue, 6 Jun 2023 16:52:58 +0200 Subject: [PATCH 084/110] Feature: group unmapped properties by language (#38) * group unmapped properties by lang * downcase language keys of unmapped properties --------- Co-authored-by: Syphax bouazzouni --- lib/goo/sparql/mixins/solution_lang_filter.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 3d4a66b8..de41c7a8 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -56,7 +56,7 @@ def model_group_by_lang(model, requested_lang) cpy = {} unmapped.each do |attr, v| - cpy[attr] = is_a_uri?(v.first) ? v.to_a : v.group_by { |x| x.language.to_s } + cpy[attr] = group_by_lang(v) end model.unmapped = cpy @@ -65,6 +65,18 @@ def model_group_by_lang(model, requested_lang) private + def group_by_lang(values) + + return values.to_a if is_a_uri?(values.first) + + values = values.group_by { |x| x.language ? x.language.to_s.downcase : :none } + + no_lang = values[:none] || [] + return no_lang if !no_lang.empty? && no_lang.all? { |x| !x.plain? } + + values + end + def is_a_uri?(value) value.is_a?(RDF::URI) && value.valid? end From e0bf04f4231250cea9aa6c25177b7b6f2ff9dabd Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Tue, 13 Jun 2023 16:17:49 +0200 Subject: [PATCH 085/110] assert that pre in an array in get_value_object --- lib/goo/sparql/solutions_mapper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 6361eb9c..1e6da4b4 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -166,8 +166,8 @@ def get_value_object(id, objects_new, object, list_attributes, predicate) if object.nil? object = pre.nil? ? [] : pre - else - object = pre.nil? ? [object] : (pre.dup << object) + else + object = pre.nil? ? [object] : (Array(pre).dup << object) object.uniq! end From 39b7f2f2e9c398b981fb03feaa1eebc270877341 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Tue, 13 Jun 2023 16:18:05 +0200 Subject: [PATCH 086/110] add label to attributes_to_translate --- lib/goo/sparql/mixins/solution_lang_filter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index de41c7a8..22a109ce 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -6,7 +6,7 @@ class LanguageFilter attr_reader :requested_lang, :unmapped, :objects_by_lang def initialize(requested_lang: nil, unmapped: false, list_attributes: []) - @attributes_to_translate = [:synonym, :prefLabel, :definition] + @attributes_to_translate = [:synonym, :prefLabel, :definition, :label] @list_attributes = list_attributes @objects_by_lang = {} @unmapped = unmapped @@ -15,7 +15,6 @@ def initialize(requested_lang: nil, unmapped: false, list_attributes: []) def enrich_models(models_by_id) - ## if the requested language is ALL, we can enrich the models with the objects by language objects_by_lang.each do |id, predicates| model = models_by_id[id] predicates.each do |predicate, values| From 8af1d47029e203cf006025ca54180cfd6fa53956 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Tue, 20 Jun 2023 19:07:28 +0200 Subject: [PATCH 087/110] update define_method --- lib/goo/base/settings/settings.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/goo/base/settings/settings.rb b/lib/goo/base/settings/settings.rb index 5f669d08..3f5780a8 100644 --- a/lib/goo/base/settings/settings.rb +++ b/lib/goo/base/settings/settings.rb @@ -274,9 +274,13 @@ def shape_attribute(attr) self.instance_variable_set("@#{attr}",value) end define_method("#{attr}") do |*args| + attr_value = self.instance_variable_get("@#{attr}") + attr_value = attr_value.values.first if attr_value.is_a?(Hash) && !args.include?(:show_with_language) + + if self.class.handler?(attr) if @loaded_attributes.include?(attr) - return self.instance_variable_get("@#{attr}") + return attr_value end value = self.send("#{self.class.handler(attr)}") self.instance_variable_set("@#{attr}",value) @@ -285,7 +289,7 @@ def shape_attribute(attr) end if (not @persistent) or @loaded_attributes.include?(attr) - return self.instance_variable_get("@#{attr}") + return attr_value else # TODO: bug here when no labels from one of the main_lang available... (when it is called by ontologies_linked_data ontologies_submission) raise Goo::Base::AttributeNotLoaded, "Attribute `#{attr}` is not loaded for #{self.id}. Loaded attributes: #{@loaded_attributes.inspect}." From 5b7c78f67449512c511e8a4b752ca2ee724d6786 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Tue, 20 Jun 2023 19:07:46 +0200 Subject: [PATCH 088/110] update solution mapper --- lib/goo/sparql/mixins/solution_lang_filter.rb | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 22a109ce..fde181b9 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -17,10 +17,12 @@ def enrich_models(models_by_id) objects_by_lang.each do |id, predicates| model = models_by_id[id] - predicates.each do |predicate, values| - if @attributes_to_translate.any? { |attr| predicate.eql?(attr) } - save_model_values(model, values, predicate, unmapped) - end + predicates.each do |predicate, values| + + if values.values.all? { |v| v.all? { |x| literal?(x) && x.plain?} } + save_model_values(model, values, predicate, unmapped) + + end end end end @@ -100,7 +102,6 @@ def language_match?(language) def store_objects_by_lang(id, predicate, object, language) # store objects in this format: [id][predicate][language] = [objects] - return if requested_lang.is_a?(Array) && !requested_lang.include?(language) language_key = language.downcase @@ -123,6 +124,7 @@ def get_model_attribute_value(model, predicate) def add_unmapped_to_model(model, predicate, value) + if model.respond_to? :klass # struct model[:unmapped] ||= {} model[:unmapped][predicate] ||= [] @@ -133,16 +135,10 @@ def add_unmapped_to_model(model, predicate, value) end def save_model_values(model, values, predicate, unmapped) - if unmapped - add_unmapped_to_model(model, predicate, values) - else - - if !list_attributes?(predicate) - values = values.map { |k, v| [k, v.first] }.to_h - end + add_unmapped_to_model(model, predicate, values) if unmapped + values = values.map { |k, v| [k, v.first] }.to_h unless list_attributes?(predicate) - model.send("#{predicate}=", values, on_load: true) - end + model.send("#{predicate}=", values, on_load: true) end def unmapped_get(model, predicate) From 3161fbb11f6f1721204b38f0e5f76e53f8aa109f Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Tue, 20 Jun 2023 19:08:00 +0200 Subject: [PATCH 089/110] update get_preload_value --- lib/goo/sparql/solutions_mapper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 1e6da4b4..1f6e234c 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -98,7 +98,7 @@ def map_each_solutions(select) models_unmapped_to_array(@models_by_id) if @unmapped - + @models_by_id end @@ -189,8 +189,8 @@ def add_object_to_model(id, objects, current_obj, predicate) def get_preload_value(id, object, predicate) pre_val = nil if predicate_preloaded?(id, predicate) - pre_val = preloaded_value(id, predicate) - pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) + pre_val = preloaded_value(id, predicate) + pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) end pre_val end From 6c1790433c6897dd398dce89ff724646e1c7e6af Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Wed, 28 Jun 2023 02:29:45 +0200 Subject: [PATCH 090/110] Feature: Support multi lingual - add show_language argument to the attributes getters (#39) * update define_method * update solution mapper * update get_preload_value --- lib/goo/base/settings/settings.rb | 8 +++++-- lib/goo/sparql/mixins/solution_lang_filter.rb | 24 ++++++++----------- lib/goo/sparql/solutions_mapper.rb | 6 ++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/goo/base/settings/settings.rb b/lib/goo/base/settings/settings.rb index 5f669d08..3f5780a8 100644 --- a/lib/goo/base/settings/settings.rb +++ b/lib/goo/base/settings/settings.rb @@ -274,9 +274,13 @@ def shape_attribute(attr) self.instance_variable_set("@#{attr}",value) end define_method("#{attr}") do |*args| + attr_value = self.instance_variable_get("@#{attr}") + attr_value = attr_value.values.first if attr_value.is_a?(Hash) && !args.include?(:show_with_language) + + if self.class.handler?(attr) if @loaded_attributes.include?(attr) - return self.instance_variable_get("@#{attr}") + return attr_value end value = self.send("#{self.class.handler(attr)}") self.instance_variable_set("@#{attr}",value) @@ -285,7 +289,7 @@ def shape_attribute(attr) end if (not @persistent) or @loaded_attributes.include?(attr) - return self.instance_variable_get("@#{attr}") + return attr_value else # TODO: bug here when no labels from one of the main_lang available... (when it is called by ontologies_linked_data ontologies_submission) raise Goo::Base::AttributeNotLoaded, "Attribute `#{attr}` is not loaded for #{self.id}. Loaded attributes: #{@loaded_attributes.inspect}." diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 22a109ce..fde181b9 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -17,10 +17,12 @@ def enrich_models(models_by_id) objects_by_lang.each do |id, predicates| model = models_by_id[id] - predicates.each do |predicate, values| - if @attributes_to_translate.any? { |attr| predicate.eql?(attr) } - save_model_values(model, values, predicate, unmapped) - end + predicates.each do |predicate, values| + + if values.values.all? { |v| v.all? { |x| literal?(x) && x.plain?} } + save_model_values(model, values, predicate, unmapped) + + end end end end @@ -100,7 +102,6 @@ def language_match?(language) def store_objects_by_lang(id, predicate, object, language) # store objects in this format: [id][predicate][language] = [objects] - return if requested_lang.is_a?(Array) && !requested_lang.include?(language) language_key = language.downcase @@ -123,6 +124,7 @@ def get_model_attribute_value(model, predicate) def add_unmapped_to_model(model, predicate, value) + if model.respond_to? :klass # struct model[:unmapped] ||= {} model[:unmapped][predicate] ||= [] @@ -133,16 +135,10 @@ def add_unmapped_to_model(model, predicate, value) end def save_model_values(model, values, predicate, unmapped) - if unmapped - add_unmapped_to_model(model, predicate, values) - else - - if !list_attributes?(predicate) - values = values.map { |k, v| [k, v.first] }.to_h - end + add_unmapped_to_model(model, predicate, values) if unmapped + values = values.map { |k, v| [k, v.first] }.to_h unless list_attributes?(predicate) - model.send("#{predicate}=", values, on_load: true) - end + model.send("#{predicate}=", values, on_load: true) end def unmapped_get(model, predicate) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 1e6da4b4..1f6e234c 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -98,7 +98,7 @@ def map_each_solutions(select) models_unmapped_to_array(@models_by_id) if @unmapped - + @models_by_id end @@ -189,8 +189,8 @@ def add_object_to_model(id, objects, current_obj, predicate) def get_preload_value(id, object, predicate) pre_val = nil if predicate_preloaded?(id, predicate) - pre_val = preloaded_value(id, predicate) - pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) + pre_val = preloaded_value(id, predicate) + pre_val = pre_val.select { |x| x.id == object }.first if pre_val.is_a?(Array) end pre_val end From cca637e29efeb40867ee47806e0e08fc32609f25 Mon Sep 17 00:00:00 2001 From: HADDAD Zineddine Date: Thu, 13 Jul 2023 18:45:41 +0200 Subject: [PATCH 091/110] fix save_model_values if unmmaped condition --- lib/goo/sparql/mixins/solution_lang_filter.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index fde181b9..746aece4 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -135,10 +135,15 @@ def add_unmapped_to_model(model, predicate, value) end def save_model_values(model, values, predicate, unmapped) - add_unmapped_to_model(model, predicate, values) if unmapped - values = values.map { |k, v| [k, v.first] }.to_h unless list_attributes?(predicate) + if unmapped + add_unmapped_to_model(model, predicate, values) + + else + values = values.map { |k, v| [k, v.first] }.to_h unless list_attributes?(predicate) + + model.send("#{predicate}=", values, on_load: true) + end - model.send("#{predicate}=", values, on_load: true) end def unmapped_get(model, predicate) From 94959cd3c34c2d7d6fa27b9220aa908d67114cd3 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 18:02:36 +0200 Subject: [PATCH 092/110] fix getters for list attributes to not take only the first value --- lib/goo/base/settings/settings.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/goo/base/settings/settings.rb b/lib/goo/base/settings/settings.rb index 3f5780a8..6c416298 100644 --- a/lib/goo/base/settings/settings.rb +++ b/lib/goo/base/settings/settings.rb @@ -275,7 +275,12 @@ def shape_attribute(attr) end define_method("#{attr}") do |*args| attr_value = self.instance_variable_get("@#{attr}") - attr_value = attr_value.values.first if attr_value.is_a?(Hash) && !args.include?(:show_with_language) + + if self.class.not_show_all_languages?(attr_value, args) + is_array = attr_value.values.first.is_a?(Array) + attr_value = attr_value.values.flatten + attr_value = attr_value.first unless is_array + end if self.class.handler?(attr) @@ -395,6 +400,14 @@ def read_only(attributes) instance end + def show_all_languages?(args) + args.include?(:show_with_language) + end + + def not_show_all_languages?(values, args) + values.is_a?(Hash) && !show_all_languages?(args) + end + private def set_no_list_by_default(options) From 37da0d36cd59de4cf477da07dbdbda41003b4c43 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 18:03:25 +0200 Subject: [PATCH 093/110] remove the languages hash for the unmapped if not a mutli langual asked --- lib/goo/base/resource.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index 083ef27e..633c9594 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -15,7 +15,7 @@ class Resource attr_reader :modified_attributes attr_reader :errors attr_reader :aggregates - attr_accessor :unmapped + attr_writer :unmapped attr_reader :id @@ -136,6 +136,12 @@ def unmmaped_to_array @unmapped = cpy end + def unmapped(*args) + @unmapped.transform_values do |language_values| + self.class.not_show_all_languages?(language_values, args) ? language_values.values.flatten: language_values + end + end + def delete(*args) if self.kind_of?(Goo::Base::Enum) raise ArgumentError, "Enums cannot be deleted" unless args[0] && args[0][:init_enum] From a65048089f1ddae6fed7e1fffecce64bb96df450 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 18:05:28 +0200 Subject: [PATCH 094/110] move some language helper from the mapper to the lang_filter module --- lib/goo/sparql/mixins/solution_lang_filter.rb | 21 +++++++++++++++++-- lib/goo/sparql/solutions_mapper.rb | 16 +------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index fde181b9..127da1a4 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -63,6 +63,13 @@ def model_group_by_lang(model, requested_lang) model.unmapped = cpy end + def models_unmapped_to_array(m) + if show_all_languages? + model_group_by_lang(m) + else + m.unmmaped_to_array + end + end private @@ -155,8 +162,18 @@ def list_attributes?(predicate) end - def literal?(object) - return object_language(object).nil? ? false : true + def show_all_languages? + @requested_lang.is_a?(Array) || @requested_lang.eql?(:ALL) + end + + def get_language(languages) + languages = portal_language if languages.nil? || languages.empty? + lang = languages.to_s.split(',').map { |l| l.upcase.to_sym } + lang.length == 1 ? lang.first : lang + end + + def portal_language + Goo.main_languages.first end end diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 1f6e234c..4cfd9104 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -104,16 +104,6 @@ def map_each_solutions(select) private - def get_language(languages) - languages = portal_language if languages.nil? || languages.empty? - lang = languages.split(',').map {|l| l.upcase.to_sym} - lang.length == 1 ? lang.first : lang - end - - def portal_language - Goo.main_languages.first - end - def init_unloaded_attributes(found, list_attributes) return if @incl.nil? @@ -271,11 +261,7 @@ def create_class_model(id, klass, klass_struct) def models_unmapped_to_array(models_by_id) models_by_id.each do |_idm, m| - if is_multiple_langs? - @lang_filter.model_group_by_lang(m, @requested_lang) - else - m.unmmaped_to_array - end + @lang_filter.models_unmapped_to_array(m) end end From 6785c4bbf5814ce8fedff0f8f87798f969bf36de Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 18:09:49 +0200 Subject: [PATCH 095/110] move @requested_lang variable from the mapper to the lang_filter module --- lib/goo/sparql/mixins/solution_lang_filter.rb | 4 ++-- lib/goo/sparql/solutions_mapper.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 127da1a4..f29ac635 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -5,12 +5,12 @@ class LanguageFilter attr_reader :requested_lang, :unmapped, :objects_by_lang - def initialize(requested_lang: nil, unmapped: false, list_attributes: []) + def initialize(requested_lang: RequestStore.store[:requested_lang], unmapped: false, list_attributes: []) @attributes_to_translate = [:synonym, :prefLabel, :definition, :label] @list_attributes = list_attributes @objects_by_lang = {} @unmapped = unmapped - @requested_lang = requested_lang + @requested_lang = get_language(requested_lang) end def enrich_models(models_by_id) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 4cfd9104..13cdbbd6 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -22,7 +22,7 @@ def initialize(aggregate_projections, bnode_extraction, embed_struct, @incl = options[:include] @count = options[:count] @collection = options[:collection] - @requested_lang = get_language(options[:requested_lang].to_s) + @options = options end def map_each_solutions(select) @@ -31,7 +31,7 @@ def map_each_solutions(select) list_attributes = Set.new(@klass.attributes(:list)) all_attributes = Set.new(@klass.attributes(:all)) - @lang_filter = Goo::SPARQL::Solution::LanguageFilter.new(requested_lang: @requested_lang, unmapped: @unmapped, + @lang_filter = Goo::SPARQL::Solution::LanguageFilter.new(requested_lang: @options[:requested_lang].to_s, unmapped: @unmapped, list_attributes: list_attributes) select.each_solution do |sol| From 966ab12be7e46f3c278952762adcaa1eafefb710 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 18:10:30 +0200 Subject: [PATCH 096/110] remove no more used @attributes_to_translate variable in lang_filter --- lib/goo/sparql/mixins/solution_lang_filter.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index f29ac635..6d93c8e3 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -6,7 +6,6 @@ class LanguageFilter attr_reader :requested_lang, :unmapped, :objects_by_lang def initialize(requested_lang: RequestStore.store[:requested_lang], unmapped: false, list_attributes: []) - @attributes_to_translate = [:synonym, :prefLabel, :definition, :label] @list_attributes = list_attributes @objects_by_lang = {} @unmapped = unmapped From a990260bdea958ce293c1de75234ee0c25fb9213 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 18:49:30 +0200 Subject: [PATCH 097/110] fix save_model_values method to not save RDF:Literal object but a string --- lib/goo/sparql/mixins/solution_lang_filter.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index bc7ed15d..2df05692 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -145,7 +145,11 @@ def save_model_values(model, values, predicate, unmapped) add_unmapped_to_model(model, predicate, values) else - values = values.map { |k, v| [k, v.first] }.to_h unless list_attributes?(predicate) + values = values.map do |language, values_literals| + values_string = values_literals.map{|x| x.object} + values_string = values_string.first unless list_attributes?(predicate) + [language, values_string] + end.to_h model.send("#{predicate}=", values, on_load: true) end From 5d0a84b10b7adad31bbadd260d86c6d6d63b014b Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 18:55:51 +0200 Subject: [PATCH 098/110] remove not used method in lang filter module --- lib/goo/sparql/mixins/solution_lang_filter.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index 2df05692..b6d5efd9 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -120,15 +120,6 @@ def store_objects_by_lang(id, predicate, object, language) end - def get_model_attribute_value(model, predicate) - if unmapped - unmapped_get(model, predicate) - else - model.instance_variable_get("@#{predicate}") - end - end - - def add_unmapped_to_model(model, predicate, value) if model.respond_to? :klass # struct From 58b07f3f743e628f260519d81e3301f222e45095 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 19:06:46 +0200 Subject: [PATCH 099/110] refecator and rename some methods of the lang_filter module --- lib/goo/sparql/mixins/solution_lang_filter.rb | 108 +++++++++--------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/lib/goo/sparql/mixins/solution_lang_filter.rb b/lib/goo/sparql/mixins/solution_lang_filter.rb index b6d5efd9..7e2bd788 100644 --- a/lib/goo/sparql/mixins/solution_lang_filter.rb +++ b/lib/goo/sparql/mixins/solution_lang_filter.rb @@ -2,7 +2,7 @@ module Goo module SPARQL module Solution class LanguageFilter - + attr_reader :requested_lang, :unmapped, :objects_by_lang def initialize(requested_lang: RequestStore.store[:requested_lang], unmapped: false, list_attributes: []) @@ -12,54 +12,29 @@ def initialize(requested_lang: RequestStore.store[:requested_lang], unmapped: fa @requested_lang = get_language(requested_lang) end - def enrich_models(models_by_id) - + def fill_models_with_all_languages(models_by_id) objects_by_lang.each do |id, predicates| model = models_by_id[id] - predicates.each do |predicate, values| - + predicates.each do |predicate, values| + if values.values.all? { |v| v.all? { |x| literal?(x) && x.plain?} } - save_model_values(model, values, predicate, unmapped) - - end + pull_stored_values(model, values, predicate, @unmapped) + end end - end + end end - - - def set_model_value(model, predicate, objects, object) - - language = object_language(object) - if requested_lang.eql?(:ALL) || !literal?(object) || language_match?(language) - model.send("#{predicate}=", objects, on_load: true) - end - if requested_lang.eql?(:ALL) || requested_lang.is_a?(Array) - language = "@none" if language.nil? || language.eql?(:no_lang) - store_objects_by_lang(model.id, predicate, object, language) + def set_model_value(model, predicate, values, value) + set_value(model, predicate, value) do + model.send("#{predicate}=", values, on_load: true) end - end - def model_set_unmapped(model, predicate, value) - language = object_language(value) - if requested_lang.eql?(:ALL) || language.nil? || language_match?(language) + def set_unmapped_value(model, predicate, value) + set_value(model, predicate, value) do return add_unmapped_to_model(model, predicate, value) end - - store_objects_by_lang(model.id, predicate, value, language) - end - - def model_group_by_lang(model, requested_lang) - unmapped = model.unmapped - cpy = {} - - unmapped.each do |attr, v| - cpy[attr] = group_by_lang(v) - end - - model.unmapped = cpy end def models_unmapped_to_array(m) @@ -72,16 +47,41 @@ def models_unmapped_to_array(m) private + + def set_value(model, predicate, value, &block) + language = object_language(value) + + if requested_lang.eql?(:ALL) || !literal?(value) || language_match?(language) + block.call + end + + if requested_lang.eql?(:ALL) || requested_lang.is_a?(Array) + language = "@none" if language.nil? || language.eql?(:no_lang) + store_objects_by_lang(model.id, predicate, value, language) + end + end + + def model_group_by_lang(model) + unmapped = model.unmapped + cpy = {} + + unmapped.each do |attr, v| + cpy[attr] = group_by_lang(v) + end + + model.unmapped = cpy + end + def group_by_lang(values) - + return values.to_a if is_a_uri?(values.first) - + values = values.group_by { |x| x.language ? x.language.to_s.downcase : :none } - + no_lang = values[:none] || [] return no_lang if !no_lang.empty? && no_lang.all? { |x| !x.plain? } - values + values end def is_a_uri?(value) @@ -94,24 +94,23 @@ def object_language(new_value) def language_match?(language) # no_lang means that the object is not a literal - if language.eql?(:no_lang) - return true - end + return true if language.eql?(:no_lang) - if requested_lang.is_a?(Array) - return requested_lang.include?(language) - end + return requested_lang.include?(language) if requested_lang.is_a?(Array) - return language.eql?(requested_lang) + language.eql?(requested_lang) + end + def literal?(object) + !object_language(object).nil? end def store_objects_by_lang(id, predicate, object, language) # store objects in this format: [id][predicate][language] = [objects] return if requested_lang.is_a?(Array) && !requested_lang.include?(language) - language_key = language.downcase - + language_key = language.downcase + objects_by_lang[id] ||= {} objects_by_lang[id][predicate] ||= {} objects_by_lang[id][predicate][language_key] ||= [] @@ -121,7 +120,7 @@ def store_objects_by_lang(id, predicate, object, language) def add_unmapped_to_model(model, predicate, value) - + if model.respond_to? :klass # struct model[:unmapped] ||= {} model[:unmapped][predicate] ||= [] @@ -131,11 +130,10 @@ def add_unmapped_to_model(model, predicate, value) end end - def save_model_values(model, values, predicate, unmapped) + def pull_stored_values(model, values, predicate, unmapped) if unmapped - add_unmapped_to_model(model, predicate, values) - - else + add_unmapped_to_model(model, predicate, values) + else values = values.map do |language, values_literals| values_string = values_literals.map{|x| x.object} values_string = values_string.first unless list_attributes?(predicate) From efd0436fa52e75ad0cc7eb32a4a166f2c3b8efba Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 19:07:27 +0200 Subject: [PATCH 100/110] use the new name of the lang filter methods in the solution mapper --- lib/goo/sparql/solutions_mapper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/goo/sparql/solutions_mapper.rb b/lib/goo/sparql/solutions_mapper.rb index 13cdbbd6..e537cae1 100644 --- a/lib/goo/sparql/solutions_mapper.rb +++ b/lib/goo/sparql/solutions_mapper.rb @@ -76,7 +76,7 @@ def map_each_solutions(select) end # for this moment we are not going to enrich models , maybe we will use it if the results are empty - @lang_filter.enrich_models(@models_by_id) + @lang_filter.fill_models_with_all_languages(@models_by_id) init_unloaded_attributes(found, list_attributes) @@ -419,7 +419,7 @@ def add_unmapped_to_model(sol) id = sol[:id] value = sol[:attributeObject] - @lang_filter.model_set_unmapped(@models_by_id[id], @properties_to_include[predicate][:uri], value) + @lang_filter.set_unmapped_value(@models_by_id[id], @properties_to_include[predicate][:uri], value) end def add_aggregations_to_model(sol) From e26009af8a11ffbc6a7ab016c41645b76bcac936 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 19:20:57 +0200 Subject: [PATCH 101/110] replace the getters argument to show languages from :show_all_languages to :show_languages: true --- lib/goo/base/settings/settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/goo/base/settings/settings.rb b/lib/goo/base/settings/settings.rb index 6c416298..1308bef0 100644 --- a/lib/goo/base/settings/settings.rb +++ b/lib/goo/base/settings/settings.rb @@ -401,7 +401,7 @@ def read_only(attributes) end def show_all_languages?(args) - args.include?(:show_with_language) + args.first.is_a?(Hash) && args.first.keys.include?(:show_languages) && args.first[:show_languages] end def not_show_all_languages?(values, args) From e5d4d1a87d77c2018e14476b02ec391465c2f97a Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Fri, 14 Jul 2023 20:56:55 +0200 Subject: [PATCH 102/110] catch transform_values of unmapped if it is nil --- lib/goo/base/resource.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index 633c9594..5166a967 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -137,7 +137,7 @@ def unmmaped_to_array end def unmapped(*args) - @unmapped.transform_values do |language_values| + @unmapped&.transform_values do |language_values| self.class.not_show_all_languages?(language_values, args) ? language_values.values.flatten: language_values end end From 5952f8708f64f0a28dcf2ae44c4d8e4610682448 Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 15 Jul 2023 00:45:46 +0200 Subject: [PATCH 103/110] change the getters show_all_languages argument from to include_languages --- lib/goo/base/settings/settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/goo/base/settings/settings.rb b/lib/goo/base/settings/settings.rb index 1308bef0..1263a44a 100644 --- a/lib/goo/base/settings/settings.rb +++ b/lib/goo/base/settings/settings.rb @@ -401,7 +401,7 @@ def read_only(attributes) end def show_all_languages?(args) - args.first.is_a?(Hash) && args.first.keys.include?(:show_languages) && args.first[:show_languages] + args.first.is_a?(Hash) && args.first.keys.include?(:include_languages) && args.first[:include_languages] end def not_show_all_languages?(values, args) From a81f12ca1d5d6cf7c4b7c30718ff6bd8e4a56aff Mon Sep 17 00:00:00 2001 From: Syphax Bouazzouni Date: Sat, 15 Jul 2023 00:46:21 +0200 Subject: [PATCH 104/110] make the map_attributes handle the option showing all the languages --- lib/goo/base/resource.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/goo/base/resource.rb b/lib/goo/base/resource.rb index 5166a967..02709f5e 100644 --- a/lib/goo/base/resource.rb +++ b/lib/goo/base/resource.rb @@ -361,13 +361,13 @@ def self.range_object(attr,id) - def self.map_attributes(inst,equivalent_predicates=nil) + def self.map_attributes(inst,equivalent_predicates=nil, include_languages: false) 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 + unmapped = inst.respond_to?(:klass) ? inst[:unmapped] : inst.unmapped(include_languages: include_languages) list_attrs = klass.attributes(:list) unmapped_string_keys = Hash.new unmapped.each do |k,v| @@ -398,13 +398,18 @@ def self.map_attributes(inst,equivalent_predicates=nil) object = unmapped_string_keys[attr_uri] end - object = object.map {|o| o.is_a?(RDF::URI) ? o : o.object} + if object.is_a?(Hash) + object = object.transform_values{|values| Array(values).map{|o|o.is_a?(RDF::URI) ? o : o.object}} + else + object = object.map {|o| o.is_a?(RDF::URI) ? o : o.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) + + object = object.first unless list_attrs.include?(attr) || include_languages if inst.respond_to?(:klass) inst[attr] = object else From cd7d26d2fcbeb0992e175a736e21a4c146aab139 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 21 Sep 2023 19:20:07 +0200 Subject: [PATCH 105/110] fix order by an attribute that is already filtered --- lib/goo/sparql/query_builder.rb | 60 +++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index 40e888d0..d0814457 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -25,16 +25,14 @@ def build_select_query(ids, variables, graphs, patterns, query_options, properties_to_include) patterns = graph_match(@collection, @graph_match, graphs, @klass, patterns, query_options, @unions) + variables, patterns = add_some_type_to_id(patterns, query_options, variables) + aggregate_projections, aggregate_vars, variables, optional_patterns = get_aggregate_vars(@aggregate, @collection, graphs, @klass, @unions, variables) + query_filter_str, patterns, optional_patterns, filter_variables = + filter_query_strings(@collection, graphs, @klass, optional_patterns, patterns, @query_filters) - aggregate_projections, aggregate_vars, - variables, optional_patterns = get_aggregate_vars(@aggregate, @collection, graphs, - @klass, @unions, variables) @order_by, variables, optional_patterns = init_order_by(@count, @klass, @order_by, optional_patterns, variables,patterns, query_options, graphs) - variables, patterns = add_some_type_to_id(patterns, query_options, variables) - query_filter_str, patterns, optional_patterns, filter_variables = - filter_query_strings(@collection, graphs, @klass, optional_patterns, patterns, @query_filters) variables = [] if @count variables.delete :some_type @@ -121,7 +119,7 @@ def order_by order_by_str = @order_by.map do |attr, order| if order.is_a?(Hash) sub_attr, order = order.first - attr = @internal_variables_map[sub_attr] + attr = @internal_variables_map.select{ |internal_var, attr_var| attr_var.eql?({attr => sub_attr}) || attr_var.eql?(sub_attr)}.keys.last end "#{order.to_s.upcase}(?#{attr})" end @@ -165,23 +163,24 @@ def ids_filter(ids) def patterns_for_match(klass, attr, value, graphs, patterns, unions, internal_variables, subject = :id, in_union = false, in_aggregate = false, query_options = {}, collection = nil) + new_internal_var = value if value.respond_to?(:each) || value.instance_of?(Symbol) next_pattern = value.instance_of?(Array) ? value.first : value #for filters next_pattern = { next_pattern => [] } if next_pattern.instance_of?(Symbol) - value = "internal_join_var_#{internal_variables.length}".to_sym + new_internal_var = "internal_join_var_#{internal_variables.length}".to_sym if in_aggregate - value = "#{attr}_agg_#{in_aggregate}".to_sym + new_internal_var = "#{attr}_agg_#{in_aggregate}".to_sym end - internal_variables << value - @internal_variables_map[attr] = value + internal_variables << new_internal_var + @internal_variables_map[new_internal_var] = value.empty? ? attr : {attr => value} end add_rules(attr, klass, query_options) graph, pattern = - query_pattern(klass, attr, value: value, subject: subject, collection: collection) + query_pattern(klass, attr, value: new_internal_var, subject: subject, collection: collection) if pattern if !in_union patterns << pattern @@ -194,7 +193,7 @@ def patterns_for_match(klass, attr, value, graphs, patterns, unions, range = klass.range(attr) next_pattern.each do |next_attr, next_value| patterns_for_match(range, next_attr, next_value, graphs, - patterns, unions, internal_variables, subject = value, + patterns, unions, internal_variables, subject = new_internal_var, in_union, in_aggregate, collection = collection) end end @@ -270,11 +269,30 @@ def init_order_by(count, klass, order_by, optional_patterns, variables, patterns order_by.each do |attr, direction| if direction.is_a?(Hash) + # TODO this part can be improved/refactored, the complexity was added because order by don't work + # if the pattern is in the mandatory ones (variable `patterns`) + # and optional (variable `optional_patterns`) at the same time sub_attr, direction = direction.first graph_match_iteration = Goo::Base::PatternIteration.new(Goo::Base::Pattern.new({attr => [sub_attr]})) old_internal = internal_variables.dup + old_patterns = optional_patterns.dup + walk_pattern(klass, graph_match_iteration, graphs, optional_patterns, @unions, internal_variables, in_aggregate = false, query_options, @collection) - variables << (internal_variables - old_internal).last + new_variables = (internal_variables - old_internal) + internal_variables.delete(new_variables) + new_patterns = optional_patterns - old_patterns + already_existent_pattern = patterns.select{|x| x[1].eql?(new_patterns.last[1])}.first + + if already_existent_pattern + already_existent_variable = already_existent_pattern[2] + optional_patterns = old_patterns + key = @internal_variables_map.select{|key, value| key.eql?(new_variables.last)}.keys.first + @internal_variables_map[key] = (already_existent_variable || new_variables.last) if key + variables << already_existent_variable + else + variables << new_variables.last + end + else quad = query_pattern(klass, attr) optional_patterns << quad[1] @@ -325,7 +343,12 @@ def query_filter_sparql(klass, filter, filter_patterns, filter_graphs, end filter_var = inspected_patterns[filter_pattern_match] - unless filter_operation.value.instance_of?(Goo::Filter) + if filter_operation.value.instance_of?(Goo::Filter) + filter_operations << "#{sparql_op_string(filter_operation.operator)}" + query_filter_sparql(klass, filter_operation.value, filter_patterns, + filter_graphs, filter_operations, + internal_variables, inspected_patterns, collection) + else case filter_operation.operator when :unbound filter_operations << "!BOUND(?#{filter_var.to_s})" @@ -349,11 +372,6 @@ def query_filter_sparql(klass, filter, filter_patterns, filter_graphs, " #{value.to_ntriples}") end - else - filter_operations << "#{sparql_op_string(filter_operation.operator)}" - query_filter_sparql(klass, filter_operation.value, filter_patterns, - filter_graphs, filter_operations, - internal_variables, inspected_patterns, collection) end end end @@ -399,7 +417,7 @@ def add_some_type_to_id(patterns, query_options, variables) end def internal_variables - @internal_variables_map.values + @internal_variables_map.keys end end end From 891fa997fc35b50432fd645c4b0a11fd25eecf4d Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Fri, 22 Sep 2023 03:30:17 +0200 Subject: [PATCH 106/110] don't add the filtered variables to the select clause of the query --- lib/goo/sparql/query_builder.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index d0814457..a65f5ec9 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -288,15 +288,16 @@ def init_order_by(count, klass, order_by, optional_patterns, variables, patterns optional_patterns = old_patterns key = @internal_variables_map.select{|key, value| key.eql?(new_variables.last)}.keys.first @internal_variables_map[key] = (already_existent_variable || new_variables.last) if key - variables << already_existent_variable + + #variables << already_existent_variable else - variables << new_variables.last + #variables << new_variables.last end else quad = query_pattern(klass, attr) optional_patterns << quad[1] - variables << attr + #variables << attr end #patterns << quad[1] @@ -398,7 +399,7 @@ def filter_query_strings(collection, graphs, klass, patterns.concat(filter_patterns) end end - filter_variables << inspected_patterns.values.last + #filter_variables << inspected_patterns.values.last end [query_filter_str, patterns, optional_patterns, filter_variables] end From e87c21e06164bf0bb08c7035a408fb24f0b2327b Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 19 Oct 2023 17:19:14 +0200 Subject: [PATCH 107/110] add filters patterns to select variables --- lib/goo/sparql/query_builder.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index a65f5ec9..d1843dda 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -33,7 +33,8 @@ def build_select_query(ids, variables, graphs, patterns, @order_by, variables, optional_patterns = init_order_by(@count, @klass, @order_by, optional_patterns, variables,patterns, query_options, graphs) - + query_filter_str, patterns, optional_patterns, filter_variables = + filter_query_strings(@collection, graphs, internal_variables, @klass, optional_patterns, patterns, @query_filters) variables = [] if @count variables.delete :some_type From 19c9ce19291f3ba74f60191482216a5530c71dc1 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 19 Oct 2023 17:19:40 +0200 Subject: [PATCH 108/110] fix pagination with order_by with filter that returns empty pages for 4store --- lib/goo/sparql/query_builder.rb | 27 +++++++++++++++++---------- test/test_where.rb | 11 +++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index d1843dda..6077bd60 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -32,13 +32,15 @@ def build_select_query(ids, variables, graphs, patterns, @order_by, variables, optional_patterns = init_order_by(@count, @klass, @order_by, optional_patterns, variables,patterns, query_options, graphs) + order_by_str, order_variables = order_by_string + variables, patterns = add_some_type_to_id(patterns, query_options, variables) query_filter_str, patterns, optional_patterns, filter_variables = - filter_query_strings(@collection, graphs, internal_variables, @klass, optional_patterns, patterns, @query_filters) + filter_query_strings(@collection, graphs, @klass, optional_patterns, patterns, @query_filters) variables = [] if @count variables.delete :some_type - select_distinct(variables, aggregate_projections, filter_variables) + select_distinct(variables, aggregate_projections, filter_variables, order_variables) .from(graphs) .where(patterns) .union_bind_in_where(properties_to_include) @@ -54,7 +56,10 @@ def build_select_query(ids, variables, graphs, patterns, @query.union(*@unions) unless @unions.empty? ids_filter(ids) if ids - order_by if @order_by + + + @query.order_by(*order_by_str) if @order_by + put_query_aggregate_vars(aggregate_vars) if aggregate_vars count if @count @@ -116,16 +121,17 @@ def put_query_aggregate_vars(aggregate_vars) self end - def order_by - order_by_str = @order_by.map do |attr, order| + def order_by_string + order_variables = [] + order_str = @order_by&.map do |attr, order| if order.is_a?(Hash) sub_attr, order = order.first attr = @internal_variables_map.select{ |internal_var, attr_var| attr_var.eql?({attr => sub_attr}) || attr_var.eql?(sub_attr)}.keys.last end + order_variables << attr "#{order.to_s.upcase}(?#{attr})" end - @query.order_by(*order_by_str) - self + [order_str,order_variables] end def from(graphs) @@ -140,10 +146,11 @@ def from(graphs) self end - def select_distinct(variables, aggregate_projections, filter_variables) + def select_distinct(variables, aggregate_variables, filter_variables, order_variables) select_vars = variables.dup - reject_aggregations_from_vars(select_vars, aggregate_projections) if aggregate_projections - select_vars = (select_vars + filter_variables).uniq if @page # Fix for 4store pagination with a filter + reject_aggregations_from_vars(select_vars, aggregate_variables) if aggregate_variables + # Fix for 4store pagination with a filter https://github.com/ontoportal-lirmm/ontologies_api/issues/25 + select_vars = (select_vars + filter_variables + order_variables).uniq if @page @query = @query.select(*select_vars).distinct(true) self end diff --git a/test/test_where.rb b/test/test_where.rb index 30d933e3..748dca88 100644 --- a/test/test_where.rb +++ b/test/test_where.rb @@ -262,6 +262,17 @@ def test_embed_two_levels end end + def test_paging_with_filter_order + total_count = Student.where.count + page_1 = Student.where.page(1, total_count - 1).order_by(name: :asc).to_a + refute_empty page_1 + assert page_1.next? + page_2 = Student.where.page(page_1.next_page, total_count - 1).order_by(name: :asc).to_a + + + refute_empty page_2 + assert_equal total_count, page_1.size + page_2.size + end def test_unique_object_references From c4dd04d90244edf9e3f8e651d68eb128d5944bf2 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 19 Oct 2023 19:09:16 +0200 Subject: [PATCH 109/110] include the in the select variables filtered variables --- lib/goo/sparql/query_builder.rb | 2 +- test/test_where.rb | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/goo/sparql/query_builder.rb b/lib/goo/sparql/query_builder.rb index 6077bd60..3b0f3589 100644 --- a/lib/goo/sparql/query_builder.rb +++ b/lib/goo/sparql/query_builder.rb @@ -407,7 +407,7 @@ def filter_query_strings(collection, graphs, klass, patterns.concat(filter_patterns) end end - #filter_variables << inspected_patterns.values.last + filter_variables << inspected_patterns.values.last end [query_filter_str, patterns, optional_patterns, filter_variables] end diff --git a/test/test_where.rb b/test/test_where.rb index 748dca88..c80fed33 100644 --- a/test/test_where.rb +++ b/test/test_where.rb @@ -263,11 +263,13 @@ def test_embed_two_levels end def test_paging_with_filter_order - total_count = Student.where.count - page_1 = Student.where.page(1, total_count - 1).order_by(name: :asc).to_a + + f = Goo::Filter.new(:birth_date) > DateTime.parse('1978-01-03') + total_count = Student.where.filter(f).count + page_1 = Student.where.include(:name, :birth_date).page(1, total_count - 1).filter(f).order_by(name: :asc).to_a refute_empty page_1 assert page_1.next? - page_2 = Student.where.page(page_1.next_page, total_count - 1).order_by(name: :asc).to_a + page_2 = Student.where.include(:name, :birth_date).page(page_1.next_page, total_count - 1).filter(f).order_by(name: :asc).to_a refute_empty page_2 From 5247e8d55ebf3e2b6ccb8dc68a9b9df080e44924 Mon Sep 17 00:00:00 2001 From: Syphax bouazzouni Date: Thu, 19 Oct 2023 19:10:23 +0200 Subject: [PATCH 110/110] optimize pagination query by not re-doing the filters and order in the include query --- lib/goo/base/where.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/goo/base/where.rb b/lib/goo/base/where.rb index 5bc0fa8c..81cd26ce 100644 --- a/lib/goo/base/where.rb +++ b/lib/goo/base/where.rb @@ -209,6 +209,11 @@ def process_query_intl(count=false) options_load[:ids] = ids if ids models_by_id = {} + if @page_i && (options_load[:models].length > 0) + options_load.delete(:filters) + options_load.delete(:order_by) + end + if (@page_i && options_load[:models].length > 0) || (!@page_i && (@count.nil? || @count > 0)) models_by_id = Goo::SPARQL::Queries.model_load(options_load)