ladder 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,30 +1,37 @@
1
- module Ladder::Searchable::File
2
- extend ActiveSupport::Concern
1
+ module Ladder
2
+ module Searchable
3
+ module File
4
+ extend ActiveSupport::Concern
3
5
 
4
- included do
5
- # Index binary content using Elasticsearch mapper attachment plugin
6
- # https://github.com/elasticsearch/elasticsearch-mapper-attachments
7
- mapping _source: { enabled: false } do
8
- indexes :file, type: 'attachment', fields: {
9
- file: { store: true },
10
- title: { store: true },
11
- date: { store: true },
12
- author: { store: true },
13
- keywords: { store: true },
14
- content_type: { store: true },
15
- content_length: { store: true },
16
- language: { store: true }
17
- }
18
- end
6
+ included do
7
+ # Index binary content using Elasticsearch mapper attachment plugin
8
+ # https://github.com/elasticsearch/elasticsearch-mapper-attachments
9
+ mapping _source: { enabled: false } do
10
+ indexes :file, type: 'attachment', fields: {
11
+ file: { store: true },
12
+ title: { store: true },
13
+ date: { store: true },
14
+ author: { store: true },
15
+ keywords: { store: true },
16
+ content_type: { store: true },
17
+ content_length: { store: true },
18
+ language: { store: true }
19
+ }
20
+ end
19
21
 
20
- # Explicitly set mapping definition on index
21
- self.__elasticsearch__.create_index!
22
- end
22
+ # Explicitly set mapping definition on index
23
+ __elasticsearch__.create_index!
24
+ end
23
25
 
24
- ##
25
- # Return a Base64-encoded copy of data
26
- def as_indexed_json(opts = {})
27
- { file: Base64.encode64(data) }
26
+ ##
27
+ # Serialize a Base64-encoded version of data for indexing
28
+ #
29
+ # @see Elasticsearch::Model::Serializing#as_indexed_json
30
+ #
31
+ # @return [Hash] a serialized version of the file
32
+ def as_indexed_json(*)
33
+ { file: Base64.encode64(data) }
34
+ end
35
+ end
28
36
  end
29
-
30
- end
37
+ end
@@ -1,19 +1,30 @@
1
- module Ladder::Searchable::Resource
2
- extend ActiveSupport::Concern
3
-
4
- def as_indexed_json(opts = {})
5
- respond_to?(:serialized_json) ? serialized_json : as_json(except: [:id, :_id])
6
- end
1
+ module Ladder
2
+ module Searchable
3
+ module Resource
4
+ extend ActiveSupport::Concern
7
5
 
8
- module ClassMethods
6
+ ##
7
+ # Serialize the resource as JSON for indexing
8
+ #
9
+ # @see Elasticsearch::Model::Serializing#as_indexed_json
10
+ #
11
+ # @return [Hash] a serialized version of the resource
12
+ def as_indexed_json(*)
13
+ respond_to?(:serialized_json) ? serialized_json : as_json(except: [:id, :_id])
14
+ end
9
15
 
10
- ##
11
- # Specify type of serialization to use for indexing
12
- #
13
- def index_for_search(opts = {}, &block)
14
- define_method(:serialized_json, block)
16
+ module ClassMethods
17
+ ##
18
+ # Specify type of serialization to use for indexing;
19
+ # if a block is provided, it is expected to return a Hash
20
+ # that will be used in lieu of {#as_indexed_json} for
21
+ # serializing the resource in the index
22
+ #
23
+ # @return [void]
24
+ def index_for_search(&block)
25
+ define_method(:serialized_json, block) if block_given?
26
+ end
27
+ end
15
28
  end
16
-
17
29
  end
18
-
19
- end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module Ladder
2
- VERSION = "0.3.1"
3
- end
2
+ VERSION = '0.3.2'
3
+ end
@@ -15,7 +15,7 @@ describe Ladder::File do
15
15
 
16
16
  after do
17
17
  Object.send(:remove_const, :LADDER_BASE_URI) if Object
18
- Object.send(:remove_const, "Datastream") if Object
18
+ Object.send(:remove_const, 'Datastream') if Object
19
19
  end
20
20
 
21
21
  shared_context 'with relations' do
@@ -42,7 +42,7 @@ describe Ladder::File do
42
42
 
43
43
  context 'with one-sided has-many' do
44
44
  it 'should have a relation' do
45
- expect(thing.relations['files'].relation).to eq (Mongoid::Relations::Referenced::ManyToMany)
45
+ expect(thing.relations['files'].relation).to eq Mongoid::Relations::Referenced::ManyToMany
46
46
  expect(thing.files.to_a).to include subject
47
47
  end
48
48
 
@@ -59,7 +59,6 @@ describe Ladder::File do
59
59
  expect(subject.class.properties).to be_empty
60
60
  end
61
61
  end
62
-
63
62
  end
64
63
 
65
64
  context 'with data from file' do
@@ -73,8 +72,12 @@ describe Ladder::File do
73
72
  end
74
73
 
75
74
  context 'with data from string after creation' do
76
- data = "And so Moomintroll was helplessly thrown out into a strange and dangerous world and dropped up to his ears in the first snowdrift of his experience. It felt unpleasantly prickly to his velvet skin, but at the same time his nose caught a new smell. It was a more serious smell than any he had met before, and slightly frightening. But it made him wide awake and greatly interested."
77
-
75
+ data = 'And so Moomintroll was helplessly thrown out into a strange and dangerous world and dropped
76
+ up to his ears in the first snowdrift of his experience. It felt unpleasantly prickly to his
77
+ velvet skin, but at the same time his nose caught a new smell. It was a more serious smell
78
+ than any he had met before, and slightly frightening. But it made him wide awake and greatly
79
+ interested.'
80
+
78
81
  let(:subject) { Datastream.new }
79
82
  let(:source) { data } # UTF-8 (string)
80
83
 
@@ -85,5 +88,4 @@ describe Ladder::File do
85
88
  include_context 'with relations'
86
89
  it_behaves_like 'a File'
87
90
  end
88
-
89
- end
91
+ end
@@ -10,12 +10,17 @@ describe Ladder::Resource::Dynamic do
10
10
 
11
11
  class Thing
12
12
  include Ladder::Resource::Dynamic
13
+ configure type: RDF::DC.BibliographicResource
14
+
15
+ field :alt
16
+ property :alt, predicate: RDF::DC.alternative # non-localized literal
17
+ property :title, predicate: RDF::DC.title # localized literal
13
18
  end
14
19
  end
15
20
 
16
21
  after do
17
22
  Object.send(:remove_const, :LADDER_BASE_URI) if Object
18
- Object.send(:remove_const, "Thing") if Object
23
+ Object.send(:remove_const, 'Thing') if Object
19
24
  end
20
25
 
21
26
  context 'with data' do
@@ -23,155 +28,20 @@ describe Ladder::Resource::Dynamic do
23
28
 
24
29
  before do
25
30
  # non-localized literal
26
- subject.class.field :alt
27
- subject.class.property :alt, predicate: RDF::DC.alternative
28
31
  subject.alt = 'Mumintrollet pa kometjakt'
29
32
 
30
33
  # localized literal
31
- subject.class.property :title, predicate: RDF::DC.title
32
34
  subject.title = 'Comet in Moominland'
33
35
  end
34
36
 
35
37
  it_behaves_like 'a Resource'
36
-
37
- describe '#property' do
38
- context 'with undefined property' do
39
- before do
40
- subject.property :description, predicate: RDF::DC.description
41
- subject.description = "Second in Tove Jansson's series of Moomin books"
42
- end
43
-
44
- it 'should create a context' do
45
- expect(subject._context).to eq({description: RDF::DC.description.to_uri.to_s})
46
- end
47
-
48
- it 'should build an accessor' do
49
- expect(subject.description).to eq "Second in Tove Jansson's series of Moomin books"
50
- end
51
- end
52
-
53
- context 'with conflicting property' do
54
- before do
55
- subject.property :title, predicate: RDF::DC11.title
56
- subject.dc11_title = "Kometjakten"
57
- end
58
-
59
- it 'should create a context' do
60
- expect(subject._context).to eq({dc11_title: RDF::DC11.title.to_uri.to_s})
61
- end
62
-
63
- it 'should build an accessor' do
64
- expect(subject.dc11_title).to eq "Kometjakten"
65
- end
66
- end
67
- end
68
-
69
- describe '#update_resource' do
70
- before do
71
- # undefined property
72
- subject.property :description, predicate: RDF::DC.description
73
- subject.description = "Second in Tove Jansson's series of Moomin books"
74
-
75
- # conflicting property
76
- subject.property :title, predicate: RDF::DC11.title
77
- subject.dc11_title = "Kometjakten"
78
-
79
- # defined field
80
- subject << RDF::Statement(nil, RDF::DC.title, 'Kometen kommer')
81
-
82
- # conflicting property
83
- subject << RDF::Statement(nil, RDF::DC.alternative, "Kometjakten")
84
-
85
- # URI value
86
- subject << RDF::Statement(nil, RDF::DC.identifier, RDF::URI('http://some.uri'))
87
-
88
- subject.update_resource
89
- end
90
-
91
- it 'should have updated values' do
92
- expect(subject.resource.statements.count).to eq 5
93
- expect(subject.resource.query(predicate: RDF::DC.description, object: "Second in Tove Jansson's series of Moomin books").count).to eq 1
94
- expect(subject.resource.query(predicate: RDF::DC11.title, object: "Kometjakten").count).to eq 1
95
- expect(subject.resource.query(predicate: RDF::DC.title, object: RDF::Literal.new('Kometen kommer', language: :en)).count).to eq 1
96
- expect(subject.resource.query(predicate: RDF::DC.alternative, object: "Kometjakten").count).to eq 1
97
- expect(subject.resource.query(predicate: RDF::DC.identifier, object: RDF::URI('http://some.uri')).count).to eq 1
98
- end
99
- end
100
-
101
- describe '#<<' do
102
- context 'with defined field' do
103
- before do
104
- subject << RDF::Statement(nil, RDF::DC.title, 'Kometen kommer')
105
- end
106
-
107
- it 'should not create a context' do
108
- expect(subject._context).to be nil
109
- end
110
-
111
- it 'should update existing values' do
112
- expect(subject.title).to eq 'Kometen kommer'
113
- end
114
- end
115
-
116
- context 'with undefined field' do
117
- before do
118
- subject << RDF::Statement(nil, RDF::DC.description, "Second in Tove Jansson's series of Moomin books")
119
- end
120
-
121
- it 'should create a context' do
122
- expect(subject._context).to eq({ description: RDF::DC.description.to_uri.to_s })
123
- end
124
-
125
- it 'should build an accessor' do
126
- expect(subject.description).to eq "Second in Tove Jansson's series of Moomin books"
127
- end
128
- end
129
-
130
- context 'with conflicting property' do
131
- before do
132
- subject << RDF::Statement(nil, RDF::DC11.title, "Kometjakten")
133
- end
134
-
135
- it 'should create a context' do
136
- expect(subject._context).to eq({ dc11_title: RDF::DC11.title.to_uri.to_s })
137
- end
138
-
139
- it 'should build an accessor' do
140
- expect(subject.dc11_title).to eq "Kometjakten"
141
- end
142
- end
143
-
144
- context 'with a URI value' do
145
- before do
146
- subject << RDF::Statement(nil, RDF::DC.identifier, RDF::URI('http://some.uri'))
147
- end
148
-
149
- it 'should store the URI as a string' do
150
- expect(subject.identifier).to eq 'http://some.uri'
151
- end
38
+ it_behaves_like 'a Dynamic Resource'
39
+ end
152
40
 
153
- it 'should cast a URI into the resource' do
154
- subject.update_resource
155
- query = subject.resource.query(subject: subject.rdf_subject, predicate: RDF::DC.identifier)
156
- expect(query.first_object).to be_a_kind_of RDF::URI
157
- end
158
- end
159
- end
160
-
161
- describe '#resource_class' do
162
- before do
163
- subject.property :description, predicate: RDF::DC.description
164
- end
41
+ context 'from JSON-LD' do
42
+ let(:subject) { Thing.new_from_graph(RDF::Graph.load './spec/shared/graph.jsonld') }
165
43
 
166
- it 'should have modified properties on the instance' do
167
- expect(subject.resource.class.properties.keys).to include 'description'
168
- end
169
-
170
- it 'should not modify the global class properties' do
171
- expect(subject.class.resource_class.properties.keys).to_not include 'description'
172
- expect(subject.class.resource_class.properties).to eq subject.class.new.class.resource_class.properties
173
- end
174
- end
44
+ it_behaves_like 'a Resource'
45
+ it_behaves_like 'a Dynamic Resource'
175
46
  end
176
-
177
- end
47
+ end
@@ -10,266 +10,63 @@ describe Ladder::Resource do
10
10
 
11
11
  class Thing
12
12
  include Ladder::Resource
13
+ configure type: RDF::DC.BibliographicResource
14
+
15
+ field :alt
16
+ property :alt, predicate: RDF::DC.alternative # non-localized literal
17
+ property :title, predicate: RDF::DC.title # localized literal
18
+ property :identifier, predicate: RDF::DC.identifier
13
19
  end
14
20
  end
15
21
 
16
22
  after do
17
23
  Object.send(:remove_const, :LADDER_BASE_URI) if Object
18
- Object.send(:remove_const, "Thing") if Object
19
- end
20
-
21
- shared_context 'with data' do
22
- before do
23
- subject.class.configure type: RDF::DC.BibliographicResource
24
-
25
- # non-localized literal
26
- subject.class.field :alt
27
- subject.class.property :alt, predicate: RDF::DC.alternative
28
- subject.alt = 'Mumintrollet pa kometjakt'
29
-
30
- # localized literal
31
- subject.class.property :title, predicate: RDF::DC.title
32
- subject.title = 'Comet in Moominland'
33
- end
24
+ Object.send(:remove_const, 'Thing') if Object
34
25
  end
35
26
 
36
27
  shared_context 'with relations' do
37
28
  let(:person) { Person.new }
38
29
  let(:concept) { Concept.new }
39
30
  let(:part) { Part.new }
40
-
31
+
41
32
  before do
42
33
  class Person
43
34
  include Ladder::Resource
35
+ configure type: RDF::DC.AgentClass
36
+
37
+ property :things, predicate: RDF::DC.relation, class_name: 'Thing'
44
38
  end
45
39
 
46
40
  class Concept
47
41
  include Ladder::Resource
42
+ configure type: RDF::SKOS.Concept
48
43
  end
49
44
 
50
45
  class Part
51
46
  include Ladder::Resource
52
- end
47
+ configure type: RDF::DC.PhysicalResource
53
48
 
54
- person.class.configure type: RDF::DC.AgentClass
55
- concept.class.configure type: RDF::SKOS.Concept
56
- part.class.configure type: RDF::DC.PhysicalResource
49
+ embedded_in :thing
50
+ property :thing, predicate: RDF::DC.relation, class_name: 'Thing'
51
+ end
57
52
 
58
53
  # many-to-many
59
- person.class.property :things, predicate: RDF::DC.relation, class_name: 'Thing'
60
- subject.class.property :people, predicate: RDF::DC.creator, class_name: 'Person'
61
- subject.people << person
54
+ Thing.property :people, predicate: RDF::DC.creator, class_name: 'Person'
62
55
 
63
56
  # one-sided has-many
64
- subject.class.has_and_belongs_to_many :concepts, inverse_of: nil
65
- subject.class.property :concepts, predicate: RDF::DC.subject, class_name: 'Concept'
66
- subject.concepts << concept
57
+ Thing.has_and_belongs_to_many :concepts, inverse_of: nil, autosave: true
58
+ Thing.property :concepts, predicate: RDF::DC.subject, class_name: 'Concept'
67
59
 
68
60
  # embedded one
69
- part.class.embedded_in :thing
70
- part.class.property :thing, predicate: RDF::DC.relation, class_name: 'Thing'
71
- subject.class.embeds_one :part, cascade_callbacks: true
72
- subject.class.property :part, predicate: RDF::DC.hasPart, class_name: 'Part'
73
- subject.part = part
74
- subject.save
61
+ Thing.embeds_one :part, cascade_callbacks: true
62
+ Thing.property :part, predicate: RDF::DC.hasPart, class_name: 'Part'
75
63
  end
76
64
 
77
65
  after do
78
- Object.send(:remove_const, "Person") if Object
66
+ Object.send(:remove_const, 'Person') if Object
79
67
  Object.send(:remove_const, 'Concept') if Object
80
68
  Object.send(:remove_const, 'Part') if Object
81
69
  end
82
-
83
- it 'should have relations' do
84
- expect(subject.people.to_a).to include person
85
- expect(subject.concepts.to_a).to include concept
86
- expect(subject.part).to eq part
87
- end
88
-
89
- it 'should have inverse relations' do
90
- expect(person.things.to_a).to include subject
91
- expect(concept.relations).to be_empty
92
- expect(part.thing).to eq subject
93
- end
94
-
95
- context 'with many-to-many' do
96
- it 'should have a relation' do
97
- expect(subject.relations['people'].relation).to eq (Mongoid::Relations::Referenced::ManyToMany)
98
- expect(subject.people.to_a).to include person
99
- end
100
-
101
- it 'should have an inverse relation' do
102
- expect(person.relations['things'].relation).to eq (Mongoid::Relations::Referenced::ManyToMany)
103
- expect(person.things.to_a).to include subject
104
- end
105
-
106
- it 'should have a valid predicate' do
107
- expect(subject.class.properties['people'].predicate).to eq RDF::DC.creator
108
- end
109
-
110
- it 'should have a valid inverse predicate' do
111
- expect(person.class.properties['things'].predicate).to eq RDF::DC.relation
112
- end
113
- end
114
-
115
- context 'with one-sided has-many' do
116
- it 'should have a relation' do
117
- expect(subject.relations['concepts'].relation).to eq (Mongoid::Relations::Referenced::ManyToMany)
118
- expect(subject.concepts.to_a).to include concept
119
- end
120
-
121
- it 'should not have an inverse relation' do
122
- expect(subject.relations['concepts'].inverse_of).to be nil
123
- expect(concept.relations).to be_empty
124
- end
125
-
126
- it 'should have a valid predicate' do
127
- expect(subject.class.properties['concepts'].predicate).to eq RDF::DC.subject
128
- end
129
-
130
- it 'should not have an inverse predicate' do
131
- expect(concept.class.properties).to be_empty
132
- end
133
- end
134
-
135
- context 'with embedded-one' do
136
- it 'should have a relation' do
137
- expect(subject.relations['part'].relation).to eq (Mongoid::Relations::Embedded::One)
138
- expect(subject.part).to eq part
139
- end
140
-
141
- it 'should have an inverse relation' do
142
- expect(part.relations['thing'].relation).to eq (Mongoid::Relations::Embedded::In)
143
- expect(part.thing).to eq subject
144
- end
145
-
146
- it 'should have a valid predicate' do
147
- expect(subject.class.properties['part'].predicate).to eq RDF::DC.hasPart
148
- end
149
-
150
- it 'should have a valid inverse predicate' do
151
- expect(part.class.properties['thing'].predicate).to eq RDF::DC.relation
152
- end
153
- end
154
-
155
- context '#update_resource with related' do
156
- # TODO add tests for autosaved relations
157
- before do
158
- subject.update_resource(related: true)
159
- end
160
-
161
- it 'should have a literal object' do
162
- query = subject.resource.query(subject: subject.rdf_subject, predicate: RDF::DC.title)
163
- expect(query.first_object.to_s).to eq 'Comet in Moominland'
164
- end
165
-
166
- it 'should have an embedded object' do
167
- query = subject.resource.query(subject: subject.rdf_subject, predicate: RDF::DC.hasPart)
168
- expect(query.count).to eq 1
169
- expect(query.first_object).to eq part.rdf_subject
170
- end
171
-
172
- it 'should have an embedded object relation' do
173
- query = subject.resource.query(subject: part.rdf_subject, predicate: RDF::DC.relation)
174
- expect(query.count).to eq 1
175
- expect(query.first_object).to eq subject.rdf_subject
176
- end
177
-
178
- it 'should have related objects' do
179
- # many-to-many
180
- query_creator = subject.resource.query(subject: subject.rdf_subject, predicate: RDF::DC.creator)
181
- expect(query_creator.count).to eq 1
182
- expect(query_creator.first_object).to eq person.rdf_subject
183
-
184
- # one-sided has-many
185
- query_subject = subject.resource.query(subject: subject.rdf_subject, predicate: RDF::DC.subject)
186
- expect(query_subject.count).to eq 1
187
- expect(query_subject.first_object).to eq concept.rdf_subject
188
-
189
- # embedded-one
190
- query_part = subject.resource.query(subject: subject.rdf_subject, predicate: RDF::DC.hasPart)
191
- expect(query_part.count).to eq 1
192
- expect(query_part.first_object).to eq part.rdf_subject
193
- end
194
-
195
- it 'should have related object relations' do
196
- # many-to-many
197
- query = person.resource.query(subject: person.rdf_subject, predicate: RDF::DC.relation)
198
- expect(query.count).to eq 1
199
- expect(query.first_object).to eq subject.rdf_subject
200
-
201
- # one-sided has-many
202
- expect(concept.resource.query(object: subject.rdf_subject)).to be_empty
203
-
204
- # embedded-one
205
- query = part.resource.query(subject: part.rdf_subject, predicate: RDF::DC.relation)
206
- expect(query.count).to eq 1
207
- expect(query.first_object).to eq subject.rdf_subject
208
- end
209
- end
210
-
211
- context '#update_resource with related and then without related' do
212
- # TODO add tests for autosaved relations
213
- before do
214
- subject.update_resource(related: true)
215
- subject.update_resource # implicit false
216
- end
217
-
218
- it 'should not have related objects' do
219
- expect(subject.resource.query(subject: person.rdf_subject)).to be_empty
220
- expect(subject.resource.query(subject: concept.rdf_subject)).to be_empty
221
- end
222
-
223
- it 'should have embedded object relations' do
224
- query = subject.resource.query(subject: part.rdf_subject, predicate: RDF::DC.relation)
225
- expect(query.count).to eq 1
226
- expect(query.first_object).to eq subject.rdf_subject
227
- end
228
-
229
- it 'should have related object relations' do
230
- # many-to-many
231
- query = person.resource.query(subject: person.rdf_subject, predicate: RDF::DC.relation)
232
- expect(query.count).to eq 1
233
- expect(query.first_object).to eq subject.rdf_subject
234
-
235
- # one-sided has-many
236
- expect(concept.resource.query(object: subject.rdf_subject)).to be_empty
237
-
238
- # embedded-one
239
- query = part.resource.query(subject: part.rdf_subject, predicate: RDF::DC.relation)
240
- expect(query.count).to eq 1
241
- expect(query.first_object).to eq subject.rdf_subject
242
- end
243
- end
244
-
245
- context 'serializable' do
246
- # TODO: contexts with relations and without
247
- # expect(subject.as_jsonld(related: true)).to eq subject.as_jsonld
248
- # expect(subject.as_qname(related: true)).to eq subject.as_qname
249
- # expect(subject.as_framed_jsonld).to eq subject.as_jsonld
250
-
251
- describe '#as_jsonld related: true' do
252
- it 'should output a valid jsonld representation of itself and related' do
253
- graph = RDF::Graph.new << JSON::LD::API.toRdf(subject.as_jsonld related: true)
254
- expect(subject.update_resource(related: true).to_hash).to eq graph.to_hash
255
- end
256
- end
257
-
258
- describe '#as_qname related: true' do
259
- it 'should output a valid qname representation of itself and related' do
260
- # TODO
261
- end
262
- end
263
-
264
- describe '#as_framed_jsonld' do
265
- it 'should output a valid framed jsonld representation of itself and related' do
266
- framed_graph = RDF::Graph.new << JSON::LD::API.toRdf(subject.as_framed_jsonld)
267
- related_graph = RDF::Graph.new << JSON::LD::API.toRdf(subject.as_jsonld related: true)
268
- expect(framed_graph.to_hash).to eq related_graph.to_hash
269
- end
270
- end
271
- end
272
-
273
70
  end
274
71
 
275
72
  context 'with data' do
@@ -286,7 +83,31 @@ describe Ladder::Resource do
286
83
  include_context 'with data'
287
84
  include_context 'with relations'
288
85
 
86
+ before do
87
+ subject.people << person # many-to-many
88
+ subject.concepts << concept # one-sided has-many
89
+ subject.part = part # embedded one
90
+ subject.save
91
+ end
92
+
289
93
  it_behaves_like 'a Resource'
94
+ it_behaves_like 'a Resource with relations'
290
95
  end
291
96
 
292
- end
97
+ context 'from JSON-LD' do
98
+ let(:subject) { Thing.new_from_graph(RDF::Graph.load './spec/shared/graph.jsonld') }
99
+
100
+ include_context 'with relations'
101
+
102
+ let(:person) { subject.people.first }
103
+ let(:concept) { subject.concepts.first }
104
+ let(:part) { subject.part }
105
+
106
+ before do
107
+ subject.save
108
+ end
109
+
110
+ it_behaves_like 'a Resource'
111
+ it_behaves_like 'a Resource with relations'
112
+ end
113
+ end