kalimba 0.0.1

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.
@@ -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