ricordami 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/CHANGELOG.md +13 -0
  2. data/ISSUES.md +0 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.md +454 -0
  5. data/TODO.md +21 -0
  6. data/examples/calls.rb +64 -0
  7. data/examples/singers.rb +42 -0
  8. data/lib/ricordami/attribute.rb +52 -0
  9. data/lib/ricordami/can_be_queried.rb +133 -0
  10. data/lib/ricordami/can_be_validated.rb +27 -0
  11. data/lib/ricordami/can_have_relationships.rb +152 -0
  12. data/lib/ricordami/configuration.rb +39 -0
  13. data/lib/ricordami/connection.rb +22 -0
  14. data/lib/ricordami/exceptions.rb +14 -0
  15. data/lib/ricordami/has_attributes.rb +159 -0
  16. data/lib/ricordami/has_indices.rb +105 -0
  17. data/lib/ricordami/is_lockable.rb +52 -0
  18. data/lib/ricordami/is_persisted.rb +123 -0
  19. data/lib/ricordami/is_retrievable.rb +35 -0
  20. data/lib/ricordami/key_namer.rb +63 -0
  21. data/lib/ricordami/model.rb +29 -0
  22. data/lib/ricordami/query.rb +68 -0
  23. data/lib/ricordami/relationship.rb +40 -0
  24. data/lib/ricordami/unique_index.rb +59 -0
  25. data/lib/ricordami/unique_validator.rb +21 -0
  26. data/lib/ricordami/value_index.rb +26 -0
  27. data/lib/ricordami/version.rb +3 -0
  28. data/lib/ricordami.rb +26 -0
  29. data/spec/acceptance/manage_relationships_spec.rb +42 -0
  30. data/spec/acceptance/model_with_validation_spec.rb +78 -0
  31. data/spec/acceptance/query_model_spec.rb +93 -0
  32. data/spec/acceptance_helper.rb +2 -0
  33. data/spec/ricordami/attribute_spec.rb +113 -0
  34. data/spec/ricordami/can_be_queried_spec.rb +254 -0
  35. data/spec/ricordami/can_be_validated_spec.rb +115 -0
  36. data/spec/ricordami/can_have_relationships_spec.rb +255 -0
  37. data/spec/ricordami/configuration_spec.rb +45 -0
  38. data/spec/ricordami/connection_spec.rb +25 -0
  39. data/spec/ricordami/exceptions_spec.rb +43 -0
  40. data/spec/ricordami/has_attributes_spec.rb +266 -0
  41. data/spec/ricordami/has_indices_spec.rb +73 -0
  42. data/spec/ricordami/is_lockable_spec.rb +45 -0
  43. data/spec/ricordami/is_persisted_spec.rb +186 -0
  44. data/spec/ricordami/is_retrievable_spec.rb +55 -0
  45. data/spec/ricordami/key_namer_spec.rb +56 -0
  46. data/spec/ricordami/model_spec.rb +65 -0
  47. data/spec/ricordami/query_spec.rb +156 -0
  48. data/spec/ricordami/relationship_spec.rb +123 -0
  49. data/spec/ricordami/unique_index_spec.rb +87 -0
  50. data/spec/ricordami/unique_validator_spec.rb +41 -0
  51. data/spec/ricordami/value_index_spec.rb +40 -0
  52. data/spec/spec_helper.rb +29 -0
  53. data/spec/support/constants.rb +43 -0
  54. data/spec/support/db_manager.rb +18 -0
  55. data/test/bin/data_loader.rb +107 -0
  56. data/test/data/domains.txt +462 -0
  57. data/test/data/first_names.txt +1220 -0
  58. data/test/data/last_names.txt +1028 -0
  59. data/test/data/people_100_000.csv.bz2 +0 -0
  60. data/test/data/people_10_000.csv.bz2 +0 -0
  61. data/test/data/people_1_000_000.csv.bz2 +0 -0
  62. 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