toystore 0.5

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.
Files changed (113) hide show
  1. data/.autotest +11 -0
  2. data/.bundle/config +2 -0
  3. data/.gitignore +6 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +49 -0
  6. data/LICENSE +9 -0
  7. data/LOGGING.rdoc +16 -0
  8. data/README.rdoc +13 -0
  9. data/Rakefile +7 -0
  10. data/examples/memcached.rb +20 -0
  11. data/examples/memory.rb +20 -0
  12. data/examples/models.rb +51 -0
  13. data/examples/redis.rb +20 -0
  14. data/lib/toy.rb +81 -0
  15. data/lib/toy/attribute.rb +73 -0
  16. data/lib/toy/attributes.rb +137 -0
  17. data/lib/toy/caching.rb +20 -0
  18. data/lib/toy/callbacks.rb +48 -0
  19. data/lib/toy/collection.rb +55 -0
  20. data/lib/toy/connection.rb +28 -0
  21. data/lib/toy/dirty.rb +47 -0
  22. data/lib/toy/dolly.rb +30 -0
  23. data/lib/toy/embedded_list.rb +45 -0
  24. data/lib/toy/embedded_lists.rb +68 -0
  25. data/lib/toy/equality.rb +19 -0
  26. data/lib/toy/exceptions.rb +29 -0
  27. data/lib/toy/extensions/array.rb +22 -0
  28. data/lib/toy/extensions/boolean.rb +43 -0
  29. data/lib/toy/extensions/date.rb +24 -0
  30. data/lib/toy/extensions/float.rb +13 -0
  31. data/lib/toy/extensions/hash.rb +17 -0
  32. data/lib/toy/extensions/integer.rb +22 -0
  33. data/lib/toy/extensions/nil_class.rb +17 -0
  34. data/lib/toy/extensions/object.rb +26 -0
  35. data/lib/toy/extensions/set.rb +23 -0
  36. data/lib/toy/extensions/string.rb +17 -0
  37. data/lib/toy/extensions/time.rb +29 -0
  38. data/lib/toy/identity.rb +26 -0
  39. data/lib/toy/identity/abstract_key_factory.rb +10 -0
  40. data/lib/toy/identity/uuid_key_factory.rb +9 -0
  41. data/lib/toy/identity_map.rb +109 -0
  42. data/lib/toy/index.rb +74 -0
  43. data/lib/toy/indices.rb +56 -0
  44. data/lib/toy/inspect.rb +12 -0
  45. data/lib/toy/list.rb +46 -0
  46. data/lib/toy/lists.rb +37 -0
  47. data/lib/toy/logger.rb +26 -0
  48. data/lib/toy/mass_assignment_security.rb +16 -0
  49. data/lib/toy/persistence.rb +138 -0
  50. data/lib/toy/plugins.rb +23 -0
  51. data/lib/toy/proxies/embedded_list.rb +74 -0
  52. data/lib/toy/proxies/list.rb +97 -0
  53. data/lib/toy/proxies/proxy.rb +59 -0
  54. data/lib/toy/querying.rb +57 -0
  55. data/lib/toy/reference.rb +134 -0
  56. data/lib/toy/references.rb +19 -0
  57. data/lib/toy/serialization.rb +81 -0
  58. data/lib/toy/store.rb +36 -0
  59. data/lib/toy/timestamps.rb +22 -0
  60. data/lib/toy/validations.rb +45 -0
  61. data/lib/toy/version.rb +3 -0
  62. data/lib/toystore.rb +1 -0
  63. data/spec/helper.rb +35 -0
  64. data/spec/spec.opts +3 -0
  65. data/spec/support/constants.rb +41 -0
  66. data/spec/support/identity_map_matcher.rb +20 -0
  67. data/spec/support/name_and_number_key_factory.rb +5 -0
  68. data/spec/toy/attribute_spec.rb +176 -0
  69. data/spec/toy/attributes_spec.rb +394 -0
  70. data/spec/toy/caching_spec.rb +62 -0
  71. data/spec/toy/callbacks_spec.rb +97 -0
  72. data/spec/toy/connection_spec.rb +47 -0
  73. data/spec/toy/dirty_spec.rb +99 -0
  74. data/spec/toy/dolly_spec.rb +76 -0
  75. data/spec/toy/embedded_list_spec.rb +607 -0
  76. data/spec/toy/embedded_lists_spec.rb +172 -0
  77. data/spec/toy/equality_spec.rb +46 -0
  78. data/spec/toy/exceptions_spec.rb +18 -0
  79. data/spec/toy/extensions/array_spec.rb +25 -0
  80. data/spec/toy/extensions/boolean_spec.rb +41 -0
  81. data/spec/toy/extensions/date_spec.rb +48 -0
  82. data/spec/toy/extensions/float_spec.rb +14 -0
  83. data/spec/toy/extensions/hash_spec.rb +21 -0
  84. data/spec/toy/extensions/integer_spec.rb +29 -0
  85. data/spec/toy/extensions/nil_class_spec.rb +14 -0
  86. data/spec/toy/extensions/set_spec.rb +27 -0
  87. data/spec/toy/extensions/string_spec.rb +28 -0
  88. data/spec/toy/extensions/time_spec.rb +94 -0
  89. data/spec/toy/identity/abstract_key_factory_spec.rb +7 -0
  90. data/spec/toy/identity/uuid_key_factory_spec.rb +7 -0
  91. data/spec/toy/identity_map_spec.rb +150 -0
  92. data/spec/toy/identity_spec.rb +52 -0
  93. data/spec/toy/index_spec.rb +230 -0
  94. data/spec/toy/indices_spec.rb +141 -0
  95. data/spec/toy/inspect_spec.rb +15 -0
  96. data/spec/toy/list_spec.rb +576 -0
  97. data/spec/toy/lists_spec.rb +95 -0
  98. data/spec/toy/logger_spec.rb +33 -0
  99. data/spec/toy/mass_assignment_security_spec.rb +116 -0
  100. data/spec/toy/persistence_spec.rb +312 -0
  101. data/spec/toy/plugins_spec.rb +39 -0
  102. data/spec/toy/querying_spec.rb +162 -0
  103. data/spec/toy/reference_spec.rb +400 -0
  104. data/spec/toy/references_spec.rb +86 -0
  105. data/spec/toy/serialization_spec.rb +354 -0
  106. data/spec/toy/store_spec.rb +41 -0
  107. data/spec/toy/timestamps_spec.rb +63 -0
  108. data/spec/toy/validations_spec.rb +171 -0
  109. data/spec/toy_spec.rb +26 -0
  110. data/specs.watchr +52 -0
  111. data/test/lint_test.rb +40 -0
  112. data/toystore.gemspec +24 -0
  113. metadata +290 -0
@@ -0,0 +1,28 @@
1
+ require 'helper'
2
+
3
+ describe "String.to_store" do
4
+ it "should convert value to_s" do
5
+ [21, '21'].each do |value|
6
+ String.to_store(value).should == '21'
7
+ end
8
+ end
9
+
10
+ it "should be nil if nil" do
11
+ String.to_store(nil).should be_nil
12
+ end
13
+ end
14
+
15
+ describe "String.from_store" do
16
+ it "should be string if value present" do
17
+ String.from_store('Scotch! Scotch! Scotch!').should == 'Scotch! Scotch! Scotch!'
18
+ end
19
+
20
+ it "should return nil if nil" do
21
+ String.from_store(nil).should be_nil
22
+ end
23
+
24
+ it "should return empty string if blank" do
25
+ String.from_store('').should == ''
26
+ end
27
+ end
28
+
@@ -0,0 +1,94 @@
1
+ require 'helper'
2
+
3
+ describe "Time.to_store without Time.zone" do
4
+ before :each do
5
+ Time.zone = nil
6
+ end
7
+
8
+ it "should be time to milliseconds if string" do
9
+ Time.to_store('2000-01-01 01:01:01.123456').to_f.should == Time.local(2000, 1, 1, 1, 1, 1, 0).utc.to_f
10
+ end
11
+
12
+ it "should be time in utc if time" do
13
+ Time.to_store(Time.local(2009, 8, 15, 0, 0, 0)).zone.should == 'UTC'
14
+ end
15
+
16
+ it "should be nil if blank string" do
17
+ Time.to_store('').should be_nil
18
+ end
19
+
20
+ it "should not be nil if nil" do
21
+ Time.to_store(nil).should be_nil
22
+ end
23
+ end
24
+
25
+ describe "Time.to_store with Time.zone" do
26
+ it "should be time to milliseconds if time" do
27
+ Time.zone = 'Hawaii'
28
+ Time.to_store(Time.zone.local(2009, 8, 15, 14, 0, 0, 123456)).to_f.should == Time.utc(2009, 8, 16, 0, 0, 0, 0).to_f
29
+ Time.zone = nil
30
+ end
31
+
32
+ it "should be time to milliseconds if string" do
33
+ Time.zone = 'Hawaii'
34
+ Time.to_store('2009-08-15 14:00:00.123456').to_f.should == Time.utc(2009, 8, 16, 0, 0, 0, 0).to_f
35
+ Time.zone = nil
36
+ end
37
+
38
+ it "should not round up times at the end of the month" do
39
+ Time.to_store(Time.now.end_of_month).to_i.should == Time.now.end_of_month.utc.to_i
40
+ end
41
+
42
+ it "should be nil if blank string" do
43
+ Time.zone = 'Hawaii'
44
+ Time.to_store('').should be_nil
45
+ Time.zone = nil
46
+ end
47
+
48
+ it "should be nil if nil" do
49
+ Time.zone = 'Hawaii'
50
+ Time.to_store(nil).should be_nil
51
+ Time.zone = nil
52
+ end
53
+ end
54
+
55
+ describe "Time.from_store without Time.zone" do
56
+ it "should be time in utc" do
57
+ time = Time.now
58
+ Time.from_store(time).should be_within(1).of(time.utc)
59
+ end
60
+
61
+ it "should be time if string" do
62
+ time = Time.now
63
+ Time.from_store(time.to_s).should be_within(1).of(time)
64
+ end
65
+
66
+ it "should be nil if nil" do
67
+ Time.from_store(nil).should be_nil
68
+ end
69
+ end
70
+
71
+ describe "Time.from_store with Time.zone" do
72
+ before do
73
+ Time.zone = 'Hawaii'
74
+ end
75
+
76
+ after do
77
+ Time.zone = nil
78
+ end
79
+
80
+ it "should be time in Time.zone" do
81
+ time = Time.from_store(Time.utc(2009, 10, 1))
82
+ time.should == Time.zone.local(2009, 9, 30, 14)
83
+ time.is_a?(ActiveSupport::TimeWithZone).should be_true
84
+ end
85
+
86
+ it "should be time if string" do
87
+ time = Time.zone.now
88
+ Time.from_store(time.to_s).should be_within(1).of(time)
89
+ end
90
+
91
+ it "should be nil if nil" do
92
+ Time.from_store(nil).should be_nil
93
+ end
94
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ describe Toy::Identity::AbstractKeyFactory do
4
+ it "should raise not implemented error for #next_key" do
5
+ lambda { Toy::Identity::AbstractKeyFactory.new.next_key("any object") }.should raise_error(NotImplementedError)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ describe Toy::Identity::UUIDKeyFactory do
4
+ it "should use uuid for next key" do
5
+ Toy::Identity::UUIDKeyFactory.new.next_key(nil).length.should == 36
6
+ end
7
+ end
@@ -0,0 +1,150 @@
1
+ require 'helper'
2
+
3
+ describe Toy::IdentityMap do
4
+ uses_constants('User', 'Skill')
5
+
6
+ before do
7
+ Toy.identity_map.clear
8
+ end
9
+
10
+ describe ".identity_map" do
11
+ it "defaults to hash" do
12
+ User.identity_map.should == {}
13
+ end
14
+
15
+ it "memoizes" do
16
+ User.identity_map.should equal(User.identity_map)
17
+ end
18
+ end
19
+
20
+ it "adds to map on save" do
21
+ user = User.new
22
+ user.save!
23
+ user.should be_in_identity_map
24
+ end
25
+
26
+ it "adds to map on load" do
27
+ user = User.load({'id' => '1'})
28
+ user.should be_in_identity_map
29
+ end
30
+
31
+ it "removes from map on delete" do
32
+ user = User.create
33
+ user.should be_in_identity_map
34
+ user.delete
35
+ user.should_not be_in_identity_map
36
+ end
37
+
38
+ it "removes from map on destroy" do
39
+ user = User.create
40
+ user.should be_in_identity_map
41
+ user.destroy
42
+ user.should_not be_in_identity_map
43
+ end
44
+
45
+ describe ".get" do
46
+ it "adds to map if not in map" do
47
+ user = User.create
48
+ user.identity_map.clear
49
+ user.should_not be_in_identity_map
50
+ user = User.get(user.id)
51
+ user.should be_in_identity_map
52
+ end
53
+
54
+ it "returns from map if in map" do
55
+ user = User.create
56
+ user.should be_in_identity_map
57
+ User.get(user.id).should equal(user)
58
+ end
59
+
60
+ it "does not query if in map" do
61
+ user = User.create
62
+ user.should be_in_identity_map
63
+ user.store.should_not_receive(:read)
64
+ User.get(user.id).should equal(user)
65
+ end
66
+ end
67
+
68
+ describe "#reload" do
69
+ it "forces new query each time and skips the identity map" do
70
+ user = User.create
71
+ user.should be_in_identity_map
72
+ User.store.should_receive(:read).with(user.store_key).and_return({})
73
+ user.reload
74
+ end
75
+ end
76
+
77
+ describe "identity map off" do
78
+ it "does not add to map on save" do
79
+ User.identity_map_off
80
+ user = User.new
81
+ user.save!
82
+ user.should_not be_in_identity_map
83
+ end
84
+
85
+ it "does not add to map on load" do
86
+ User.identity_map_off
87
+ user = User.load('id' => '1')
88
+ user.should_not be_in_identity_map
89
+ end
90
+
91
+ it "does not remove from map on delete" do
92
+ user = User.create
93
+ user.should be_in_identity_map
94
+ User.identity_map_off
95
+ user.delete
96
+ user.should be_in_identity_map
97
+ end
98
+
99
+ it "does not remove from map on destroy" do
100
+ user = User.create
101
+ user.should be_in_identity_map
102
+ User.identity_map_off
103
+ user.destroy
104
+ user.should be_in_identity_map
105
+ end
106
+
107
+ describe ".get" do
108
+ it "does not add to map if not in map" do
109
+ User.identity_map_off
110
+ user = User.create
111
+ user.should_not be_in_identity_map
112
+ user = User.get(user.id)
113
+ user.should_not be_in_identity_map
114
+ end
115
+
116
+ it "does not load from map if in map" do
117
+ user = User.create
118
+ user.should be_in_identity_map
119
+ User.identity_map_off
120
+ user.store.should_receive(:read).with(user.store_key).and_return(user.persisted_attributes)
121
+ User.get(user.id)
122
+ end
123
+ end
124
+ end
125
+
126
+ describe ".without_identity_map" do
127
+ describe "with identity map off" do
128
+ it "turns identity map off, yields, and returns it to previous state" do
129
+ User.identity_map_off
130
+ User.should be_identity_map_off
131
+ User.without_identity_map do
132
+ user = User.create
133
+ user.should_not be_in_identity_map
134
+ end
135
+ User.should be_identity_map_off
136
+ end
137
+ end
138
+
139
+ describe "with identity map on" do
140
+ it "turns identity map off, yields, and returns it to previous state" do
141
+ User.should be_identity_map_on
142
+ User.without_identity_map do
143
+ user = User.create
144
+ user.should_not be_in_identity_map
145
+ end
146
+ User.should be_identity_map_on
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,52 @@
1
+ require 'helper'
2
+
3
+ describe Toy::Identity do
4
+ uses_constants('User', 'Piece')
5
+
6
+ describe "setting the key" do
7
+ it "should set key factory to UUIDKeyFactory" do
8
+ User.key(:uuid).should be_instance_of(Toy::Identity::UUIDKeyFactory)
9
+ end
10
+
11
+ it "should set key factory passed in factory" do
12
+ factory = Toy::Identity::UUIDKeyFactory
13
+ User.key(factory).should == factory
14
+ end
15
+
16
+ it "should use Toy.key_factory by default" do
17
+ key_factory = mock
18
+ Toy.key_factory = key_factory
19
+ klass = Class.new { include Toy::Store }
20
+
21
+ key_factory.should_receive(:next_key).and_return('some_key')
22
+ klass.next_key
23
+
24
+ Toy.key_factory = nil
25
+ end
26
+ end
27
+
28
+ describe ".next_key" do
29
+ it "should call the next key on the key factory" do
30
+ factory = Toy::Identity::UUIDKeyFactory
31
+ factory.should_receive(:next_key).and_return('some_key')
32
+ User.key(factory)
33
+ User.next_key.should == 'some_key'
34
+ end
35
+
36
+ it "should raise an exception for nil key" do
37
+ factory = Toy::Identity::UUIDKeyFactory
38
+ factory.should_receive(:next_key).and_return(nil)
39
+ User.key(factory)
40
+ lambda { User.next_key }.should raise_error
41
+ end
42
+ end
43
+
44
+ describe "initializing the id" do
45
+ it "should pass use pass the new object" do
46
+ Piece.attribute(:name, String)
47
+ Piece.attribute(:number, Integer)
48
+ Piece.key(NameAndNumberKeyFactory.new)
49
+ Piece.new(:name => 'Rook', :number => 1).id.should == 'Rook-1'
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,230 @@
1
+ require 'helper'
2
+
3
+ describe Toy::Index do
4
+ uses_constants('User', 'Student', 'Friendship')
5
+
6
+ before do
7
+ User.attribute(:ssn, String)
8
+ @index = User.index(:ssn)
9
+ end
10
+ let(:index) { @index }
11
+
12
+ it "has a model" do
13
+ index.model.should == User
14
+ end
15
+
16
+ it "has a name" do
17
+ index.name.should == :ssn
18
+ end
19
+
20
+ it "raises error if attribute does not exist" do
21
+ lambda {
22
+ User.index :student_id
23
+ }.should raise_error(ArgumentError, "No attribute student_id for index")
24
+ end
25
+
26
+ it "has a sha1'd key for a value" do
27
+ index.key('some_value').should == 'User:ssn:8c818171573b03feeae08b0b4ffeb6999e3afc05'
28
+ end
29
+
30
+ it "adds index to model" do
31
+ User.indices.keys.should include(:ssn)
32
+ end
33
+
34
+ describe "#eql?" do
35
+ it "returns true if same class, model, and name" do
36
+ index.should eql(index)
37
+ end
38
+
39
+ it "returns false if not same class" do
40
+ index.should_not eql({})
41
+ end
42
+
43
+ it "returns false if not same model" do
44
+ Student.attribute :ssn, String
45
+ index.should_not eql(Toy::Index.new(Student, :ssn))
46
+ end
47
+
48
+ it "returns false if not the same name" do
49
+ User.attribute :student_id, String
50
+ index.should_not eql(Toy::Index.new(User, :student_id))
51
+ end
52
+ end
53
+
54
+ describe "single key" do
55
+ describe "creating with index" do
56
+ before do
57
+ @user = User.create(:ssn => '555-00-1234')
58
+ end
59
+ let(:user) { @user }
60
+
61
+ it "creates key for indexed value" do
62
+ User.store.should be_key("User:ssn:f6edc9155d79e311ad2d4a6e1b54004f31497f4c")
63
+ end
64
+
65
+ it "adds instance id to index array" do
66
+ User.get_index(:ssn, '555-00-1234').should == [user.id]
67
+ end
68
+ end
69
+
70
+ describe "creating second record for same index value" do
71
+ before do
72
+ @user1 = User.create(:ssn => '555-00-1234')
73
+ @user2 = User.create(:ssn => '555-00-1234')
74
+ end
75
+
76
+ it "adds both instances to index" do
77
+ User.get_index(:ssn, '555-00-1234').should == [@user1.id, @user2.id]
78
+ end
79
+ end
80
+
81
+ describe "destroying with index" do
82
+ before do
83
+ @user = User.create(:ssn => '555-00-1234')
84
+ @user.destroy
85
+ end
86
+
87
+ it "removes id from index" do
88
+ User.get_index(:ssn, '555-00-1234').should == []
89
+ end
90
+ end
91
+
92
+ describe "updating record and changing indexed value" do
93
+ before do
94
+ @user = User.create(:ssn => '555-00-1234')
95
+ @user.update_attributes(:ssn => '555-00-4321')
96
+ end
97
+
98
+ it "removes from old index" do
99
+ User.get_index(:ssn, '555-00-1234').should == []
100
+ end
101
+
102
+ it "adds to new index" do
103
+ User.get_index(:ssn, '555-00-4321').should == [@user.id]
104
+ end
105
+ end
106
+
107
+ describe "updating record without changing indexed value" do
108
+ before do
109
+ @user = User.create(:ssn => '555-00-1234')
110
+ @user.update_attributes(:ssn => '555-00-1234')
111
+ end
112
+
113
+ it "leaves index alone" do
114
+ User.get_index(:ssn, '555-00-1234').should == [@user.id]
115
+ end
116
+ end
117
+
118
+ describe "first by index" do
119
+ it "should not find values that are not in index" do
120
+ User.first_by_ssn('does-not-exist').should be_nil
121
+ end
122
+
123
+ it "should find indexed value" do
124
+ user = User.create(:ssn => '555-00-1234')
125
+ User.first_by_ssn('555-00-1234').should == user
126
+ end
127
+ end
128
+
129
+ describe "first or new by index" do
130
+ it "initializes if not existing" do
131
+ user = User.first_or_new_by_ssn('does-not-exist')
132
+ user.ssn.should == 'does-not-exist'
133
+ user.should_not be_persisted
134
+ end
135
+
136
+ it "returns if existing" do
137
+ user = User.create(:ssn => '555-00-1234')
138
+ User.first_or_new_by_ssn('555-00-1234').should == user
139
+ end
140
+ end
141
+
142
+ describe "first or create by index" do
143
+ it "creates if not existing" do
144
+ user = User.first_or_create_by_ssn('does-not-exist')
145
+ user.ssn.should == 'does-not-exist'
146
+ user.should be_persisted
147
+ end
148
+
149
+ it "returns if existing" do
150
+ user = User.create(:ssn => '555-00-1234')
151
+ User.first_or_create_by_ssn('555-00-1234').should == user
152
+ end
153
+ end
154
+ end
155
+
156
+ describe "array key" do
157
+ before do
158
+ User.list :friendships
159
+ Friendship.list :users
160
+ Friendship.index :user_ids
161
+ @user1 = User.create(:ssn => '555-00-1234')
162
+ @user2 = User.create(:ssn => '555-00-9876')
163
+ @friendship = Friendship.create(:user_ids => [@user1.id, @user2.id])
164
+ end
165
+ let(:user1) { @user1 }
166
+ let(:user2) { @user2 }
167
+ let(:friendship) { @friendship }
168
+
169
+ describe "creating with index" do
170
+ it "creates key for indexed values sorted" do
171
+ sha_value = Digest::SHA1.hexdigest([user1.id, user2.id].sort.join(''))
172
+ Friendship.store.should be_key("Friendship:user_ids:#{sha_value}")
173
+ end
174
+
175
+ it "adds instance id to index array" do
176
+ Friendship.get_index(:user_ids, [user1.id, user2.id]).should == [friendship.id]
177
+ Friendship.get_index(:user_ids, [user2.id, user1.id]).should == [friendship.id]
178
+ end
179
+ end
180
+
181
+ describe "destroying with index" do
182
+ before do
183
+ friendship.destroy
184
+ end
185
+
186
+ it "removes id from index" do
187
+ Friendship.get_index(:user_ids, [user2.id, user1.id]).should == []
188
+ end
189
+ end
190
+
191
+ describe "first by index" do
192
+ it "should not find values that are not in index" do
193
+ Friendship.first_by_user_ids([user1.id, 'does-not-exist']).should be_nil
194
+ end
195
+
196
+ it "should find indexed value" do
197
+ Friendship.first_by_user_ids([user1.id, user2.id]).should == friendship
198
+ Friendship.first_by_user_ids([user2.id, user1.id]).should == friendship
199
+ end
200
+ end
201
+
202
+ describe "first or new by index" do
203
+ it "initializes if not existing" do
204
+ new_friend = User.create(:ssn => '555-00-1928')
205
+ new_friendship = Friendship.first_or_new_by_user_ids([user1.id, new_friend.id])
206
+ new_friendship.user_ids.sort.should == [user1.id, new_friend.id].sort
207
+ new_friendship.should_not be_persisted
208
+ end
209
+
210
+ it "returns if existing" do
211
+ Friendship.first_or_new_by_user_ids([user1.id, user2.id]).should == friendship
212
+ Friendship.first_or_new_by_user_ids([user2.id, user1.id]).should == friendship
213
+ end
214
+ end
215
+
216
+ describe "first or create by index" do
217
+ it "creates if not existing" do
218
+ new_friend = User.create(:ssn => '555-00-1928')
219
+ new_friendship = Friendship.first_or_create_by_user_ids([user1.id, new_friend.id])
220
+ new_friendship.user_ids.sort.should == [user1.id, new_friend.id].sort
221
+ new_friendship.should be_persisted
222
+ end
223
+
224
+ it "returns if existing" do
225
+ Friendship.first_or_create_by_user_ids([user1.id, user2.id]).should == friendship
226
+ Friendship.first_or_create_by_user_ids([user2.id, user1.id]).should == friendship
227
+ end
228
+ end
229
+ end
230
+ end