kalimba 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ require "active_support/concern"
2
+
3
+ module Kalimba
4
+ # Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
5
+ # +record+ method to retrieve the record which did not validate.
6
+ #
7
+ # @example
8
+ # begin
9
+ # complex_operation_that_calls_save!_internally
10
+ # rescue Kalimba::RecordInvalid => invalid
11
+ # puts invalid.record.errors
12
+ # end
13
+ class RecordInvalid < KalimbaError
14
+ attr_reader :record
15
+ def initialize(record)
16
+ @record = record
17
+ errors = @record.errors.full_messages.join(", ")
18
+ # TODO: use I18n later
19
+ # super I18n.t("activerecord.errors.messages.record_invalid", :errors => errors)
20
+ super "invalid record"
21
+ end
22
+ end
23
+
24
+ module Validations
25
+ extend ActiveSupport::Concern
26
+ include ActiveModel::Validations
27
+
28
+ module ClassMethods
29
+ # Creates an object just like Persistence.create but calls <tt>save!</tt> instead of +save+
30
+ # so an exception is raised if the record is invalid.
31
+ def create!(attributes = {}, &block)
32
+ if attributes.is_a?(Array)
33
+ attributes.each { |attr| create!(attr, &block) }
34
+ else
35
+ create(attributes, &block) || (raise RecordInvalid, self)
36
+ end
37
+ end
38
+ end
39
+
40
+ def save(options = {})
41
+ perform_validations(options) ? super : false
42
+ end
43
+
44
+ def save!(options = {})
45
+ save || (raise RecordInvalid, self)
46
+ end
47
+
48
+ # Runs all the validations within the specified context. Returns true if no errors are found,
49
+ # false otherwise.
50
+ #
51
+ # If the argument is false (default is +nil+), the context is set to <tt>:create</tt> if
52
+ # <tt>new_record?</tt> is true, and to <tt>:update</tt> if it is not.
53
+ #
54
+ # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
55
+ # some <tt>:on</tt> option will only run in the specified context.
56
+ def valid?(context = nil)
57
+ context ||= (new_record? ? :create : :update)
58
+ output = super(context)
59
+ errors.empty? && output
60
+ end
61
+
62
+ protected
63
+
64
+ def perform_validations(options={})
65
+ perform_validation = options[:validate] != false
66
+ perform_validation ? valid?(options[:context]) : true
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Kalimba
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,145 @@
1
+ require "spec_helper"
2
+
3
+ describe "attribute handling" do
4
+ before :all do
5
+ class AttributeTestOilRig < Kalimba::Resource
6
+ type "http://schema.org/OilRig"
7
+ base_uri "http://example.org/oil_rigs"
8
+
9
+ property :safe, :predicate => "http://example.org/safe", :datatype => NS::XMLSchema["boolean"]
10
+ property :name, :predicate => "http://example.org/name", :datatype => NS::XMLSchema["string"]
11
+ property :local_time, :predicate => "http://example.org/time", :datatype => NS::XMLSchema["time"]
12
+ property :created_at, :predicate => "http://example.org/date", :datatype => NS::XMLSchema["date"]
13
+ end
14
+ end
15
+
16
+ let(:rig) { AttributeTestOilRig.for("bp1") }
17
+
18
+ describe "boolean value" do
19
+ subject { rig.safe }
20
+
21
+ context "when not set" do
22
+ it { should be_a NilClass }
23
+ end
24
+
25
+ context "when set to 'false'" do
26
+ before { rig.safe = false }
27
+
28
+ it { should be_a FalseClass }
29
+ end
30
+ end
31
+
32
+ describe "localized string" do
33
+ before do
34
+ # populate the storage with literals in different locales
35
+ { en: "Quarter Pounder with Cheese",
36
+ fr: "Le Big Mac" }.each do |lang, text|
37
+ Kalimba.repository.statements.create(subject: rig.subject,
38
+ predicate: rig.class.properties["name"][:predicate],
39
+ object: text.with_lang(lang))
40
+ end
41
+ end
42
+
43
+ subject { rig.name }
44
+
45
+ context "when retrieved" do
46
+ describe "string language" do
47
+ subject { rig.name.lang }
48
+
49
+ context "in :fr locale" do
50
+ around do |example|
51
+ I18n.with_locale(:fr) { example.call }
52
+ end
53
+
54
+ it { should eql :fr }
55
+ end
56
+
57
+ context "in :en locale" do
58
+ around do |example|
59
+ I18n.with_locale(:en) { example.call }
60
+ end
61
+
62
+ it { should eql :en }
63
+ end
64
+ end
65
+ end
66
+
67
+ context "when stored" do
68
+ it "should not be overwritten by a localized string in another language" do
69
+ rig.update_attributes(name: "Burger")
70
+
71
+ s1 = Redlander::Statement.new(subject: rig.subject,
72
+ predicate: rig.class.properties["name"][:predicate],
73
+ object: "Quarter Pounder with Cheese".with_lang(:en))
74
+ s2 = Redlander::Statement.new(subject: rig.subject,
75
+ predicate: rig.class.properties["name"][:predicate],
76
+ object: "Le Big Mac".with_lang(:fr))
77
+ s3 = Redlander::Statement.new(subject: rig.subject,
78
+ predicate: rig.class.properties["name"][:predicate],
79
+ object: "Burger")
80
+
81
+ Kalimba.repository.statements.to_a.should include s1
82
+ Kalimba.repository.statements.to_a.should include s2
83
+ Kalimba.repository.statements.to_a.should include s3
84
+ end
85
+ end
86
+ end
87
+
88
+ describe "multiparameter attribute" do
89
+ describe "localized string" do
90
+ subject { rig.name }
91
+
92
+ context "when assigned" do
93
+ before { rig.assign_attributes("name(1)" => "Oil Rig", "name(2)" => "en") }
94
+
95
+ it { should be_a LocalizedString }
96
+ it { should eql "Oil Rig" }
97
+ it "should have language set to 'en'" do
98
+ expect(rig.name.lang).to eql "en"
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "time" do
104
+ subject { rig.local_time }
105
+
106
+ context "when assigned from two parameters" do
107
+ before do
108
+ rig.assign_attributes("local_time(1)" => "2013-03-08",
109
+ "local_time(2)" => "18:14")
110
+ end
111
+
112
+ it { should be_a Time }
113
+ it { should eql Time.new(2013, 3, 8, 18, 14) }
114
+ end
115
+
116
+ context "when assigned from many parameters" do
117
+ before do
118
+ rig.assign_attributes("local_time(1i)" => "2013",
119
+ "local_time(2i)" => "03",
120
+ "local_time(3i)" => "08",
121
+ "local_time(4i)" => "18",
122
+ "local_time(5i)" => "14")
123
+ end
124
+
125
+ it { should be_a Time }
126
+ it { should eql Time.new(2013, 3, 8, 18, 14) }
127
+ end
128
+ end
129
+
130
+ describe "date" do
131
+ subject { rig.created_at }
132
+
133
+ context "when assigned" do
134
+ before do
135
+ rig.assign_attributes("created_at(1i)" => "2013",
136
+ "created_at(2i)" => "03",
137
+ "created_at(3i)" => "08")
138
+ end
139
+
140
+ it { should be_a Date }
141
+ it { should eql Date.new(2013, 3, 8) }
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,90 @@
1
+ require "spec_helper"
2
+
3
+ describe "callbacks" do
4
+ before :all do
5
+ class CallbacksTestPerson < Human
6
+ attr_accessor :triggers
7
+
8
+ type "http://schema.org/CallbacksTestPerson"
9
+ base_uri "http://example.com/people/"
10
+
11
+ before_create :trigger_1
12
+ before_update :trigger_2
13
+ before_save :trigger_3
14
+ before_destroy :trigger_4
15
+
16
+ def initialize(*args)
17
+ @triggers = []
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def trigger_1
24
+ @triggers << 1
25
+ end
26
+
27
+ def trigger_2
28
+ @triggers << 2
29
+ end
30
+
31
+ def trigger_3
32
+ @triggers << 3
33
+ end
34
+
35
+ def trigger_4
36
+ @triggers << 4
37
+ end
38
+ end
39
+ end
40
+
41
+ let(:person) { CallbacksTestPerson.create }
42
+
43
+ context "when creating a resource" do
44
+ it "should trigger 2 callbacks" do
45
+ expect(person.triggers.size).to eql 2
46
+ end
47
+
48
+ it "before_create callback should be triggered" do
49
+ expect(person.triggers).to include 1
50
+ end
51
+
52
+ it "before_save callback should be triggered" do
53
+ expect(person.triggers).to include 3
54
+ end
55
+ end
56
+
57
+ context "when destroying a resource" do
58
+ before do
59
+ person.triggers = []
60
+ person.destroy
61
+ end
62
+
63
+ it "should trigger 1 callback" do
64
+ expect(person.triggers.size).to eql 1
65
+ end
66
+
67
+ it "before_destroy callback should be triggered" do
68
+ expect(person.triggers).to include 4
69
+ end
70
+ end
71
+
72
+ context "when updating a resource" do
73
+ before do
74
+ person.triggers = []
75
+ person.update_attributes(name: "Vasya")
76
+ end
77
+
78
+ it "should trigger 2 callbacks" do
79
+ expect(person.triggers.size).to eql 2
80
+ end
81
+
82
+ it "before_update callback should be triggered" do
83
+ expect(person.triggers).to include 2
84
+ end
85
+
86
+ it "before_save callback should be triggered" do
87
+ expect(person.triggers).to include 3
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,344 @@
1
+ require "spec_helper"
2
+
3
+ describe Kalimba::Persistence do
4
+ before :all do
5
+ class PersistenceTestPerson < Engineer
6
+ type "http://schema.org/PersistenceTestPerson"
7
+ base_uri "http://example.org/people"
8
+ end
9
+
10
+ class PersistenceTestOilRig < Kalimba::Resource
11
+ type "http://schema.org/OilRig"
12
+ base_uri "http://example.org/oil_rigs"
13
+
14
+ property :safe, :predicate => "http://example.org/safe", :datatype => NS::XMLSchema["boolean"]
15
+ property :operator, :predicate => "http://example.org/operator", :datatype => :PersistenceTestPerson
16
+ has_many :saboteurs, :predicate => "http://example.org/saboteur", :datatype => "http://schema.org/Saboteur"
17
+ end
18
+ end
19
+
20
+ describe "associations" do
21
+ before { PersistenceTestOilRig.create }
22
+ let(:rig) { PersistenceTestOilRig.first }
23
+
24
+ describe "instantiation" do
25
+ context "of existing Kalimba resources" do
26
+ before do
27
+ Kalimba.repository.statements.create(:subject => rig.subject,
28
+ :predicate => PersistenceTestOilRig.properties["operator"][:predicate],
29
+ :object => PersistenceTestPerson.create.subject)
30
+ end
31
+
32
+ subject { rig.operator }
33
+
34
+ it { should be_an_instance_of(PersistenceTestPerson) }
35
+ end
36
+
37
+ context "of unknown Kalimba resources" do
38
+ before do
39
+ Kalimba.repository.statements.create(:subject => rig.subject,
40
+ :predicate => PersistenceTestOilRig.properties["saboteurs"][:predicate],
41
+ :object => URI("http://schema.org/Saboteur#Karen_Knight"))
42
+ Kalimba.repository.statements.create(:subject => rig.subject,
43
+ :predicate => PersistenceTestOilRig.properties["saboteurs"][:predicate],
44
+ :object => URI("http://schema.org/Saboteur#Lee_Knight"))
45
+ end
46
+
47
+ subject { rig.saboteurs }
48
+
49
+ it { should be_a(Enumerable) }
50
+
51
+ it "should yield anonymous class instances" do
52
+ expect(subject.first.class).to be_anonymous
53
+ end
54
+
55
+ it "should add the anonymous class to RDFSClass repository" do
56
+ subject
57
+ expect(Kalimba::Resource.descendants).to include(subject.first.class)
58
+ end
59
+
60
+ it "should not create more than one anonymous class for homogenious objects" do
61
+ expect(subject.first.class).to eql subject.last.class
62
+ end
63
+
64
+ it "should set class type to the declared datatype" do
65
+ expect(subject.first.class.type).to eql PersistenceTestOilRig.properties["saboteurs"][:datatype]
66
+ end
67
+
68
+ it "should set class base_uri to the URI of the instance (without the fragment)" do
69
+ expect(subject.first.class.base_uri).to eql URI("http://schema.org/Saboteur/")
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "count" do
76
+ subject { PersistenceTestPerson.count }
77
+
78
+ before do
79
+ 3.times { PersistenceTestPerson.create }
80
+ end
81
+
82
+ it { should eql 3 }
83
+ end
84
+
85
+ describe "find" do
86
+ let(:options) { {:conditions => {:rank => 4}} }
87
+
88
+ context "when records are found" do
89
+ before do
90
+ PersistenceTestPerson.create(:rank => 4, :duties => %w(sex sleep eat drink dream))
91
+ end
92
+
93
+ describe ":first" do
94
+ subject { PersistenceTestPerson.find(:first, options) }
95
+
96
+ it { should be_a PersistenceTestPerson }
97
+ end
98
+
99
+ describe ":all" do
100
+ subject { PersistenceTestPerson.find(:all, options) }
101
+
102
+ it { should be_a Enumerable }
103
+ it "should return an array of found entries" do
104
+ subject.size.should eql 1
105
+ subject.first.should be_a PersistenceTestPerson
106
+ end
107
+ end
108
+ end
109
+
110
+ context "when records are not found" do
111
+ before do
112
+ PersistenceTestPerson.create(:rank => 0, :duties => %w(sex sleep eat drink dream))
113
+ end
114
+
115
+ describe ":first" do
116
+ subject { PersistenceTestPerson.find(:first, options) }
117
+
118
+ it { should be_nil }
119
+ end
120
+
121
+ describe ":all" do
122
+ subject { PersistenceTestPerson.find(:all, options) }
123
+
124
+ it { should be_empty }
125
+ end
126
+ end
127
+ end
128
+
129
+ describe "create" do
130
+ let(:person) { PersistenceTestPerson.create }
131
+ subject { person }
132
+
133
+ it { should be_a Kalimba::Resource }
134
+
135
+ it "should persist the instance" do
136
+ subject.should be_persisted
137
+ subject.should_not be_new_record
138
+ end
139
+ end
140
+
141
+ describe "destroy_all" do
142
+ before do
143
+ 2.times { PersistenceTestPerson.create }
144
+ end
145
+
146
+ it "should destroy all instances of the given RDFS class" do
147
+ expect { PersistenceTestPerson.destroy_all }.to change(Kalimba.repository, :size).by(-2)
148
+ end
149
+
150
+ it "should not destroy instances of other RDFS classes" do
151
+ rig = PersistenceTestOilRig.create
152
+ PersistenceTestPerson.destroy_all
153
+ expect(PersistenceTestOilRig.exist?).to be_true
154
+ end
155
+ end
156
+
157
+ describe "exist?" do
158
+ subject { PersistenceTestPerson.exist? }
159
+
160
+ context "when there are no RDFS class instances in the repository" do
161
+ before { PersistenceTestPerson.new }
162
+
163
+ it { should be_false }
164
+ end
165
+
166
+ context "when there are RDFS class instances in the repository" do
167
+ before { PersistenceTestPerson.create }
168
+
169
+ it { should be_true }
170
+ end
171
+ end
172
+
173
+ describe "new record" do
174
+ let(:person) { PersistenceTestPerson.new }
175
+ subject { person }
176
+
177
+ it { should be_new_record }
178
+ it { should_not be_persisted }
179
+
180
+ describe "subject" do
181
+ subject { person.subject }
182
+
183
+ it { should be_nil }
184
+
185
+ context "after save" do
186
+ before { person.save }
187
+
188
+ it { should be_a URI }
189
+ end
190
+ end
191
+
192
+ describe "save" do
193
+ it "should return true" do
194
+ expect(person.save).to be_true
195
+ end
196
+
197
+ it "should persist the record" do
198
+ person.save
199
+ person.should_not be_new_record
200
+ person.should_not be_changed
201
+ person.should be_persisted
202
+ end
203
+
204
+ context "when failed halfway" do
205
+ # TODO: need a more "natural" method of causing an error on save
206
+ before { person.stub(store_type: false) }
207
+
208
+ it "should not leave remains in the repository" do
209
+ person.save
210
+ Kalimba.repository.statements.exist?(subject: person.subject).should be_false
211
+ end
212
+ end
213
+ end
214
+
215
+ context "with changes" do
216
+ context "when saved" do
217
+ it "should have values type cast" do
218
+ person.rank = "2"
219
+ expect {
220
+ person.save && person.reload
221
+ }.to change(person, :rank).from("2").to(2)
222
+ end
223
+
224
+ it "should not persist non-castable values" do
225
+ person.retired = true
226
+ expect(person.save).to be_true
227
+
228
+ person.reload
229
+ expect(person.retired).to be_nil
230
+ end
231
+ end
232
+
233
+ context "to a single value" do
234
+ before { person.rank = 1 }
235
+
236
+ context "when saved" do
237
+ before { subject.save }
238
+
239
+ it "should be added statements with changed attributes (type + rank)" do
240
+ expect(Kalimba.repository.size).to eql 2
241
+ end
242
+ end
243
+ end
244
+
245
+ context "to an association" do
246
+ let(:charlie) { PersistenceTestPerson.for("charlie") }
247
+ before do
248
+ charlie.rank = 99
249
+ person.boss = charlie
250
+ end
251
+
252
+ context "when saved" do
253
+ before { subject.save }
254
+
255
+ it "should persist the association" do
256
+ charlie.should_not be_new_record
257
+ charlie.should be_persisted
258
+ charlie.should_not be_changed
259
+ end
260
+ end
261
+ end
262
+
263
+ context "to collections" do
264
+ before { person.duties = %w(building designing) }
265
+
266
+ context "when saved" do
267
+ before { person.save }
268
+
269
+ it "should be added statements with changed attributes (type + duties*2)" do
270
+ expect(Kalimba.repository.size).to eql 3
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ describe "reload" do
277
+ subject { PersistenceTestPerson.for(person.id) }
278
+
279
+ context "with related data in the storage" do
280
+ before { person.update_attributes(:rank => 7, :duties => %w(idling procrastinating)) }
281
+
282
+ it "should assign attributes the values from the storage" do
283
+ expect { subject.reload }.to change { subject.attributes["rank"] }.from(nil).to(7)
284
+ end
285
+
286
+ it "should assign attributes the collections from the storage" do
287
+ expect { subject.reload }.to change { subject.attributes["duties"] }.from([]).to(%w(idling procrastinating))
288
+ end
289
+
290
+ context "when accessing an attribute" do
291
+ it "should retrieve it from the storage" do
292
+ expect { subject.rank }.to change { subject.attributes["rank"] }.from(nil).to(7)
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ describe "already persisted record" do
300
+ let(:person) { PersistenceTestPerson.create }
301
+ subject { person }
302
+
303
+ it { should_not be_new_record }
304
+ it { should be_persisted }
305
+
306
+ describe "subject" do
307
+ subject { person.subject }
308
+
309
+ it { should be_a URI }
310
+ end
311
+
312
+ describe "destroy" do
313
+ before { @another = PersistenceTestPerson.create }
314
+
315
+ it "should remove the record from the storage" do
316
+ subject # create subject
317
+ expect { subject.destroy }.to change(Kalimba.repository, :size).by(-1)
318
+ subject.should_not be_new_record
319
+ subject.should_not be_persisted
320
+ subject.should be_destroyed
321
+ subject.should be_frozen
322
+ end
323
+
324
+ it "should not remove other records from the storage" do
325
+ subject.destroy
326
+ expect(Kalimba.repository.statements.exist?(:subject => @another.subject)).to be_true
327
+ end
328
+ end
329
+
330
+ describe "update_attributes" do
331
+ subject { person.update_attributes(:rank => 9) }
332
+
333
+ it { should be_true }
334
+
335
+ it "should assign the given attributes" do
336
+ expect { person.update_attributes(:rank => 0) }.to change(person, :rank).to(0)
337
+ end
338
+
339
+ it "should have no changes" do
340
+ expect(person.changes).to be_empty
341
+ end
342
+ end
343
+ end
344
+ end