active-triples 0.11.0 → 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/active-triples.gemspec +2 -1
- data/lib/active_triples/configuration.rb +22 -11
- data/lib/active_triples/extension_strategy.rb +1 -1
- data/lib/active_triples/list.rb +0 -2
- data/lib/active_triples/node_config.rb +64 -8
- data/lib/active_triples/persistable.rb +2 -12
- data/lib/active_triples/persistence_strategies/parent_strategy.rb +16 -15
- data/lib/active_triples/property.rb +36 -11
- data/lib/active_triples/rdf_source.rb +86 -9
- data/lib/active_triples/relation.rb +61 -42
- data/lib/active_triples/schema.rb +20 -1
- data/lib/active_triples/version.rb +1 -1
- data/spec/active_triples/extension_strategy_spec.rb +12 -1
- data/spec/active_triples/node_config_spec.rb +54 -0
- data/spec/active_triples/persistence_strategies/parent_strategy_spec.rb +24 -17
- data/spec/active_triples/property_spec.rb +10 -0
- data/spec/active_triples/rdf_source_spec.rb +103 -122
- data/spec/active_triples/relation_spec.rb +199 -12
- data/spec/active_triples/resource_spec.rb +115 -0
- data/spec/active_triples/schema_spec.rb +6 -0
- metadata +21 -7
@@ -203,6 +203,8 @@ describe ActiveTriples::Relation do
|
|
203
203
|
|
204
204
|
types = { numeric: [0, 1, 2, 3_000_000_000],
|
205
205
|
string: ['moomin', 'snork', 'snufkin'],
|
206
|
+
lang: [RDF::Literal('Moomin', language: :en),
|
207
|
+
RDF::Literal('Mummi', language: :fi)],
|
206
208
|
date: [Date.today, Date.today - 1],
|
207
209
|
uri: [RDF::URI('one'), RDF::URI('two'), RDF::URI('three')],
|
208
210
|
node: [RDF::Node.new, RDF::Node.new],
|
@@ -239,6 +241,16 @@ describe ActiveTriples::Relation do
|
|
239
241
|
other << 'extra'
|
240
242
|
expect(subject <=> other).to be_nil
|
241
243
|
end
|
244
|
+
|
245
|
+
it "gives nil when other contains a subset by language" do
|
246
|
+
subject << RDF::Literal("Moomin", language: :aa)
|
247
|
+
expect(subject <=> other).to be_nil
|
248
|
+
end
|
249
|
+
|
250
|
+
it "gives nil when other contains a superset by language" do
|
251
|
+
other << RDF::Literal("Moomin", language: :aa)
|
252
|
+
expect(subject <=> other).to be_nil
|
253
|
+
end
|
242
254
|
end
|
243
255
|
end
|
244
256
|
|
@@ -274,7 +286,7 @@ describe ActiveTriples::Relation do
|
|
274
286
|
end
|
275
287
|
|
276
288
|
it 'adds new child node to relation' do
|
277
|
-
expect { subject.build }.to change { subject.count }.by
|
289
|
+
expect { subject.build }.to change { subject.count }.by 1
|
278
290
|
end
|
279
291
|
|
280
292
|
it 'builds child as new blank node by default' do
|
@@ -286,6 +298,24 @@ describe ActiveTriples::Relation do
|
|
286
298
|
expect(subject.build(id: uri)).to be_uri
|
287
299
|
end
|
288
300
|
|
301
|
+
context 'when cast is false' do
|
302
|
+
include_context 'with symbol property' do
|
303
|
+
before do
|
304
|
+
reflections.property :moomin,
|
305
|
+
cast: false,
|
306
|
+
predicate: RDF::Vocab::DC.relation
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'still returns as a resource' do
|
311
|
+
expect(subject.build).to be_a ActiveTriples::RDFSource
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'adds a new child node to the relation' do
|
315
|
+
expect { subject.build }.to change { subject.count }.by 1
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
289
319
|
context 'with configured properties' do
|
290
320
|
include_context 'with symbol property' do
|
291
321
|
before do
|
@@ -320,6 +350,15 @@ describe ActiveTriples::Relation do
|
|
320
350
|
expect { subject.delete(1) }.not_to change { subject.to_a }
|
321
351
|
end
|
322
352
|
|
353
|
+
it 'does not notify observers when no changes are made' do
|
354
|
+
observer = double(:observer)
|
355
|
+
parent_resource.add_observer(observer)
|
356
|
+
|
357
|
+
expect(observer).not_to receive(:notify)
|
358
|
+
|
359
|
+
subject.delete(1)
|
360
|
+
end
|
361
|
+
|
323
362
|
context 'with values' do
|
324
363
|
before { subject << values }
|
325
364
|
|
@@ -337,6 +376,17 @@ describe ActiveTriples::Relation do
|
|
337
376
|
.to contain_exactly(*values[1..-1])
|
338
377
|
end
|
339
378
|
|
379
|
+
it 'notifies observers of changes' do
|
380
|
+
observer = double(:observer)
|
381
|
+
parent_resource.add_observer(observer)
|
382
|
+
|
383
|
+
expect(observer).to receive(:notify)
|
384
|
+
.with(subject.property,
|
385
|
+
a_collection_containing_exactly(*values[1..-1]))
|
386
|
+
|
387
|
+
subject.delete(values.first)
|
388
|
+
end
|
389
|
+
|
340
390
|
it 'deletes a URI value' do
|
341
391
|
values.delete(uri)
|
342
392
|
expect { subject.delete(uri) }
|
@@ -364,11 +414,19 @@ describe ActiveTriples::Relation do
|
|
364
414
|
include_context 'with symbol property'
|
365
415
|
|
366
416
|
let(:parent_resource) { ActiveTriples::Resource.new }
|
417
|
+
let(:observer) { double(:observer) }
|
367
418
|
|
368
419
|
it 'gives nil for non-existant value' do
|
369
420
|
expect(subject.delete?(1)).to be_nil
|
370
421
|
end
|
371
422
|
|
423
|
+
it 'does not notify observers when no changes are made' do
|
424
|
+
parent_resource.add_observer(observer)
|
425
|
+
|
426
|
+
expect(observer).not_to receive(:notify)
|
427
|
+
subject.delete?(1)
|
428
|
+
end
|
429
|
+
|
372
430
|
it 'returns value when deleted' do
|
373
431
|
subject.set(1)
|
374
432
|
expect(subject.delete?(1)).to eq 1
|
@@ -379,12 +437,23 @@ describe ActiveTriples::Relation do
|
|
379
437
|
expect { subject.delete?(1) }
|
380
438
|
.to change { subject.to_a }.to be_empty
|
381
439
|
end
|
440
|
+
|
441
|
+
it 'notifies observers of changes' do
|
442
|
+
subject.set(1)
|
443
|
+
parent_resource.add_observer(observer)
|
444
|
+
|
445
|
+
expect(observer).to receive(:notify)
|
446
|
+
.with(subject.property, be_empty)
|
447
|
+
|
448
|
+
subject.delete?(1)
|
449
|
+
end
|
382
450
|
end
|
383
451
|
|
384
452
|
describe '#subtract' do
|
385
453
|
include_context 'with symbol property'
|
386
454
|
|
387
455
|
let(:parent_resource) { ActiveTriples::Resource.new }
|
456
|
+
let(:observer) { double(:observer) }
|
388
457
|
|
389
458
|
it 'subtracts values as arguments' do
|
390
459
|
subject.set([1, 2, 3])
|
@@ -403,6 +472,16 @@ describe ActiveTriples::Relation do
|
|
403
472
|
expect { subject.subtract([:two, :three]) }
|
404
473
|
.to change { subject.to_a }.to contain_exactly(:one)
|
405
474
|
end
|
475
|
+
|
476
|
+
it 'notifies observers of changes' do
|
477
|
+
subject.set([1, 2, 3])
|
478
|
+
parent_resource.add_observer(observer)
|
479
|
+
|
480
|
+
expect(observer).to receive(:notify)
|
481
|
+
.with(subject.property, a_collection_containing_exactly(1))
|
482
|
+
|
483
|
+
subject.subtract([2, 3])
|
484
|
+
end
|
406
485
|
end
|
407
486
|
|
408
487
|
describe '#swap' do
|
@@ -450,12 +529,32 @@ describe ActiveTriples::Relation do
|
|
450
529
|
expect { subject.clear }
|
451
530
|
.to change { subject.parent.query(query_pattern) }.to([])
|
452
531
|
end
|
532
|
+
|
533
|
+
it 'notifies observers of changed state' do
|
534
|
+
observer = double(:observer)
|
535
|
+
parent_resource.add_observer(observer)
|
536
|
+
|
537
|
+
expect(observer)
|
538
|
+
.to receive(:notify)
|
539
|
+
.with(subject.property, be_empty)
|
540
|
+
|
541
|
+
subject.clear
|
542
|
+
end
|
453
543
|
end
|
454
544
|
|
455
545
|
it 'is a no-op when relation is empty' do
|
456
546
|
subject.parent << [subject.parent.rdf_subject, RDF.type, 'moomin']
|
457
547
|
expect { subject.clear }.not_to change { subject.parent.statements.to_a }
|
458
548
|
end
|
549
|
+
|
550
|
+
it 'does not notify observers when relation is empty' do
|
551
|
+
observer = double(:observer)
|
552
|
+
parent_resource.add_observer(observer)
|
553
|
+
|
554
|
+
expect(observer).not_to receive(:notify)
|
555
|
+
|
556
|
+
subject.clear
|
557
|
+
end
|
459
558
|
end
|
460
559
|
|
461
560
|
describe '#<<' do
|
@@ -474,6 +573,19 @@ describe ActiveTriples::Relation do
|
|
474
573
|
.to change { subject.to_a }.to contain_exactly(*values)
|
475
574
|
end
|
476
575
|
|
576
|
+
it 'notifies observers on the parent' do
|
577
|
+
observer = double(:observer)
|
578
|
+
values = [:moomin, :snork]
|
579
|
+
parent_resource.add_observer(observer)
|
580
|
+
|
581
|
+
expect(observer)
|
582
|
+
.to receive(:notify)
|
583
|
+
.with(subject.property,
|
584
|
+
a_collection_containing_exactly(*values))
|
585
|
+
|
586
|
+
subject << values
|
587
|
+
end
|
588
|
+
|
477
589
|
it 'keeps datatypes' do
|
478
590
|
values = [RDF::Literal(Date.today), RDF::Literal(:moomin)]
|
479
591
|
|
@@ -506,8 +618,8 @@ describe ActiveTriples::Relation do
|
|
506
618
|
end
|
507
619
|
|
508
620
|
it 'retains unknown datatypes' do
|
509
|
-
literal =
|
510
|
-
RDF::Literal('snowflake',
|
621
|
+
literal =
|
622
|
+
RDF::Literal('snowflake',
|
511
623
|
datatype: RDF::URI('http://emaple.com/snowflake'))
|
512
624
|
|
513
625
|
subject << literal
|
@@ -531,7 +643,7 @@ describe ActiveTriples::Relation do
|
|
531
643
|
literal = DummySnowflake.new('special')
|
532
644
|
|
533
645
|
subject << literal
|
534
|
-
|
646
|
+
|
535
647
|
expect { subject << 'special' }
|
536
648
|
.to change { subject.send(:objects).to_a }
|
537
649
|
.to contain_exactly(literal, RDF::Literal('special'))
|
@@ -641,18 +753,81 @@ describe ActiveTriples::Relation do
|
|
641
753
|
.to contain_exactly(*values)
|
642
754
|
end
|
643
755
|
|
644
|
-
context 'and
|
756
|
+
context 'and a class is configured' do
|
757
|
+
let(:this_type) { RDF::URI('http://example.org/Moomin') }
|
758
|
+
let(:this_class) do
|
759
|
+
Class.new do
|
760
|
+
include ActiveTriples::RDFSource
|
761
|
+
configure type: RDF::URI('http://example.org/Moomin')
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
let(:other_type) { RDF::URI('http://example.org/Snork') }
|
766
|
+
let(:other_class) do
|
767
|
+
Class.new do
|
768
|
+
include ActiveTriples::RDFSource
|
769
|
+
configure type: RDF::URI('http://example.org/Snork')
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
645
773
|
before do
|
646
774
|
reflections
|
647
|
-
.property
|
648
|
-
|
649
|
-
|
775
|
+
.property property,
|
776
|
+
class_name: this_class,
|
777
|
+
predicate: RDF::URI('http://example.org/moomin')
|
650
778
|
end
|
651
779
|
|
652
|
-
it '
|
653
|
-
subject.
|
654
|
-
|
655
|
-
|
780
|
+
it 'casts values with no type to the class' do
|
781
|
+
expect(subject).to contain_exactly(an_instance_of(this_class),
|
782
|
+
an_instance_of(this_class),
|
783
|
+
an_instance_of(this_class))
|
784
|
+
end
|
785
|
+
|
786
|
+
it 'casts values with other type to the other class' do
|
787
|
+
subject << other_class.new
|
788
|
+
expect(subject).to contain_exactly(an_instance_of(this_class),
|
789
|
+
an_instance_of(this_class),
|
790
|
+
an_instance_of(this_class),
|
791
|
+
an_instance_of(other_class))
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
context 'and persistence_strategy is configured' do
|
796
|
+
context 'as a repository strategy' do
|
797
|
+
before do
|
798
|
+
reflections
|
799
|
+
.property :moomin,
|
800
|
+
predicate: RDF::URI('http://example.org/moomin'),
|
801
|
+
persist_to: ActiveTriples::RepositoryStrategy
|
802
|
+
end
|
803
|
+
|
804
|
+
it 'assigns persistence strategy' do
|
805
|
+
subject.each do |node|
|
806
|
+
expect(node.persistence_strategy)
|
807
|
+
.to be_a ActiveTriples::RepositoryStrategy
|
808
|
+
end
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
context 'as a parent strategy' do
|
813
|
+
before do
|
814
|
+
reflections
|
815
|
+
.property :moomin,
|
816
|
+
predicate: RDF::URI('http://example.org/moomin'),
|
817
|
+
persist_to: ActiveTriples::ParentStrategy
|
818
|
+
end
|
819
|
+
|
820
|
+
it 'assigns persistence strategy' do
|
821
|
+
subject.each do |node|
|
822
|
+
expect(node.persistence_strategy)
|
823
|
+
.to be_a ActiveTriples::ParentStrategy
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
it 'assigns parent' do
|
828
|
+
subject.each do |node|
|
829
|
+
expect(node.persistence_strategy.parent).to eql subject.parent
|
830
|
+
end
|
656
831
|
end
|
657
832
|
end
|
658
833
|
end
|
@@ -767,6 +942,18 @@ describe ActiveTriples::Relation do
|
|
767
942
|
.to change { subject.to_a }.to contain_exactly(*values)
|
768
943
|
end
|
769
944
|
|
945
|
+
it 'notifies observers on the parent' do
|
946
|
+
observer = double(:observer)
|
947
|
+
values = [:moomin, :snork]
|
948
|
+
parent_resource.add_observer(observer)
|
949
|
+
|
950
|
+
expect(observer)
|
951
|
+
.to receive(:notify)
|
952
|
+
.with(subject.property, a_collection_containing_exactly(*values))
|
953
|
+
|
954
|
+
subject.set(values)
|
955
|
+
end
|
956
|
+
|
770
957
|
context 'when given a Relation' do
|
771
958
|
before do
|
772
959
|
class DummySnowflake < RDF::Literal
|
@@ -635,6 +635,121 @@ describe ActiveTriples::Resource do
|
|
635
635
|
end
|
636
636
|
end
|
637
637
|
|
638
|
+
describe 'sources with properties with class_name defined' do
|
639
|
+
before(:context) do
|
640
|
+
class DummyChapter < ActiveTriples::Resource
|
641
|
+
ontology = RDF::URI('http://www.example.com/ontology')
|
642
|
+
type = RDF::URI('http://www.example.com/type')
|
643
|
+
|
644
|
+
configure repository: :default, type: type / 'Chapter'
|
645
|
+
|
646
|
+
property :title, predicate: ontology / 'title'
|
647
|
+
property :subtitle, predicate: ontology / 'subtitle'
|
648
|
+
end
|
649
|
+
|
650
|
+
class DummyBook < ActiveTriples::Resource
|
651
|
+
ontology = RDF::URI('http://www.example.com/ontology')
|
652
|
+
type = RDF::URI('http://www.example.com/type')
|
653
|
+
|
654
|
+
configure repository: :default, type: type / 'Book'
|
655
|
+
|
656
|
+
property :title, predicate: ontology / 'title'
|
657
|
+
property :has_chapter, predicate: ontology / 'hasChapter',
|
658
|
+
class_name: DummyChapter
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
context 'when loading models from graph' do
|
663
|
+
before(:context) do
|
664
|
+
r = RDF::Repository.new
|
665
|
+
ActiveTriples::Repositories.repositories[:default] = r
|
666
|
+
|
667
|
+
@book_url = 'http://www.example.com/BOOK_URI'
|
668
|
+
@book_title = 'Example Book.'
|
669
|
+
@chapter_title = 'Chapter 1'
|
670
|
+
ttl = "<#{@book_url}> a <http://www.example.com/type/Book>;
|
671
|
+
<http://www.example.com/ontology/hasChapter> [
|
672
|
+
a <http://www.example.com/type/Chapter>;
|
673
|
+
<http://www.example.com/ontology/title> \"#{@chapter_title}\"
|
674
|
+
];
|
675
|
+
<http://www.example.com/ontology/title> \"#{@book_title}\" ."
|
676
|
+
book_graph = ::RDF::Graph.new.from_ttl ttl
|
677
|
+
r = ActiveTriples::Repositories.repositories[:default]
|
678
|
+
r << book_graph
|
679
|
+
|
680
|
+
@book = DummyBook.new(RDF::URI.new(@book_url))
|
681
|
+
end
|
682
|
+
|
683
|
+
it 'populates DummyBook properly' do
|
684
|
+
expect(@book.rdf_subject.to_s).to eq @book_url
|
685
|
+
expect(@book).to be_a DummyBook
|
686
|
+
expect(@book.type)
|
687
|
+
.to include(RDF::URI.new('http://www.example.com/type/Book'))
|
688
|
+
expect(@book.title.first).to eq @book_title
|
689
|
+
end
|
690
|
+
|
691
|
+
it 'populates DummyChapter properly' do
|
692
|
+
chapter = @book.has_chapter.first
|
693
|
+
expect(chapter).to be_a DummyChapter
|
694
|
+
expect(chapter.title.first).to eq @chapter_title
|
695
|
+
expect(chapter.type)
|
696
|
+
.to include(RDF::URI.new('http://www.example.com/type/Chapter'))
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
context 'when loading models through properties' do
|
701
|
+
before(:context) do
|
702
|
+
r = RDF::Repository.new
|
703
|
+
ActiveTriples::Repositories.repositories[:default] = r
|
704
|
+
|
705
|
+
bk1 = DummyBook.new('http://www.example.com/book1')
|
706
|
+
bk1.title = 'Learning about Explicit Links in ActiveTriples'
|
707
|
+
|
708
|
+
ch1 = DummyChapter.new('http://www.example.com/book1/chapter1')
|
709
|
+
ch1.title = 'Defining a source with an Explicit Link'
|
710
|
+
bk1.has_chapter = ch1
|
711
|
+
ch1.persist!
|
712
|
+
bk1.persist!
|
713
|
+
|
714
|
+
@bk1 = DummyBook.new('http://www.example.com/book1')
|
715
|
+
@ch1 = DummyChapter.new('http://www.example.com/book1/chapter1')
|
716
|
+
end
|
717
|
+
|
718
|
+
it 'populates DummyBook (resumed resource) properly' do
|
719
|
+
expect(@bk1.type.first)
|
720
|
+
.to eq RDF::URI('http://www.example.com/type/Book')
|
721
|
+
expect(@bk1.title.first)
|
722
|
+
.to eq 'Learning about Explicit Links in ActiveTriples'
|
723
|
+
end
|
724
|
+
|
725
|
+
it 'populates DummyChapter (property resource) properly' do
|
726
|
+
ch1 = @bk1.has_chapter.first
|
727
|
+
expect(ch1.type.first)
|
728
|
+
.to eq RDF::URI('http://www.example.com/type/Chapter')
|
729
|
+
expect(ch1.title.first)
|
730
|
+
.to eq 'Defining a source with an Explicit Link'
|
731
|
+
end
|
732
|
+
|
733
|
+
it 'populates DummyChapter (directly from repository) properly' do
|
734
|
+
expect(@ch1.type.first)
|
735
|
+
.to eq RDF::URI('http://www.example.com/type/Chapter')
|
736
|
+
expect(@ch1.title.first)
|
737
|
+
.to eq 'Defining a source with an Explicit Link'
|
738
|
+
end
|
739
|
+
|
740
|
+
it 'does not reload from repository twice' do
|
741
|
+
ch1 = @bk1.has_chapter.first
|
742
|
+
expect(ch1.title.first).to eq 'Defining a source with an Explicit Link'
|
743
|
+
|
744
|
+
@ch1.subtitle = 'Changed after original load'
|
745
|
+
@ch1.persist!
|
746
|
+
expect(@ch1.subtitle).to eq ['Changed after original load']
|
747
|
+
|
748
|
+
expect(ch1.subtitle).to eq []
|
749
|
+
end
|
750
|
+
end
|
751
|
+
end
|
752
|
+
|
638
753
|
describe 'big complex graphs' do
|
639
754
|
before do
|
640
755
|
class DummyPerson
|