ld4l-foaf_rdf 0.0.7 → 0.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 10d46bdd365de45b658492da21672b0d89e25e8a
4
- data.tar.gz: 77951e466f61e788932478ede986806cbb98d0db
3
+ metadata.gz: ba134a6ca339e32fe700537fe2d30f2a5f4e6792
4
+ data.tar.gz: 31e733e7e542acd05c24ff77048245637caf6b14
5
5
  SHA512:
6
- metadata.gz: 824f16697566e71d829208bf60d5897b3961f54168a3bb34ab794dcb2b7dc36140bbee555b268c1f6963a370dfa60e7a1bece693fd753df9c0fb3bd92a7248c0
7
- data.tar.gz: d3c6408c8b8d87d2150fb276beb7a90abfbed7083ee6f85d1a4a0089897d60126227ef8fd9cbe2756e2fad574a8fd3070922bd718f98aa3c07af82909bf457bb
6
+ metadata.gz: 4419583cfad85004933d4365dd66e869ca33018bbc915124c4b4a2fe5d0172af20ff289f61de6cac11c1a97da9b90565dcdc81734bc675296695986197d6317c
7
+ data.tar.gz: e32c5634592775c1573cc79cb914bf1421ca1a461ba64eefdce31da67689c2a8e7852fd1f8519e0995f06eae140d6f8abb07dc1c40e917e6191507596982c654
data/.travis.yml CHANGED
@@ -1,12 +1,14 @@
1
1
  language: ruby
2
2
  bundler_args: --without debug
3
3
  script: "bundle exec rspec spec"
4
+ sudo: false
5
+ cache: bundler
4
6
  rvm:
5
- - 1.9.3
6
- - 2.0.0
7
- - 2.1.0
8
- - 2.1.1
9
- - jruby-19mode
7
+ - 2.2.4
8
+ - 2.3.0
9
+ - jruby-9.0.4.0
10
+ - rbx-2
10
11
  matrix:
11
12
  allow_failures:
12
- - rvm: jruby-19mode
13
+ - rvm: jruby-9.0.4.0
14
+ - rvm: rbx-2
@@ -17,18 +17,13 @@ Gem::Specification.new do |spec|
17
17
 
18
18
  spec.files = `git ls-files -z`.split("\x0")
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
- # spec.test_files = `git ls-files -- {spec}/*`.split("\n") # FROM ActiveTriples gemspec file
21
- # spec.require_paths = ["lib"] # NOT IN ActiveTriples gemspec file
22
20
 
23
- # spec.add_dependency('ffi', '~> 1.9.5')
24
- spec.add_dependency('rdf', '~> 1.1')
25
-
26
- spec.add_dependency('active-triples', '~> 0.5')
27
- spec.add_dependency('active_triples-local_name', '~> 0.1')
21
+ spec.add_dependency('rdf')
22
+ spec.add_dependency('active-triples')
23
+ spec.add_dependency('active_triples-local_name')
28
24
 
29
25
  spec.add_development_dependency('pry')
30
- # spec.add_development_dependency('pry-byebug') # Works with ruby > 2
31
- # spec.add_development_dependency('pry-debugger') # Works with ruby < 2
26
+ spec.add_development_dependency('pry-byebug')
32
27
  spec.add_development_dependency('rdoc')
33
28
  spec.add_development_dependency('rspec')
34
29
  spec.add_development_dependency('coveralls')
@@ -1,5 +1,5 @@
1
1
  module LD4L
2
2
  module FoafRDF
3
- VERSION = "0.0.7"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -0,0 +1,267 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'LD4L::FoafRDF::Agent' do
4
+
5
+ subject { LD4L::FoafRDF::Agent.new }
6
+
7
+ describe 'rdf_subject' do
8
+ it "should be a blank node if we haven't set it" do
9
+ expect(subject.rdf_subject.node?).to be true
10
+ end
11
+
12
+ it "should be settable when it has not been set yet" do
13
+ subject.set_subject! RDF::URI('http://example.org/moomin')
14
+ expect(subject.rdf_subject).to eq RDF::URI('http://example.org/moomin')
15
+ end
16
+
17
+ it "should append to base URI when setting to non-URI subject" do
18
+ subject.set_subject! '123'
19
+ expect(subject.rdf_subject).to eq RDF::URI("#{LD4L::FoafRDF::Agent.base_uri}123")
20
+ end
21
+
22
+ describe 'when changing subject' do
23
+ before do
24
+ subject << RDF::Statement.new(subject.rdf_subject, RDF::DC.title, RDF::Literal('Comet in Moominland'))
25
+ subject << RDF::Statement.new(RDF::URI('http://example.org/moomin_comics'), RDF::DC.isPartOf, subject.rdf_subject)
26
+ subject << RDF::Statement.new(RDF::URI('http://example.org/moomin_comics'), RDF::DC.relation, 'http://example.org/moomin_land')
27
+ subject.set_subject! RDF::URI('http://example.org/moomin')
28
+ end
29
+
30
+ it 'should update graph subjects' do
31
+ expect(subject.has_statement?(RDF::Statement.new(subject.rdf_subject, RDF::DC.title, RDF::Literal('Comet in Moominland')))).to be true
32
+ end
33
+
34
+ it 'should update graph objects' do
35
+ expect(subject.has_statement?(RDF::Statement.new(RDF::URI('http://example.org/moomin_comics'), RDF::DC.isPartOf, subject.rdf_subject))).to be true
36
+ end
37
+
38
+ it 'should leave other uris alone' do
39
+ expect(subject.has_statement?(RDF::Statement.new(RDF::URI('http://example.org/moomin_comics'), RDF::DC.relation, 'http://example.org/moomin_land'))).to be true
40
+ end
41
+ end
42
+
43
+ describe 'created with URI subject' do
44
+ before do
45
+ subject.set_subject! RDF::URI('http://example.org/moomin')
46
+ end
47
+
48
+ it 'should not be settable' do
49
+ expect{ subject.set_subject! RDF::URI('http://example.org/moomin2') }.to raise_error
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+ # -------------------------------------------------
56
+ # START -- Test attributes specific to this model
57
+ # -------------------------------------------------
58
+
59
+ describe 'type' do
60
+ it "should be an RDF::FOAF.Agent" do
61
+ expect(subject.type.first.value).to eq RDF::FOAF.Agent.value
62
+ end
63
+ end
64
+
65
+ describe '#localname_prefix' do
66
+ it "should return default prefix" do
67
+ prefix = LD4L::FoafRDF::Agent.localname_prefix
68
+ expect(prefix).to eq "a"
69
+ end
70
+ end
71
+
72
+ # -----------------------------------------------
73
+ # END -- Test attributes specific to this model
74
+ # -----------------------------------------------
75
+
76
+
77
+ describe "#persisted?" do
78
+ context 'with a repository' do
79
+ before do
80
+ # Create inmemory repository
81
+ repository = RDF::Repository.new
82
+ allow(subject).to receive(:repository).and_return(repository)
83
+ end
84
+
85
+ context "when the object is new" do
86
+ it "should return false" do
87
+ expect(subject).not_to be_persisted
88
+ end
89
+ end
90
+
91
+ context "when it is saved" do
92
+ before do
93
+ subject.persist!
94
+ end
95
+
96
+ it "should return true" do
97
+ expect(subject).to be_persisted
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "#persist!" do
104
+ context "when the repository is set" do
105
+ context "and the item is not a blank node" do
106
+
107
+ subject {LD4L::FoafRDF::Agent.new("123")}
108
+ let(:result) { subject.persist! }
109
+
110
+ before do
111
+ # Create inmemory repository
112
+ @repo = RDF::Repository.new
113
+ ActiveTriples::Repositories.repositories[:default] = @repo
114
+ subject.label = "An Agent"
115
+ result
116
+ end
117
+
118
+ it "should return true" do
119
+ expect(result).to eq true
120
+ end
121
+
122
+ it "should persist to the repository" do
123
+ expect(@repo.statements.first).to eq subject.statements.first
124
+ end
125
+
126
+ it "should delete from the repository" do
127
+ subject.reload
128
+ expect(subject.label.first).to eq "An Agent"
129
+ subject.label = []
130
+ expect(subject.label).to eq []
131
+ subject.persist!
132
+ subject.reload
133
+ expect(subject.label).to eq []
134
+ expect(@repo.statements.to_a.length).to eq 1 # Only the type statement
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '#destroy!' do
141
+ before do
142
+ subject << RDF::Statement(RDF::DC.LicenseDocument, RDF::DC.title, 'LICENSE')
143
+ end
144
+
145
+ subject { LD4L::FoafRDF::Agent.new('456')}
146
+
147
+ it 'should return true' do
148
+ expect(subject.destroy!).to be true
149
+ expect(subject.destroy).to be true
150
+ end
151
+
152
+ it 'should delete the graph' do
153
+ subject.destroy
154
+ expect(subject).to be_empty
155
+ end
156
+ end
157
+
158
+ describe '#type' do
159
+ it 'should return the type configured on the parent class' do
160
+ expected_result = LD4L::FoafRDF::Agent.type.kind_of?(Array) ? LD4L::FoafRDF::Agent.type : [LD4L::FoafRDF::Agent.type]
161
+ expect(subject.type).to eq expected_result
162
+ end
163
+
164
+ it 'should set the type' do
165
+ subject.type = RDF::URI('http://example.org/AnotherClass')
166
+ expect(subject.type).to eq [RDF::URI('http://example.org/AnotherClass')]
167
+ end
168
+
169
+ it 'should be the type in the graph' do
170
+ subject.query(:subject => subject.rdf_subject, :predicate => RDF.type).statements do |s|
171
+ expect(s.object).to eq RDF::URI('http://example.org/AnotherClass')
172
+ end
173
+ end
174
+ end
175
+
176
+ describe '#rdf_label' do
177
+ subject {LD4L::FoafRDF::Agent.new("123")}
178
+
179
+ it 'should return an array of label values' do
180
+ expect(subject.rdf_label).to be_kind_of Array
181
+ end
182
+
183
+ it 'should return the default label as URI when no title property exists' do
184
+ expect(subject.rdf_label).to eq ["#{LD4L::FoafRDF::Agent.base_uri}123"]
185
+ end
186
+
187
+ it 'should prioritize configured label values' do
188
+ custom_label = RDF::URI('http://example.org/custom_label')
189
+ subject.class.configure :rdf_label => custom_label
190
+ subject << RDF::Statement(subject.rdf_subject, custom_label, RDF::Literal('New Label'))
191
+ expect(subject.rdf_label).to eq ['New Label']
192
+ end
193
+ end
194
+
195
+ describe 'big complex graphs' do
196
+ before do
197
+ class DummyPerson < ActiveTriples::Resource
198
+ configure :type => RDF::URI('http://example.org/Person')
199
+ property :foafname, :predicate => RDF::FOAF.name
200
+ property :publications, :predicate => RDF::FOAF.publications, :class_name => 'DummyDocument'
201
+ property :knows, :predicate => RDF::FOAF.knows, :class_name => DummyPerson
202
+ end
203
+
204
+ class DummyDocument < ActiveTriples::Resource
205
+ configure :type => RDF::URI('http://example.org/Document')
206
+ property :title, :predicate => RDF::DC.title
207
+ property :creator, :predicate => RDF::DC.creator, :class_name => 'DummyPerson'
208
+ end
209
+
210
+ LD4L::FoafRDF::Agent.property :item, :predicate => RDF::DC.relation, :class_name => DummyDocument
211
+ end
212
+
213
+ subject { LD4L::FoafRDF::Agent.new }
214
+
215
+ let (:document1) do
216
+ d = DummyDocument.new
217
+ d.title = 'Document One'
218
+ d
219
+ end
220
+
221
+ let (:document2) do
222
+ d = DummyDocument.new
223
+ d.title = 'Document Two'
224
+ d
225
+ end
226
+
227
+ let (:person1) do
228
+ p = DummyPerson.new
229
+ p.foafname = 'Alice'
230
+ p
231
+ end
232
+
233
+ let (:person2) do
234
+ p = DummyPerson.new
235
+ p.foafname = 'Bob'
236
+ p
237
+ end
238
+
239
+ let (:data) { <<END
240
+ _:1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/SomeClass> .
241
+ _:1 <http://purl.org/dc/terms/relation> _:2 .
242
+ _:2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Document> .
243
+ _:2 <http://purl.org/dc/terms/title> "Document One" .
244
+ _:2 <http://purl.org/dc/terms/creator> _:3 .
245
+ _:2 <http://purl.org/dc/terms/creator> _:4 .
246
+ _:4 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Person> .
247
+ _:4 <http://xmlns.com/foaf/0.1/name> "Bob" .
248
+ _:3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Person> .
249
+ _:3 <http://xmlns.com/foaf/0.1/name> "Alice" .
250
+ _:3 <http://xmlns.com/foaf/0.1/knows> _:4 ."
251
+ END
252
+ }
253
+
254
+ after do
255
+ Object.send(:remove_const, "DummyDocument")
256
+ Object.send(:remove_const, "DummyPerson")
257
+ end
258
+
259
+ it 'should allow access to deep nodes' do
260
+ document1.creator = [person1, person2]
261
+ document2.creator = person1
262
+ person1.knows = person2
263
+ subject.item = [document1]
264
+ expect(subject.item.first.creator.first.knows.first.foafname).to eq ['Bob']
265
+ end
266
+ end
267
+ end
@@ -106,19 +106,22 @@ describe 'LD4L::FoafRDF::Person' do
106
106
  context "and the item is not a blank node" do
107
107
 
108
108
  subject {LD4L::FoafRDF::Person.new("123")}
109
+ let(:result) { subject.persist! }
109
110
 
110
111
  before do
111
112
  # Create inmemory repository
112
113
  @repo = RDF::Repository.new
113
- allow(subject.class).to receive(:repository).and_return(nil)
114
- allow(subject).to receive(:repository).and_return(@repo)
115
- subject.persist!
114
+ ActiveTriples::Repositories.repositories[:default] = @repo
115
+ result
116
+ end
117
+
118
+ it "should return true" do
119
+ expect(result).to eq true
116
120
  end
117
121
 
118
122
  it "should persist to the repository" do
119
123
  expect(@repo.statements.first).to eq subject.statements.first
120
124
  end
121
-
122
125
  end
123
126
  end
124
127
  end
@@ -143,7 +146,8 @@ describe 'LD4L::FoafRDF::Person' do
143
146
 
144
147
  describe '#type' do
145
148
  it 'should return the type configured on the parent class' do
146
- expect(subject.type).to eq [LD4L::FoafRDF::Person.type]
149
+ expected_result = LD4L::FoafRDF::Person.type.kind_of?(Array) ? LD4L::FoafRDF::Person.type : [LD4L::FoafRDF::Person.type]
150
+ expect(subject.type).to eq expected_result
147
151
  end
148
152
 
149
153
  it 'should set the type' do
@@ -177,17 +181,6 @@ describe 'LD4L::FoafRDF::Person' do
177
181
  end
178
182
  end
179
183
 
180
- describe '#solrize' do
181
- it 'should return a label for bnodes' do
182
- expect(subject.solrize).to eq subject.rdf_label
183
- end
184
-
185
- it 'should return a string of the resource uri' do
186
- subject.set_subject! 'http://example.org/moomin'
187
- expect(subject.solrize).to eq 'http://example.org/moomin'
188
- end
189
- end
190
-
191
184
  describe 'big complex graphs' do
192
185
  before do
193
186
  class DummyPerson < ActiveTriples::Resource
metadata CHANGED
@@ -1,139 +1,153 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ld4l-foaf_rdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - E. Lynette Rayle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-18 00:00:00.000000000 Z
11
+ date: 2016-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdf
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - '>='
18
18
  - !ruby/object:Gem::Version
19
- version: '1.1'
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - '>='
25
25
  - !ruby/object:Gem::Version
26
- version: '1.1'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: active-triples
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '>='
32
32
  - !ruby/object:Gem::Version
33
- version: '0.5'
33
+ version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '>='
39
39
  - !ruby/object:Gem::Version
40
- version: '0.5'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: active_triples-local_name
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - '>='
46
46
  - !ruby/object:Gem::Version
47
- version: '0.1'
47
+ version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - '>='
53
53
  - !ruby/object:Gem::Version
54
- version: '0.1'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: pry
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
60
74
  - !ruby/object:Gem::Version
61
75
  version: '0'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - ">="
80
+ - - '>='
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rdoc
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - ">="
87
+ - - '>='
74
88
  - !ruby/object:Gem::Version
75
89
  version: '0'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - ">="
94
+ - - '>='
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rspec
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - ">="
101
+ - - '>='
88
102
  - !ruby/object:Gem::Version
89
103
  version: '0'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - ">="
108
+ - - '>='
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: coveralls
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
- - - ">="
115
+ - - '>='
102
116
  - !ruby/object:Gem::Version
103
117
  version: '0'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
- - - ">="
122
+ - - '>='
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: guard-rspec
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
- - - ">="
129
+ - - '>='
116
130
  - !ruby/object:Gem::Version
117
131
  version: '0'
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
- - - ">="
136
+ - - '>='
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: webmock
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
- - - ">="
143
+ - - '>='
130
144
  - !ruby/object:Gem::Version
131
145
  version: '0'
132
146
  type: :development
133
147
  prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
- - - ">="
150
+ - - '>='
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
139
153
  description: LD4L FOAF RDF provides tools for modeling a person triples based on the
@@ -146,9 +160,9 @@ extra_rdoc_files:
146
160
  - LICENSE.txt
147
161
  - README.md
148
162
  files:
149
- - ".coveralls.yml"
150
- - ".gitignore"
151
- - ".travis.yml"
163
+ - .coveralls.yml
164
+ - .gitignore
165
+ - .travis.yml
152
166
  - Gemfile
153
167
  - LICENSE.txt
154
168
  - README.md
@@ -159,6 +173,7 @@ files:
159
173
  - lib/ld4l/foaf_rdf/configuration.rb
160
174
  - lib/ld4l/foaf_rdf/person.rb
161
175
  - lib/ld4l/foaf_rdf/version.rb
176
+ - spec/ld4l/foaf_rdf/agent_rdf_spec.rb
162
177
  - spec/ld4l/foaf_rdf/configuration_spec.rb
163
178
  - spec/ld4l/foaf_rdf/person_rdf_spec.rb
164
179
  - spec/ld4l/foaf_rdf_spec.rb
@@ -173,21 +188,22 @@ require_paths:
173
188
  - lib
174
189
  required_ruby_version: !ruby/object:Gem::Requirement
175
190
  requirements:
176
- - - ">="
191
+ - - '>='
177
192
  - !ruby/object:Gem::Version
178
193
  version: 1.9.3
179
194
  required_rubygems_version: !ruby/object:Gem::Requirement
180
195
  requirements:
181
- - - ">="
196
+ - - '>='
182
197
  - !ruby/object:Gem::Version
183
198
  version: '0'
184
199
  requirements: []
185
200
  rubyforge_project:
186
- rubygems_version: 2.4.3
201
+ rubygems_version: 2.2.2
187
202
  signing_key:
188
203
  specification_version: 4
189
204
  summary: FOAF RDF models.
190
205
  test_files:
206
+ - spec/ld4l/foaf_rdf/agent_rdf_spec.rb
191
207
  - spec/ld4l/foaf_rdf/configuration_spec.rb
192
208
  - spec/ld4l/foaf_rdf/person_rdf_spec.rb
193
209
  - spec/ld4l/foaf_rdf_spec.rb