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,93 @@
|
|
1
|
+
require "acceptance_helper"
|
2
|
+
require "ricordami/can_be_queried"
|
3
|
+
|
4
|
+
class Singer
|
5
|
+
include Ricordami::Model
|
6
|
+
include Ricordami::CanBeQueried
|
7
|
+
|
8
|
+
attribute :username
|
9
|
+
attribute :email
|
10
|
+
attribute :first_name
|
11
|
+
attribute :last_name
|
12
|
+
attribute :deceased, :default => "false", :indexed => :value
|
13
|
+
|
14
|
+
index :unique => :username, :get_by => true
|
15
|
+
end
|
16
|
+
|
17
|
+
feature "Query model" do
|
18
|
+
def create_12_singers
|
19
|
+
[
|
20
|
+
["Serge", "Gainsbourg"], ["Alain", "Bashung"], ["Benjamin", "Biolay"],
|
21
|
+
["Charles", "Aznavour"], ["Yves", "Montand", true], ["Nino", "Ferrer", true],
|
22
|
+
["Johnny", "Hallyday"], ["David", "Guetta"], ["Bruno", "Benabar"],
|
23
|
+
["Alain", "Souchon"], ["Jacques", "Dutronc"], ["Georges", "Brasens"]
|
24
|
+
].each do |first, last, deceased|
|
25
|
+
Singer.create(:username => (first + last[0..0]).downcase, :first_name => first,
|
26
|
+
:last_name => last, :email => "#{first.downcase}@#{last.downcase}.fr",
|
27
|
+
:deceased => deceased ? "true" : "false").should be_persisted
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
scenario "retrieve models" do
|
32
|
+
Singer.create(:username => "lucien", :email => "serge@gainsbourg.com",
|
33
|
+
:first_name => "Serge", :last_name => "Gainsbourg")
|
34
|
+
Singer.get_by_username("lucien").id.should == "1"
|
35
|
+
|
36
|
+
Singer.create(:username => "bashung", :email => "alain@bashung.com",
|
37
|
+
:first_name => "Alain", :last_name => "Bashung")
|
38
|
+
Singer.get_by_username("bashung").id.should == "2"
|
39
|
+
|
40
|
+
Singer.create(:username => "ben", :email => "benjamin@biolay.com",
|
41
|
+
:first_name => "Benjamin", :last_name => "Biolay")
|
42
|
+
Singer.get_by_username("ben").id.should == "3"
|
43
|
+
|
44
|
+
Singer.count.should == 3
|
45
|
+
Singer["1"].email.should == "serge@gainsbourg.com"
|
46
|
+
|
47
|
+
Singer.get_by_username("bashung").first_name.should == "Alain"
|
48
|
+
end
|
49
|
+
|
50
|
+
scenario "finding models using basic queries" do
|
51
|
+
Singer.create(:username => "ben", :email => "benjamin@biolay.com",
|
52
|
+
:first_name => "Benjamin", :last_name => "Biolay", :deceased => "false")
|
53
|
+
Singer.create(:username => "lucien", :email => "serge@gainsbourg.com",
|
54
|
+
:first_name => "Serge", :last_name => "Gainsbourg", :deceased => "true")
|
55
|
+
Singer.create(:username => "bashung", :email => "alain@bashung.com",
|
56
|
+
:first_name => "Alain", :last_name => "Bashung", :deceased => "true")
|
57
|
+
|
58
|
+
deceased = Singer.and(:deceased => "true").all
|
59
|
+
deceased.map(&:username).should =~ %w(lucien bashung)
|
60
|
+
q = Singer.where(:deceased => "true")
|
61
|
+
q.sort(:first_name, :asc_alpha).first.email.should == "alain@bashung.com"
|
62
|
+
q.sort(:first_name, :desc_alpha).last.email.should == "alain@bashung.com"
|
63
|
+
Singer.not(:deceased => true).map(&:username).should == ["ben"]
|
64
|
+
first = Singer.first.username
|
65
|
+
last = Singer.last.username
|
66
|
+
rand = Singer.rand.username
|
67
|
+
first.should_not == last
|
68
|
+
Singer.all.map(&:username).should include(first)
|
69
|
+
Singer.all.map(&:username).should include(last)
|
70
|
+
Singer.all.map(&:username).should include(rand)
|
71
|
+
end
|
72
|
+
|
73
|
+
scenario "paginate list of models" do
|
74
|
+
create_12_singers
|
75
|
+
|
76
|
+
Singer.paginate(:page => 1, :per_page => 5).should have(5).singers
|
77
|
+
Singer.paginate(:page => 2, :per_page => 5).should have(5).singers
|
78
|
+
Singer.paginate(:page => 3, :per_page => 5).should have(2).singers
|
79
|
+
Singer.paginate(:page => 4, :per_page => 5).should have(0).singers
|
80
|
+
|
81
|
+
page1 = Singer.sort(:last_name, :asc_alpha).paginate(:page => 1, :per_page => 5)
|
82
|
+
page1.map(&:last_name).should == ["Aznavour", "Bashung", "Benabar", "Biolay", "Brasens"]
|
83
|
+
page3 = Singer.sort(:last_name, :asc_alpha).paginate(:page => 3, :per_page => 5)
|
84
|
+
page3.map(&:last_name).should == ["Montand", "Souchon"]
|
85
|
+
end
|
86
|
+
|
87
|
+
scenario "query using #and, #any and #not filters" do
|
88
|
+
Singer.index :value => :first_name
|
89
|
+
create_12_singers
|
90
|
+
|
91
|
+
Singer.where(:first_name => "Alain").all.map(&:last_name).should =~ %w(Bashung Souchon)
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Ricordami::Attribute do
|
4
|
+
subject { Ricordami::Attribute }
|
5
|
+
|
6
|
+
describe "an instance" do
|
7
|
+
it "has a name" do
|
8
|
+
attribute = subject.new(:singer)
|
9
|
+
attribute.name.should == :singer
|
10
|
+
end
|
11
|
+
|
12
|
+
it "accepts a string for its name" do
|
13
|
+
attribute = subject.new("string")
|
14
|
+
attribute.name.should == :string
|
15
|
+
end
|
16
|
+
|
17
|
+
it "accepts also a symbol for its name" do
|
18
|
+
attribute = subject.new(:string)
|
19
|
+
attribute.name.should == :string
|
20
|
+
end
|
21
|
+
|
22
|
+
it "has an option :default for a default value" do
|
23
|
+
attribute = subject.new(:georges, :default => "jungle")
|
24
|
+
attribute.default_value.should == "jungle"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "doesn't have a default value if :default is not specified" do
|
28
|
+
subject.new(:no_defaults).default_value.should be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "allows to use a block as a default value for dynamic values" do
|
32
|
+
i = 0
|
33
|
+
attribute = subject.new(:sequence, :default => Proc.new { i += 1 })
|
34
|
+
attribute.default_value.should == 1
|
35
|
+
attribute.default_value.should == 2
|
36
|
+
attribute.default_value.should == 3
|
37
|
+
end
|
38
|
+
|
39
|
+
it "retuns if a default value is set with #default_value?" do
|
40
|
+
subject.new(:blah, :default => "1").default_value?.should be_true
|
41
|
+
without = subject.new(:foo).default_value?.should be_false
|
42
|
+
end
|
43
|
+
|
44
|
+
it "has an option :read_only when the attribute value can only be set once" do
|
45
|
+
attribute = subject.new(:georges, :read_only => true)
|
46
|
+
attribute.should be_read_only
|
47
|
+
end
|
48
|
+
|
49
|
+
it "its value can be set more than once when :read_only is not set" do
|
50
|
+
subject.new(:not_read_only).should_not be_read_only
|
51
|
+
end
|
52
|
+
|
53
|
+
it "has an option :indexed to index the attribute as unique" do
|
54
|
+
attribute = subject.new(:georges, :indexed => :unique)
|
55
|
+
attribute.indexed.should == :unique
|
56
|
+
end
|
57
|
+
|
58
|
+
it "has an option :indexed to index the attribute by value" do
|
59
|
+
attribute = subject.new(:georges, :indexed => :value)
|
60
|
+
attribute.indexed.should == :value
|
61
|
+
end
|
62
|
+
|
63
|
+
it "raises an error if :indexed is not :unique or :value" do
|
64
|
+
lambda {
|
65
|
+
subject.new(:georges, :indexed => :blah)
|
66
|
+
}.should raise_error(Ricordami::InvalidIndexDefinition)
|
67
|
+
lambda {
|
68
|
+
subject.new(:georges)
|
69
|
+
}.should_not raise_error
|
70
|
+
end
|
71
|
+
|
72
|
+
it "its value can be used for queries when :indexed is not set" do
|
73
|
+
subject.new(:not_indexed).should_not be_indexed
|
74
|
+
end
|
75
|
+
|
76
|
+
it "has an option :initial for an initial value set when saved the first time" do
|
77
|
+
attribute = subject.new(:id, :initial => "123")
|
78
|
+
attribute.initial_value.should == "123"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "doesn't have an initial value if :initial is not specified" do
|
82
|
+
subject.new(:no_initials).initial_value.should be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "allows to use a block as an initial value for dynamic values" do
|
86
|
+
i = 1
|
87
|
+
attribute = subject.new(:id, :initial => Proc.new { i *= 2 })
|
88
|
+
attribute.initial_value.should == 2
|
89
|
+
attribute.initial_value.should == 4
|
90
|
+
attribute.initial_value.should == 8
|
91
|
+
end
|
92
|
+
|
93
|
+
it "retuns if an initial value is set with #initial_value?" do
|
94
|
+
subject.new(:id, :initial => "1").initial_value?.should be_true
|
95
|
+
without = subject.new(:foo).initial_value?.should be_false
|
96
|
+
end
|
97
|
+
|
98
|
+
it "can spefify the attribute type with :type option" do
|
99
|
+
attribute = subject.new(:id, :type => :integer)
|
100
|
+
attribute.type.should == :integer
|
101
|
+
end
|
102
|
+
|
103
|
+
it "has a default type :string if not specified" do
|
104
|
+
subject.new(:name).type.should == :string
|
105
|
+
end
|
106
|
+
|
107
|
+
it "returns the converter method with #converter" do
|
108
|
+
subject.new(:name, :type => :string).converter.should == :to_s
|
109
|
+
subject.new(:name, :type => :integer).converter.should == :to_i
|
110
|
+
subject.new(:name, :type => :float).converter.should == :to_f
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "ricordami/can_be_queried"
|
3
|
+
|
4
|
+
describe Ricordami::CanBeQueried do
|
5
|
+
uses_constants("Customer")
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
Customer.send(:include, Ricordami::CanBeQueried)
|
9
|
+
Customer.attribute :country, :indexed => :value
|
10
|
+
Customer.attribute :sex, :indexed => :value
|
11
|
+
Customer.attribute :name, :indexed => :value
|
12
|
+
Customer.attribute :kind, :indexed => :value
|
13
|
+
Customer.attribute :no_index
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "building queries" do
|
17
|
+
describe "#and" do
|
18
|
+
it "returns a new query" do
|
19
|
+
query = Customer.and
|
20
|
+
query.should be_a(Ricordami::Query)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "passes self as the query runner to the query" do
|
24
|
+
query = Customer.and
|
25
|
+
query.runner.should == Customer
|
26
|
+
end
|
27
|
+
|
28
|
+
it "delegates #and to the new query" do
|
29
|
+
query = Customer.and(:key => "value")
|
30
|
+
query.expressions.should == [[:and, {:key => "value"}]]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#not" do
|
35
|
+
it "returns a new query" do
|
36
|
+
query = Customer.not
|
37
|
+
query.should be_a(Ricordami::Query)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "delegates #not to the new query" do
|
41
|
+
query = Customer.not(:key => "value")
|
42
|
+
query.expressions.should == [[:not, {:key => "value"}]]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#any" do
|
47
|
+
it "returns a new query" do
|
48
|
+
query = Customer.any
|
49
|
+
query.should be_a(Ricordami::Query)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "delegates #any to the new query" do
|
53
|
+
query = Customer.any(:key => "value")
|
54
|
+
query.expressions.should == [[:any, {:key => "value"}]]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#sort" do
|
59
|
+
it "returns a new query" do
|
60
|
+
query = Customer.sort(:sex)
|
61
|
+
query.should be_a(Ricordami::Query)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "delegates #sort to the new query" do
|
65
|
+
query = Customer.sort(:sex, :desc_alpha)
|
66
|
+
query.sort_by.should == :sex
|
67
|
+
query.sort_dir.should == :desc_alpha
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "running queries" do
|
73
|
+
before(:each) do
|
74
|
+
Customer.create(:name => "Zhanna", :sex => "F", :country => "Latvia", :kind => "human")
|
75
|
+
Customer.create(:name => "Mathieu", :sex => "M", :country => "France", :kind => "human")
|
76
|
+
Customer.create(:name => "Sophie", :sex => "F", :country => "USA", :kind => "human")
|
77
|
+
Customer.create(:name => "Brioche", :sex => "F", :country => "USA", :kind => "dog")
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ":and" do
|
81
|
+
it "raises an error if there's no value index for one of the conditions" do
|
82
|
+
lambda {
|
83
|
+
Customer.and(:no_index => "Blah").all
|
84
|
+
}.should raise_error(Ricordami::MissingIndex)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns all entries if no conditions where passed" do
|
88
|
+
Customer.and.all.map(&:name).should =~ %w(Zhanna Mathieu Sophie Brioche)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns the models found with #all (1 condition, 1 result)" do
|
92
|
+
Customer.index :value => :name
|
93
|
+
found = Customer.and(:name => "Zhanna").all
|
94
|
+
found.map(&:name).should == ["Zhanna"]
|
95
|
+
end
|
96
|
+
|
97
|
+
it "returns the models found with #all (2 conditions, 2 results)" do
|
98
|
+
found = Customer.and(:country => "USA", :sex => "F").all
|
99
|
+
found.map(&:name).should =~ ["Sophie", "Brioche"]
|
100
|
+
end
|
101
|
+
|
102
|
+
it "returns the models found with #all for a composed query" do
|
103
|
+
found = Customer.and(:country => "USA").and(:sex => "F").all
|
104
|
+
found.map(&:name).should =~ ["Sophie", "Brioche"]
|
105
|
+
end
|
106
|
+
|
107
|
+
it "can use #where instead of #and" do
|
108
|
+
found = Customer.where(:country => "USA").and(:sex => "F").all
|
109
|
+
found.map(&:name).should =~ ["Sophie", "Brioche"]
|
110
|
+
end
|
111
|
+
|
112
|
+
it "doesn't require #all if another method call is chained" do
|
113
|
+
Customer.where(:country => "USA").and(:sex => "F").map(&:name).should =~ ["Sophie", "Brioche"]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe ":any" do
|
118
|
+
it "raises an error if there's no value index for one of the conditions" do
|
119
|
+
lambda {
|
120
|
+
Customer.any(:no_index => "Blah").all
|
121
|
+
}.should raise_error(Ricordami::MissingIndex)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "returns all entries if no conditions where passed" do
|
125
|
+
Customer.any.all.map(&:name).should =~ %w(Zhanna Mathieu Sophie Brioche)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "returns the models found with #all (1 condition, 1 result)" do
|
129
|
+
Customer.index :value => :name
|
130
|
+
found = Customer.any(:name => "Zhanna").all
|
131
|
+
found.map(&:name).should == ["Zhanna"]
|
132
|
+
end
|
133
|
+
|
134
|
+
it "returns the models found with #all (2 conditions, 3 results)" do
|
135
|
+
found = Customer.any(:country => "USA", :sex => "F").all
|
136
|
+
found.map(&:name).should =~ ["Sophie", "Brioche", "Zhanna"]
|
137
|
+
end
|
138
|
+
|
139
|
+
it "returns the models found with #all for a composed query" do
|
140
|
+
found = Customer.where(:country => "USA").any(:name => "Sophie", :kind => "dog").all
|
141
|
+
found.map(&:name).should =~ ["Sophie", "Brioche"]
|
142
|
+
found = Customer.where(:country => "USA").any(:name => "Sophie", :kind => "human").all
|
143
|
+
found.map(&:name).should == ["Sophie"]
|
144
|
+
end
|
145
|
+
|
146
|
+
it "doesn't require #all if another method call is chained" do
|
147
|
+
Customer.any(:country => "USA", :sex => "F").map(&:name).should =~ ["Sophie", "Brioche", "Zhanna"]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe ":not" do
|
152
|
+
it "raises an error if there's no value index for one of the conditions" do
|
153
|
+
lambda {
|
154
|
+
Customer.not(:no_index => "Blah").all
|
155
|
+
}.should raise_error(Ricordami::MissingIndex)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "returns all entries if no conditions where passed" do
|
159
|
+
Customer.not.all.map(&:name).should =~ %w(Zhanna Mathieu Sophie Brioche)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "returns the models found with #all (1 condition, 1 result)" do
|
163
|
+
Customer.index :value => :name
|
164
|
+
found = Customer.not(:name => "Zhanna").all
|
165
|
+
found.map(&:name).should =~ ["Sophie", "Brioche", "Mathieu"]
|
166
|
+
end
|
167
|
+
|
168
|
+
it "returns the models found with #all (2 conditions, 1 result)" do
|
169
|
+
found = Customer.not(:country => "USA", :sex => "F").all
|
170
|
+
found.map(&:name).should == ["Mathieu"]
|
171
|
+
end
|
172
|
+
|
173
|
+
it "returns the models found with #all for a composed query" do
|
174
|
+
found = Customer.where(:country => "USA").not(:name => "Sophie", :kind => "dog").all
|
175
|
+
found.map(&:name).should be_empty
|
176
|
+
found = Customer.where(:country => "USA").not(:name => "Sophie", :kind => "human").all
|
177
|
+
found.map(&:name).should == ["Brioche"]
|
178
|
+
end
|
179
|
+
|
180
|
+
it "doesn't require #all if another method call is chained" do
|
181
|
+
Customer.not(:country => "USA", :sex => "F").map(&:name).should == ["Mathieu"]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "sorting result" do
|
187
|
+
uses_constants("Student")
|
188
|
+
|
189
|
+
before(:each) do
|
190
|
+
Student.send(:include, Ricordami::CanBeQueried)
|
191
|
+
Student.attribute :name, :indexed => :value
|
192
|
+
Student.attribute :grade, :indexed => :value
|
193
|
+
Student.attribute :school, :indexed => :value
|
194
|
+
[["Zhanna", 12], ["Sophie", 19],
|
195
|
+
["Brioche", 4], ["Mathieu", 15]].each do |name, grade|
|
196
|
+
Student.create(:name => name, :grade => grade, :school => "Lajoo")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
it "can sort the result alphanumerically with #sort" do
|
201
|
+
result = Student.where(:school => "Lajoo").sort(:name, :asc_alpha)
|
202
|
+
result.map(&:name).should == %w(Brioche Mathieu Sophie Zhanna)
|
203
|
+
end
|
204
|
+
|
205
|
+
it "can sort the result numerically with #sort" do
|
206
|
+
result = Student.where(:school => "Lajoo").sort(:grade, :asc_num)
|
207
|
+
result.map(&:name).should == %w(Brioche Zhanna Mathieu Sophie)
|
208
|
+
end
|
209
|
+
|
210
|
+
it "defaults to sorting ascending / alphanumerically" do
|
211
|
+
result = Student.where(:school => "Lajoo").sort(:name)
|
212
|
+
result.map(&:name).should == %w(Brioche Mathieu Sophie Zhanna)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe "fetching result" do
|
217
|
+
before(:each) do
|
218
|
+
Student.send(:include, Ricordami::CanBeQueried)
|
219
|
+
Student.attribute :name, :indexed => :value
|
220
|
+
Student.attribute :grade, :indexed => :value
|
221
|
+
Student.attribute :school, :indexed => :value
|
222
|
+
[["Zhanna", 12], ["Sophie", 19],
|
223
|
+
["Brioche", 4], ["Mathieu", 15]].each do |name, grade|
|
224
|
+
Student.create(:name => name, :grade => grade, :school => "Lajoo")
|
225
|
+
end
|
226
|
+
@query = Student.where(:school => "Lajoo").sort(:name)
|
227
|
+
end
|
228
|
+
let(:query) { @query }
|
229
|
+
|
230
|
+
it "fetches all the results with #all" do
|
231
|
+
query.all.map(&:name).should == %w(Brioche Mathieu Sophie Zhanna)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "fetches the requested page of the results with #paginate" do
|
235
|
+
fetched = query.paginate(:page => 1, :per_page => 2)
|
236
|
+
fetched.map(&:name).should == %w(Brioche Mathieu)
|
237
|
+
fetched = query.paginate(:page => 2, :per_page => 2)
|
238
|
+
fetched.map(&:name).should == %w(Sophie Zhanna)
|
239
|
+
query.paginate(:page => 3, :per_page => 2).should be_empty
|
240
|
+
end
|
241
|
+
|
242
|
+
it "fetches the first instance with #first" do
|
243
|
+
query.first.name.should == "Brioche"
|
244
|
+
end
|
245
|
+
|
246
|
+
it "fetches the last instance with #last" do
|
247
|
+
query.last.name.should == "Zhanna"
|
248
|
+
end
|
249
|
+
|
250
|
+
it "fetches a random instance with #rand" do
|
251
|
+
%w(Brioche Mathieu Sophie Zhanna).should include(query.rand.name)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "ricordami/can_be_validated"
|
3
|
+
|
4
|
+
describe Ricordami::CanBeValidated do
|
5
|
+
describe "Active Model validations" do
|
6
|
+
uses_constants("Call")
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
Call.class_eval do
|
10
|
+
include Ricordami::CanBeValidated
|
11
|
+
attribute :ani
|
12
|
+
attribute :dnis
|
13
|
+
validates_presence_of :ani
|
14
|
+
validates_numericality_of :dnis, :allow_blank => true, :message => "is not numeric"
|
15
|
+
validate :cant_call_iself
|
16
|
+
|
17
|
+
private
|
18
|
+
def cant_call_iself
|
19
|
+
return true if ani.blank?
|
20
|
+
errors.add(:ani, "can't be the same as dnis") if ani == dnis
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can validate the presence of an attribute value" do
|
26
|
+
call = Call.new
|
27
|
+
call.should_not be_valid
|
28
|
+
call.should have(1).errors
|
29
|
+
call.errors[:ani].should == ["can't be blank"]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can validate that a field is numeric" do
|
33
|
+
call = Call.new(:ani => "123", :dnis => "nope")
|
34
|
+
call.should_not be_valid
|
35
|
+
call.should have(1).errors
|
36
|
+
call.errors[:dnis].should == ["is not numeric"]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can do custom validation" do
|
40
|
+
call = Call.new(:ani => "123", :dnis => "123")
|
41
|
+
call.should_not be_valid
|
42
|
+
call.should have(1).errors
|
43
|
+
call.errors[:ani].should == ["can't be the same as dnis"]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "can't validate a model that was deleted" do
|
47
|
+
call = Call.create(:ani => "123", :dnis => "456")
|
48
|
+
call.delete
|
49
|
+
lambda { call.valid? }.should raise_error(Ricordami::ModelHasBeenDeleted)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#save" do
|
54
|
+
uses_constants("Post")
|
55
|
+
|
56
|
+
before(:each) do
|
57
|
+
Post.class_eval do
|
58
|
+
include Ricordami::CanBeValidated
|
59
|
+
attribute :title
|
60
|
+
validates_presence_of :title
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "can't #save if it is not valid" do
|
65
|
+
post = Post.new
|
66
|
+
post.save.should be_false
|
67
|
+
end
|
68
|
+
|
69
|
+
it "can save if it is not valid but passed :validate => false" do
|
70
|
+
Post.new.save(:validate => false).should be_true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "validate attribute uniqueness" do
|
75
|
+
uses_constants("User")
|
76
|
+
|
77
|
+
before(:each) do
|
78
|
+
User.class_eval do
|
79
|
+
include Ricordami::CanBeValidated
|
80
|
+
attribute :username, :read_only => true
|
81
|
+
attribute :wife
|
82
|
+
validates_uniqueness_of :username
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "is valid if no other instance uses the same attribute value" do
|
87
|
+
user = User.new(:username => "Serge Gainsbourg")
|
88
|
+
user.should be_valid
|
89
|
+
end
|
90
|
+
|
91
|
+
it "is not valid if another instance uses the same attribute value" do
|
92
|
+
serge = User.new(:username => "Gainsbourg")
|
93
|
+
serge.save.should be_true
|
94
|
+
serge.should be_valid
|
95
|
+
|
96
|
+
usurpateur = User.new(:username => "Gainsbourg")
|
97
|
+
usurpateur.should_not be_valid
|
98
|
+
usurpateur.should have(1).error
|
99
|
+
usurpateur.errors[:username].should == ["is already used"]
|
100
|
+
end
|
101
|
+
|
102
|
+
it "allows to validate the uniqueness of an attribute that can be changed" do
|
103
|
+
User.validates_uniqueness_of(:wife)
|
104
|
+
User.create(:id => "serge", :username => "Gainsbourg", :wife => "Rita")
|
105
|
+
serge = User["serge"]
|
106
|
+
serge.should be_valid
|
107
|
+
serge.save
|
108
|
+
|
109
|
+
fred = User.new(:username => "Chichin", :wife => "Rita")
|
110
|
+
fred.should_not be_valid
|
111
|
+
fred.should have(1).error
|
112
|
+
fred.errors[:wife].should == ["is already used"]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|