ricordami 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.
- data/CHANGELOG.md +13 -0
- data/ISSUES.md +0 -0
- data/MIT-LICENSE +21 -0
- data/README.md +454 -0
- data/TODO.md +21 -0
- data/examples/calls.rb +64 -0
- data/examples/singers.rb +42 -0
- data/lib/ricordami/attribute.rb +52 -0
- data/lib/ricordami/can_be_queried.rb +133 -0
- data/lib/ricordami/can_be_validated.rb +27 -0
- data/lib/ricordami/can_have_relationships.rb +152 -0
- data/lib/ricordami/configuration.rb +39 -0
- data/lib/ricordami/connection.rb +22 -0
- data/lib/ricordami/exceptions.rb +14 -0
- data/lib/ricordami/has_attributes.rb +159 -0
- data/lib/ricordami/has_indices.rb +105 -0
- data/lib/ricordami/is_lockable.rb +52 -0
- data/lib/ricordami/is_persisted.rb +123 -0
- data/lib/ricordami/is_retrievable.rb +35 -0
- data/lib/ricordami/key_namer.rb +63 -0
- data/lib/ricordami/model.rb +29 -0
- data/lib/ricordami/query.rb +68 -0
- data/lib/ricordami/relationship.rb +40 -0
- data/lib/ricordami/unique_index.rb +59 -0
- data/lib/ricordami/unique_validator.rb +21 -0
- data/lib/ricordami/value_index.rb +26 -0
- data/lib/ricordami/version.rb +3 -0
- data/lib/ricordami.rb +26 -0
- data/spec/acceptance/manage_relationships_spec.rb +42 -0
- data/spec/acceptance/model_with_validation_spec.rb +78 -0
- data/spec/acceptance/query_model_spec.rb +93 -0
- data/spec/acceptance_helper.rb +2 -0
- data/spec/ricordami/attribute_spec.rb +113 -0
- data/spec/ricordami/can_be_queried_spec.rb +254 -0
- data/spec/ricordami/can_be_validated_spec.rb +115 -0
- data/spec/ricordami/can_have_relationships_spec.rb +255 -0
- data/spec/ricordami/configuration_spec.rb +45 -0
- data/spec/ricordami/connection_spec.rb +25 -0
- data/spec/ricordami/exceptions_spec.rb +43 -0
- data/spec/ricordami/has_attributes_spec.rb +266 -0
- data/spec/ricordami/has_indices_spec.rb +73 -0
- data/spec/ricordami/is_lockable_spec.rb +45 -0
- data/spec/ricordami/is_persisted_spec.rb +186 -0
- data/spec/ricordami/is_retrievable_spec.rb +55 -0
- data/spec/ricordami/key_namer_spec.rb +56 -0
- data/spec/ricordami/model_spec.rb +65 -0
- data/spec/ricordami/query_spec.rb +156 -0
- data/spec/ricordami/relationship_spec.rb +123 -0
- data/spec/ricordami/unique_index_spec.rb +87 -0
- data/spec/ricordami/unique_validator_spec.rb +41 -0
- data/spec/ricordami/value_index_spec.rb +40 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/constants.rb +43 -0
- data/spec/support/db_manager.rb +18 -0
- data/test/bin/data_loader.rb +107 -0
- data/test/data/domains.txt +462 -0
- data/test/data/first_names.txt +1220 -0
- data/test/data/last_names.txt +1028 -0
- data/test/data/people_100_000.csv.bz2 +0 -0
- data/test/data/people_10_000.csv.bz2 +0 -0
- data/test/data/people_1_000_000.csv.bz2 +0 -0
- metadata +258 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Ricordami::HasIndices do
|
4
|
+
describe "the class" do
|
5
|
+
uses_constants("Car")
|
6
|
+
before(:each) do
|
7
|
+
Car.attribute :model
|
8
|
+
Car.attribute :name
|
9
|
+
end
|
10
|
+
|
11
|
+
it "raises an error when declaring an index that's neither unique or a value index" do
|
12
|
+
lambda { Car.index }.should raise_error(Ricordami::InvalidIndexDefinition)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "declaring a value index" do
|
16
|
+
it "can declare a value index with #index" do
|
17
|
+
index = Car.index :value => :model
|
18
|
+
Car.indices[:model].should be_a(Ricordami::ValueIndex)
|
19
|
+
Car.indices[:model].should == index
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "declaring a unique index" do
|
24
|
+
it "can declare a unique index with #index" do
|
25
|
+
index = Car.index :unique => :model
|
26
|
+
Car.indices[:model].should be_a(Ricordami::UniqueIndex)
|
27
|
+
Car.indices[:model].should == index
|
28
|
+
end
|
29
|
+
|
30
|
+
it "discards the subsequent declarations if the same index is created more than once" do
|
31
|
+
Car.index :unique => :model, :get_by => true
|
32
|
+
Car.indices[:model].need_get_by.should be_true
|
33
|
+
Car.index :unique => :model, :get_by => false
|
34
|
+
Car.indices[:model].need_get_by.should be_true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "saves the values of the unique attributes into the indices" do
|
38
|
+
Car.index :unique => :name
|
39
|
+
car = Car.new(:name => "Prius")
|
40
|
+
car.save
|
41
|
+
Car.indices[:name].all.should == ["Prius"]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "replaces old values with new ones into the indices" do
|
45
|
+
Car.index :unique => :name
|
46
|
+
car = Car.new(:name => "Prius")
|
47
|
+
car.save
|
48
|
+
Car.indices[:name].all.should == ["Prius"]
|
49
|
+
car.name = "Rav4"
|
50
|
+
car.save
|
51
|
+
Car.indices[:name].all.should == ["Rav4"]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "deletes the values of the unique attributes from the indices" do
|
55
|
+
Car.index :unique => :name
|
56
|
+
car = Car.create(:name => "Prius")
|
57
|
+
car.delete.should be_true
|
58
|
+
Car.indices[:name].all.should be_empty
|
59
|
+
end
|
60
|
+
|
61
|
+
it "adds a get_by_xxx method for each unique index xxx declared if :get_by is true" do
|
62
|
+
Car.index :unique => :name, :get_by => true
|
63
|
+
%w(il etait un petit navire).each { |n| Car.create(:name => n) }
|
64
|
+
Car.get_by_name("petit").name.should == "petit"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "doesn't add the get_by_xxx method if :get_by is not true" do
|
68
|
+
Car.index :unique => :name
|
69
|
+
Car.should_not respond_to(:get_by_name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Ricordami::IsLockable do
|
4
|
+
uses_constants("Client")
|
5
|
+
|
6
|
+
it "can get only one model lock at a time" do
|
7
|
+
5.times do
|
8
|
+
ts1 = ts2 = nil
|
9
|
+
client = Client.create
|
10
|
+
t1 = Thread.new do
|
11
|
+
client.lock! { sleep(0.01); ts1 = Time.now }
|
12
|
+
end
|
13
|
+
t2 = Thread.new do
|
14
|
+
sleep(0.01)
|
15
|
+
client.lock! { ts2 = Time.now }
|
16
|
+
end
|
17
|
+
t1.join
|
18
|
+
t2.join
|
19
|
+
ts2.should >= ts1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can get one lock per instance" do
|
24
|
+
5.times do
|
25
|
+
c1ok, c2ok = false, false
|
26
|
+
client = Client.create
|
27
|
+
other = Client.create
|
28
|
+
t1 = Thread.new do
|
29
|
+
client.lock! do
|
30
|
+
c1ok = true
|
31
|
+
sleep 0.05 until c2ok
|
32
|
+
end
|
33
|
+
end
|
34
|
+
t2 = Thread.new do
|
35
|
+
other.lock! do
|
36
|
+
c2ok = true
|
37
|
+
sleep 0.05 until c1ok
|
38
|
+
end
|
39
|
+
end
|
40
|
+
t1.join
|
41
|
+
t2.join
|
42
|
+
c2ok.should be_true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Ricordami::IsPersisted do
|
4
|
+
uses_constants("Tenant")
|
5
|
+
|
6
|
+
it "is not persisted and is a new record after it's just been initialized" do
|
7
|
+
tenant = Tenant.new
|
8
|
+
tenant.should_not be_persisted
|
9
|
+
tenant.should be_a_new_record
|
10
|
+
end
|
11
|
+
|
12
|
+
it "is persisted and not a new record after it's been successfully saved with #save" do
|
13
|
+
tenant = Tenant.new
|
14
|
+
tenant.save
|
15
|
+
tenant.should be_persisted
|
16
|
+
tenant.should_not be_a_new_record
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns the redis driver instance with #redis" do
|
20
|
+
Tenant.redis.should == Ricordami.driver
|
21
|
+
Tenant.new.redis.should == Ricordami.driver
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#save & #create" do
|
25
|
+
shared_examples_for "a persister" do
|
26
|
+
it "persists a new model" do
|
27
|
+
Tenant.attribute :balance
|
28
|
+
persister_action.call
|
29
|
+
attributes = Ricordami.driver.hgetall("Tenant:att:jojo")
|
30
|
+
attributes["id"].should == "jojo"
|
31
|
+
attributes["balance"].should == "-$99.98"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#save" do
|
36
|
+
let(:persister_action) {
|
37
|
+
Proc.new {
|
38
|
+
tenant = Tenant.new(:id => "jojo", :balance => "-$99.98")
|
39
|
+
tenant.save.should be_true
|
40
|
+
}
|
41
|
+
}
|
42
|
+
it_should_behave_like "a persister"
|
43
|
+
it "returns false if saving failed" do
|
44
|
+
switch_db_to_error
|
45
|
+
Tenant.new.save.should be_false
|
46
|
+
switch_db_to_ok
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#create" do
|
51
|
+
let(:persister_action) {
|
52
|
+
Proc.new {
|
53
|
+
tenant = Tenant.create(:id => "jojo", :balance => "-$99.98")
|
54
|
+
tenant.should be_a(Tenant)
|
55
|
+
tenant.should be_persisted
|
56
|
+
}
|
57
|
+
}
|
58
|
+
it_should_behave_like "a persister"
|
59
|
+
it "returns an instance not persisted if creating failed" do
|
60
|
+
switch_db_to_error
|
61
|
+
tenant = Tenant.create(:id => "jojo", :balance => "-$99.98")
|
62
|
+
tenant.should be_a(Tenant)
|
63
|
+
tenant.should_not be_persisted
|
64
|
+
switch_db_to_ok
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#reload and #update_attributes" do
|
70
|
+
it "reloads the attribute values from the DB with #reload" do
|
71
|
+
Tenant.attribute :name
|
72
|
+
tenant = Tenant.create(:name => "gainsbourg")
|
73
|
+
tenant.name = "updated"
|
74
|
+
tenant.name.should == "updated"
|
75
|
+
tenant.reload
|
76
|
+
tenant.name.should == "gainsbourg"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "updates passed attribute values and saves the model with #update_attributes" do
|
80
|
+
[:first, :last].each { |a| Tenant.attribute a }
|
81
|
+
tenant = Tenant.create(:first => "serge", :last => "gainsbourg")
|
82
|
+
tenant.update_attributes(:first => "SERGE").should be_true
|
83
|
+
tenant.reload
|
84
|
+
tenant.first.should == "SERGE"
|
85
|
+
tenant.last.should == "gainsbourg"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "raise an error if #update_attributes can't save the changes" do
|
89
|
+
tenant = Tenant.create(:id => "gainsbare") # id is read-only
|
90
|
+
lambda {
|
91
|
+
tenant.update_attributes(:id => "gainsbourg")
|
92
|
+
}.should raise_error(Ricordami::ReadOnlyAttribute)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "grouping DB operations with #queue_saving_operations" do
|
97
|
+
it "raises an error if no block is passed" do
|
98
|
+
lambda {
|
99
|
+
Tenant.queue_saving_operations
|
100
|
+
}.should raise_error(ArgumentError)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "raises an error if the block passed doesn't take 2 arguments" do
|
104
|
+
lambda {
|
105
|
+
Tenant.queue_saving_operations { |one| [one] }
|
106
|
+
}.should raise_error(ArgumentError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "allows to queue DB operations that will run when instance is saved" do
|
110
|
+
logs = []
|
111
|
+
Tenant.attribute :name
|
112
|
+
Tenant.queue_saving_operations do |obj, session|
|
113
|
+
logs << if obj.persisted?
|
114
|
+
"ALREADY persisted"
|
115
|
+
else
|
116
|
+
"NOT persisted"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
tenant = Tenant.new
|
120
|
+
tenant.save
|
121
|
+
tenant.update_attributes(:name => "blah")
|
122
|
+
logs.should == ["NOT persisted", "ALREADY persisted"]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "grouping DB operations with #queue_deleting_operations" do
|
127
|
+
it "raises an error if no block is passed" do
|
128
|
+
lambda {
|
129
|
+
Tenant.queue_deleting_operations
|
130
|
+
}.should raise_error(ArgumentError)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "raises an error if the block passed doesn't take 2 arguments" do
|
134
|
+
lambda {
|
135
|
+
Tenant.queue_deleting_operations { |one| [one] }
|
136
|
+
}.should raise_error(ArgumentError)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "allows to queue DB operations that will run when instance is deleted" do
|
140
|
+
logs = []
|
141
|
+
Tenant.attribute :name
|
142
|
+
Tenant.queue_deleting_operations { |obj, session| logs << "Tenant deleted #1" }
|
143
|
+
Tenant.queue_deleting_operations { |obj, session| logs << "Tenant deleted #2" }
|
144
|
+
tenant = Tenant.create
|
145
|
+
tenant.delete
|
146
|
+
logs.should == ["Tenant deleted #2", "Tenant deleted #1"]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "#delete" do
|
151
|
+
before(:each) { Tenant.attribute :name }
|
152
|
+
let(:tenant) { Tenant.create(:id => "myid") }
|
153
|
+
|
154
|
+
it "deletes the attributes from the DB" do
|
155
|
+
tenant.delete.should be_true
|
156
|
+
from_db = Ricordami.driver.hgetall("Tenant:att:myid")
|
157
|
+
from_db.should be_empty
|
158
|
+
end
|
159
|
+
|
160
|
+
it "knows when a model has been deleted" do
|
161
|
+
tenant.should_not be_deleted
|
162
|
+
tenant.delete
|
163
|
+
tenant.should be_deleted
|
164
|
+
end
|
165
|
+
|
166
|
+
it "freezes the model after deleting it" do
|
167
|
+
tenant.delete
|
168
|
+
tenant.should be_frozen
|
169
|
+
end
|
170
|
+
|
171
|
+
it "can't save a model that was deleted" do
|
172
|
+
tenant.delete
|
173
|
+
lambda { tenant.save }.should raise_error(Ricordami::ModelHasBeenDeleted)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "can't update the attributes of a model that was deleted" do
|
177
|
+
tenant.delete
|
178
|
+
lambda {
|
179
|
+
tenant.update_attributes(:name => "titi")
|
180
|
+
}.should raise_error(Ricordami::ModelHasBeenDeleted)
|
181
|
+
end
|
182
|
+
|
183
|
+
it "can't delete a model that was deleted" do
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Ricordami::IsRetrievable do
|
4
|
+
uses_constants("Tenant")
|
5
|
+
|
6
|
+
describe "#get & #[]" do
|
7
|
+
it "loads a model with #get when it exists" do
|
8
|
+
Tenant.attribute :language
|
9
|
+
Tenant.new(:id => "Francois", :language => "French").save
|
10
|
+
tenant = Tenant.get("Francois")
|
11
|
+
tenant.attributes.should == {
|
12
|
+
"id" => "Francois",
|
13
|
+
"language" => "French"
|
14
|
+
}
|
15
|
+
tenant.should be_persisted
|
16
|
+
end
|
17
|
+
|
18
|
+
it "#get raises an error if the model is not found" do
|
19
|
+
lambda {
|
20
|
+
Tenant.get("doesn't exist")
|
21
|
+
}.should raise_error(Ricordami::NotFound)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "#[] is an alias for get" do
|
25
|
+
Tenant.new(:id => "myid").save
|
26
|
+
Tenant["myid"].attributes["id"].should == "myid"
|
27
|
+
lambda { Tenant["nope"] }.should raise_error(Ricordami::NotFound)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#all & #count" do
|
32
|
+
it "saves the ids of new instances when saved" do
|
33
|
+
Tenant.attribute :name
|
34
|
+
instance = Tenant.new(:id => "hi")
|
35
|
+
instance.save
|
36
|
+
Tenant.indices[:id].all.should == ["hi"]
|
37
|
+
instance.name = "john"
|
38
|
+
instance.save
|
39
|
+
Tenant.indices[:id].all.should == ["hi"]
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns all the instances with #all" do
|
43
|
+
%w(allo la terre).each { |n| Tenant.create(:id => n) }
|
44
|
+
Tenant.all.map(&:id).should =~ ["allo", "la", "terre"]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns the number of instances with #count" do
|
48
|
+
Tenant.count.should == 0
|
49
|
+
Tenant.create(:id => "blah")
|
50
|
+
Tenant.count.should == 1
|
51
|
+
%w(allo la terre).each { |n| Tenant.create(:id => n) }
|
52
|
+
Tenant.count.should == 4
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Ricordami::KeyNamer do
|
4
|
+
subject { Ricordami::KeyNamer }
|
5
|
+
uses_constants("Call", "Leg", "Boat")
|
6
|
+
|
7
|
+
it "returns a key name for a sequence with #sequence" do
|
8
|
+
name = subject.sequence(Boat, :type => "id")
|
9
|
+
name.should == "Boat:seq:id"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns a key name for model attributes with #attributes" do
|
13
|
+
name = subject.attributes(Boat, :id => "42")
|
14
|
+
name.should == "Boat:att:42"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns a key name for a unique index with #unique_index" do
|
18
|
+
name = subject.unique_index(Call, :name => "ani")
|
19
|
+
name.should == "Call:udx:ani"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns a key name for a hash reference with #hash_ref" do
|
23
|
+
name = subject.hash_ref(Call, :fields=> ["ani", "dnis"])
|
24
|
+
name.should == "Call:hsh:ani_dnis_to_id"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns a key name for the value of a field of a model with #index" do
|
28
|
+
name = subject.index(Leg, :field => :direction, :value => "inout")
|
29
|
+
name.should == "Leg:idx:direction:aW5vdXQ="
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns a volatile key name for a set with #volatile_set" do
|
33
|
+
kn1 = subject.volatile_set(Call, :key => nil, :info => [:and, "username", "sex"])
|
34
|
+
kn1.should == "~:Call:set:and(username,sex)"
|
35
|
+
kn2 = subject.volatile_set(Call, :key => "~:Call:set:and(username,sex)",
|
36
|
+
:info => [:or, "country"])
|
37
|
+
kn2.should == "~:Call:set:and(username,sex):or(country)"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns a temporary key name for a model with #temporary" do
|
41
|
+
5.times do |i|
|
42
|
+
name = subject.temporary(Leg)
|
43
|
+
name.should == "Leg:val:_tmp:#{i + 1}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns a key name for the model lock with #lock" do
|
48
|
+
name = subject.lock(Leg, :id => 3)
|
49
|
+
name.should == "Leg:val:3:_lock"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns a key name for sort an attribute with #sort" do
|
53
|
+
name = subject.sort("Student", :sort_by => :name)
|
54
|
+
name.should == "Student:att:*->name"
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Ricordami::Model do
|
4
|
+
uses_constants('User')
|
5
|
+
|
6
|
+
it "uses ActiveSupport::Concern for a simple module structure" do
|
7
|
+
Ricordami::Model.should be_a_kind_of(ActiveSupport::Concern)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "includes the modules listed with #model_can" do
|
11
|
+
User.model_can :be_queried, :be_validated, :have_relationships
|
12
|
+
user = User.new
|
13
|
+
user.should be_a_kind_of(Ricordami::CanBeQueried)
|
14
|
+
user.should be_a_kind_of(Ricordami::CanBeValidated)
|
15
|
+
user.should be_a_kind_of(Ricordami::CanHaveRelationships)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "when being included" do
|
19
|
+
it "adds model naming" do
|
20
|
+
model_name = User.model_name
|
21
|
+
model_name.should == "User"
|
22
|
+
model_name.singular.should == "user"
|
23
|
+
model_name.plural.should == "users"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "adds to_model for use with Rails" do
|
27
|
+
user = User.new
|
28
|
+
user.to_model.should == user
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#to_key" do
|
32
|
+
before(:each) do
|
33
|
+
@user = User.new
|
34
|
+
def @user.id; "some_id" end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns [id] if persisted" do
|
38
|
+
def @user.persisted?; true end
|
39
|
+
@user.to_key.should == [@user.id]
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns nil if not persisted" do
|
43
|
+
def @user.persisted?; false end
|
44
|
+
@user.to_key.should be_nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#to_param" do
|
49
|
+
before(:each) do
|
50
|
+
@user = User.new
|
51
|
+
def @user.persisted?; true end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "returns key joined by - if to_key present" do
|
55
|
+
def @user.to_key; ["some", "id", "here"] end
|
56
|
+
@user.to_param.should == "some-id-here"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns nil if to_key nil" do
|
60
|
+
def @user.persisted?; false end
|
61
|
+
@user.to_param.should be_nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "ricordami/query"
|
3
|
+
|
4
|
+
describe Ricordami::Query do
|
5
|
+
uses_constants("Instrument")
|
6
|
+
let(:query) { Ricordami::Query.new(Instrument) }
|
7
|
+
|
8
|
+
it "has expressions" do
|
9
|
+
query.expressions.should == []
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#and" do
|
13
|
+
it "saves :and expressions" do
|
14
|
+
query.and(:allo => "la terre")
|
15
|
+
query.expressions.pop.should == [:and, {:allo => "la terre"}]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "uses :where as an alias for :and" do
|
19
|
+
query.where(:allo => "la terre")
|
20
|
+
query.expressions.pop.should == [:and, {:allo => "la terre"}]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns self" do
|
24
|
+
query.and.should == query
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#not" do
|
29
|
+
it "saves :not expressions" do
|
30
|
+
query.not(:allo => "la terre")
|
31
|
+
query.expressions.pop.should == [:not, {:allo => "la terre"}]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns self" do
|
35
|
+
query.not.should == query
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#any" do
|
40
|
+
it "saves :any expressions" do
|
41
|
+
query.any(:allo => "la terre")
|
42
|
+
query.expressions.pop.should == [:any, {:allo => "la terre"}]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns self" do
|
46
|
+
query.any.should == query
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "running the query" do
|
51
|
+
it "delegates #all to the runner" do
|
52
|
+
Instrument.should_receive(:all).with(:expressions => [[:and, {:key => "val"}]])
|
53
|
+
query.and(:key => "val").all
|
54
|
+
end
|
55
|
+
|
56
|
+
it "delegates #paginate to the runner" do
|
57
|
+
Instrument.should_receive(:paginate).with(:expressions => [[:and, {:key => "val"}]], :page => 3, :per_page => 18)
|
58
|
+
query.and(:key => "val").paginate(:page => 3, :per_page => 18)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "delegates #first to the runner" do
|
62
|
+
Instrument.should_receive(:first).with(:expressions => [[:and, {:key => "val"}]],
|
63
|
+
:sort_by => :key,
|
64
|
+
:order => "ALPHA ASC")
|
65
|
+
query.and(:key => "val").sort(:key).first
|
66
|
+
end
|
67
|
+
|
68
|
+
it "delegates #last to the runner" do
|
69
|
+
Instrument.should_receive(:last).with(:expressions => [[:and, {:key => "val"}]],
|
70
|
+
:sort_by => :key,
|
71
|
+
:order => "ALPHA DESC")
|
72
|
+
query.and(:key => "val").sort(:key, :desc_alpha).last
|
73
|
+
end
|
74
|
+
|
75
|
+
it "delegates #rand to the runner" do
|
76
|
+
Instrument.should_receive(:respond_to?).with(:rand).and_return(true)
|
77
|
+
Instrument.should_receive(:rand).with(:expressions => [[:and, {:key => "val"}]],
|
78
|
+
:sort_by => :key,
|
79
|
+
:order => "ALPHA ASC")
|
80
|
+
query.and(:key => "val").sort(:key).rand
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns the runner if it can't delegate to the runner" do
|
84
|
+
Ricordami::Query.new([]).all.should == []
|
85
|
+
Ricordami::Query.new([]).paginate.should == []
|
86
|
+
end
|
87
|
+
|
88
|
+
it "accepts any unknown method and delegate it to the result of #all" do
|
89
|
+
instruments = %w(guitar bass drums).map { |value| Struct.new(:name).new(value) }
|
90
|
+
Instrument.should_receive(:all).
|
91
|
+
with(:expressions => [[:and, {:key => "val"}]]).
|
92
|
+
and_return(instruments)
|
93
|
+
query.and(:key => "val").map(&:name).should =~ ["guitar", "bass", "drums"]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "sorting the query" do
|
98
|
+
it "remembers the sorting attribute with #sort" do
|
99
|
+
query.sort(:username)
|
100
|
+
query.sort_by.should == :username
|
101
|
+
end
|
102
|
+
|
103
|
+
it "remembers the sorting direction with #sort" do
|
104
|
+
query.sort(:username)
|
105
|
+
query.sort_dir.should == :asc_alpha
|
106
|
+
query.sort(:username, :desc_alpha)
|
107
|
+
query.sort_dir.should == :desc_alpha
|
108
|
+
end
|
109
|
+
|
110
|
+
it "raises an error if the sorting order is not :asc_alpha, :asc_num, desc_alpha or :desc_num" do
|
111
|
+
lambda {
|
112
|
+
[:asc_alpha, :asc_num, :desc_alpha, :desc_num].each do |dir|
|
113
|
+
query.sort(:username, dir)
|
114
|
+
end
|
115
|
+
}.should_not raise_error(ArgumentError)
|
116
|
+
lambda {
|
117
|
+
query.sort(:username, :blah)
|
118
|
+
}.should raise_error(ArgumentError)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "creating new objects" do
|
123
|
+
before(:each) do
|
124
|
+
Instrument.attribute :name
|
125
|
+
Instrument.attribute :instrument_type
|
126
|
+
Instrument.attribute :difficulty
|
127
|
+
end
|
128
|
+
|
129
|
+
it "builds a new object with #build" do
|
130
|
+
query.and(:instrument_type => "wind", :name => "flute")
|
131
|
+
obj = query.build
|
132
|
+
obj.should be_an(Instrument)
|
133
|
+
obj.should_not be_persisted
|
134
|
+
obj.name.should == "flute"
|
135
|
+
obj.instrument_type.should == "wind"
|
136
|
+
end
|
137
|
+
|
138
|
+
it "can pass new attributes to #build" do
|
139
|
+
query.and(:instrument_type => "wind", :name => "flute")
|
140
|
+
obj = query.build(:name => "tuba", :difficulty => "hard")
|
141
|
+
obj.instrument_type.should == "wind"
|
142
|
+
obj.name.should == "tuba"
|
143
|
+
obj.difficulty.should == "hard"
|
144
|
+
end
|
145
|
+
|
146
|
+
it "creates a new object with #create" do
|
147
|
+
query.and(:instrument_type => "wind", :name => "flute")
|
148
|
+
obj = query.create(:name => "tuba", :difficulty => "hard")
|
149
|
+
obj.should be_an(Instrument)
|
150
|
+
obj.should be_persisted
|
151
|
+
obj.instrument_type.should == "wind"
|
152
|
+
obj.name.should == "tuba"
|
153
|
+
obj.difficulty.should == "hard"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|