mongo_mapper 0.5.6 → 0.5.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -1
- data/README.rdoc +3 -0
- data/VERSION +1 -1
- data/lib/mongo_mapper.rb +14 -6
- data/lib/mongo_mapper/associations.rb +11 -5
- data/lib/mongo_mapper/associations/base.rb +17 -5
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +0 -2
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +15 -15
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +2 -2
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +1 -1
- data/lib/mongo_mapper/associations/proxy.rb +1 -0
- data/lib/mongo_mapper/callbacks.rb +18 -0
- data/lib/mongo_mapper/document.rb +206 -89
- data/lib/mongo_mapper/dynamic_finder.rb +1 -1
- data/lib/mongo_mapper/embedded_document.rb +7 -3
- data/lib/mongo_mapper/finder_options.rb +87 -66
- data/lib/mongo_mapper/pagination.rb +2 -0
- data/lib/mongo_mapper/serialization.rb +2 -3
- data/lib/mongo_mapper/serializers/json_serializer.rb +1 -1
- data/lib/mongo_mapper/support.rb +9 -0
- data/lib/mongo_mapper/validations.rb +3 -1
- data/mongo_mapper.gemspec +4 -4
- data/test/functional/associations/test_many_documents_as_proxy.rb +2 -2
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +25 -1
- data/test/functional/associations/test_many_embedded_proxy.rb +25 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +48 -6
- data/test/functional/associations/test_many_proxy.rb +27 -6
- data/test/functional/test_document.rb +49 -29
- data/test/functional/test_pagination.rb +17 -17
- data/test/functional/test_validations.rb +35 -14
- data/test/models.rb +85 -10
- data/test/support/{test_timing.rb → timing.rb} +1 -1
- data/test/test_helper.rb +8 -8
- data/test/unit/test_association_base.rb +17 -0
- data/test/unit/test_document.rb +12 -1
- data/test/unit/test_embedded_document.rb +13 -4
- data/test/unit/test_finder_options.rb +50 -48
- data/test/unit/test_pagination.rb +4 -0
- metadata +4 -4
@@ -332,9 +332,13 @@ module MongoMapper
|
|
332
332
|
end
|
333
333
|
|
334
334
|
def read_attribute(name)
|
335
|
-
|
336
|
-
|
337
|
-
|
335
|
+
if key = _keys[name]
|
336
|
+
value = key.get(instance_variable_get("@#{name}"))
|
337
|
+
instance_variable_set "@#{name}", value if !frozen?
|
338
|
+
value
|
339
|
+
else
|
340
|
+
raise KeyNotFound, "Could not find key: #{name.inspect}"
|
341
|
+
end
|
338
342
|
end
|
339
343
|
|
340
344
|
def read_attribute_before_typecast(name)
|
@@ -1,66 +1,35 @@
|
|
1
1
|
module MongoMapper
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
{'$in' => value}
|
16
|
-
end
|
17
|
-
when Hash
|
18
|
-
criteria[field] = to_mongo_criteria(model, value, field)
|
19
|
-
else
|
20
|
-
criteria[field] = value
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
criteria
|
2
|
+
# Controls the parsing and handling of options used by finders.
|
3
|
+
#
|
4
|
+
# == Important Note
|
5
|
+
#
|
6
|
+
# This class is private to MongoMapper and should not be considered part of
|
7
|
+
# MongoMapper's public API. Some documentation herein, however, may prove
|
8
|
+
# useful for understanding how MongoMapper handles the parsing of finder
|
9
|
+
# conditions and options.
|
10
|
+
#
|
11
|
+
# @private
|
12
|
+
class FinderOperator
|
13
|
+
def initialize(field, operator)
|
14
|
+
@field, @operator = field, operator
|
25
15
|
end
|
26
16
|
|
27
|
-
|
28
|
-
|
29
|
-
if model.single_collection_inherited?
|
30
|
-
criteria[:_type] = model.to_s
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.to_mongo_options(model, options)
|
35
|
-
options = options.dup
|
36
|
-
{
|
37
|
-
:fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
|
38
|
-
:skip => (options.delete(:skip) || options.delete(:offset) || 0).to_i,
|
39
|
-
:limit => (options.delete(:limit) || 0).to_i,
|
40
|
-
:sort => options.delete(:sort) || to_mongo_sort(options.delete(:order))
|
41
|
-
}
|
17
|
+
def to_criteria(value)
|
18
|
+
{@field => {@operator => value}}
|
42
19
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
:_id
|
47
|
-
else
|
48
|
-
field
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
20
|
+
end
|
21
|
+
|
22
|
+
class FinderOptions
|
52
23
|
OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
|
53
|
-
|
54
|
-
attr_reader :model, :options
|
55
|
-
|
24
|
+
|
56
25
|
def initialize(model, options)
|
57
|
-
raise ArgumentError, "
|
58
|
-
|
59
|
-
@model = model
|
60
|
-
|
61
|
-
options = options.symbolize_keys
|
62
|
-
@options, @conditions = {}, options.delete(:conditions) || {}
|
26
|
+
raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
|
27
|
+
options.symbolize_keys!
|
63
28
|
|
29
|
+
@model = model
|
30
|
+
@options = {}
|
31
|
+
@conditions = options.delete(:conditions) || {}
|
32
|
+
|
64
33
|
options.each_pair do |key, value|
|
65
34
|
if OptionKeys.include?(key)
|
66
35
|
@options[key] = value
|
@@ -68,42 +37,94 @@ module MongoMapper
|
|
68
37
|
@conditions[key] = value
|
69
38
|
end
|
70
39
|
end
|
40
|
+
|
41
|
+
add_sci_scope
|
71
42
|
end
|
72
43
|
|
44
|
+
# @return [Hash] Mongo compatible criteria options
|
45
|
+
#
|
46
|
+
# @see FinderOptions#to_mongo_criteria
|
73
47
|
def criteria
|
74
|
-
|
48
|
+
to_mongo_criteria(@conditions)
|
75
49
|
end
|
76
50
|
|
51
|
+
# @return [Hash] Mongo compatible options
|
77
52
|
def options
|
78
|
-
|
53
|
+
options = @options.dup
|
54
|
+
|
55
|
+
fields = options.delete(:fields) || options.delete(:select)
|
56
|
+
skip = options.delete(:skip) || options.delete(:offset) || 0
|
57
|
+
limit = options.delete(:limit) || 0
|
58
|
+
sort = options.delete(:sort) || convert_order_to_sort(options.delete(:order))
|
59
|
+
|
60
|
+
{:fields => to_mongo_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
|
79
61
|
end
|
80
62
|
|
63
|
+
# @return [Array<Hash>] Mongo criteria and options enclosed in an Array
|
81
64
|
def to_a
|
82
65
|
[criteria, options]
|
83
66
|
end
|
84
|
-
|
67
|
+
|
85
68
|
private
|
86
|
-
def
|
69
|
+
def to_mongo_criteria(conditions, parent_key=nil)
|
70
|
+
criteria = {}
|
71
|
+
|
72
|
+
conditions.each_pair do |field, value|
|
73
|
+
field = normalized_field(field)
|
74
|
+
if field.is_a?(FinderOperator)
|
75
|
+
criteria.merge!(field.to_criteria(value))
|
76
|
+
next
|
77
|
+
end
|
78
|
+
case value
|
79
|
+
when Array
|
80
|
+
operator_present = field.to_s =~ /^\$/
|
81
|
+
criteria[field] = operator?(field) ? value : {'$in' => value}
|
82
|
+
when Hash
|
83
|
+
criteria[field] = to_mongo_criteria(value, field)
|
84
|
+
else
|
85
|
+
criteria[field] = value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
criteria
|
90
|
+
end
|
91
|
+
|
92
|
+
def operator?(field)
|
93
|
+
field.to_s =~ /^\$/
|
94
|
+
end
|
95
|
+
|
96
|
+
def normalized_field(field)
|
97
|
+
field.to_s == 'id' ? :_id : field
|
98
|
+
end
|
99
|
+
|
100
|
+
# adds _type single collection inheritance scope for models that need it
|
101
|
+
def add_sci_scope
|
102
|
+
if @model.single_collection_inherited?
|
103
|
+
@conditions[:_type] = @model.to_s
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_mongo_fields(fields)
|
87
108
|
return if fields.blank?
|
88
|
-
|
109
|
+
|
89
110
|
if fields.is_a?(String)
|
90
111
|
fields.split(',').map { |field| field.strip }
|
91
112
|
else
|
92
113
|
fields.flatten.compact
|
93
114
|
end
|
94
115
|
end
|
95
|
-
|
96
|
-
def
|
116
|
+
|
117
|
+
def convert_order_to_sort(sort)
|
97
118
|
return if sort.blank?
|
98
119
|
pieces = sort.split(',')
|
99
120
|
pieces.map { |s| to_mongo_sort_piece(s) }
|
100
121
|
end
|
101
|
-
|
102
|
-
def
|
122
|
+
|
123
|
+
def to_mongo_sort_piece(str)
|
103
124
|
field, direction = str.strip.split(' ')
|
104
125
|
direction ||= 'ASC'
|
105
126
|
direction = direction.upcase == 'ASC' ? 1 : -1
|
106
127
|
[field, direction]
|
107
128
|
end
|
108
129
|
end
|
109
|
-
end
|
130
|
+
end
|
@@ -5,7 +5,7 @@ module MongoMapper #:nodoc:
|
|
5
5
|
class Serializer #:nodoc:
|
6
6
|
attr_reader :options
|
7
7
|
|
8
|
-
def initialize(record, options
|
8
|
+
def initialize(record, options={})
|
9
9
|
@record, @options = record, options.dup
|
10
10
|
end
|
11
11
|
|
@@ -51,5 +51,4 @@ module MongoMapper #:nodoc:
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
|
55
|
-
require dir + 'json_serializer'
|
54
|
+
require 'mongo_mapper/serializers/json_serializer'
|
@@ -49,7 +49,7 @@ module MongoMapper #:nodoc:
|
|
49
49
|
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
50
50
|
# "created_at": "2006/08/01", "awesome": true,
|
51
51
|
# "permalink": "1-konata-izumi"}
|
52
|
-
def to_json(options
|
52
|
+
def to_json(options={})
|
53
53
|
apply_to_json_defaults(options)
|
54
54
|
|
55
55
|
if include_root_in_json
|
data/lib/mongo_mapper/support.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
class BasicObject #:nodoc:
|
2
|
+
alias_method :proxy_extend, :extend
|
2
3
|
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|^methods$|instance_eval|proxy_|^object_id$)/ }
|
3
4
|
end unless defined?(BasicObject)
|
4
5
|
|
@@ -129,6 +130,14 @@ class String
|
|
129
130
|
end
|
130
131
|
end
|
131
132
|
|
133
|
+
class Symbol
|
134
|
+
%w{gt lt gte lte ne in nin mod size where exists}.each do |operator|
|
135
|
+
define_method operator do
|
136
|
+
MongoMapper::FinderOperator.new(self, "$#{operator}")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
132
141
|
class Time
|
133
142
|
def self.to_mongo(value)
|
134
143
|
if value.nil? || value == ''
|
@@ -18,7 +18,9 @@ module MongoMapper
|
|
18
18
|
option :scope
|
19
19
|
|
20
20
|
def valid?(instance)
|
21
|
-
|
21
|
+
value = instance[attribute]
|
22
|
+
return true if allow_blank && value.blank?
|
23
|
+
doc = instance.class.first({self.attribute => value}.merge(scope_conditions(instance)))
|
22
24
|
doc.nil? || instance.id == doc.id
|
23
25
|
end
|
24
26
|
|
data/mongo_mapper.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{mongo_mapper}
|
8
|
-
s.version = "0.5.
|
8
|
+
s.version = "0.5.7"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["John Nunemaker"]
|
12
|
-
s.date = %q{2009-10-
|
12
|
+
s.date = %q{2009-10-28}
|
13
13
|
s.default_executable = %q{mmconsole}
|
14
14
|
s.description = %q{Awesome gem for modeling your domain and storing it in mongo}
|
15
15
|
s.email = %q{nunemaker@gmail.com}
|
@@ -74,7 +74,7 @@ Gem::Specification.new do |s|
|
|
74
74
|
"test/functional/test_validations.rb",
|
75
75
|
"test/models.rb",
|
76
76
|
"test/support/custom_matchers.rb",
|
77
|
-
"test/support/
|
77
|
+
"test/support/timing.rb",
|
78
78
|
"test/test_helper.rb",
|
79
79
|
"test/unit/serializers/test_json_serializer.rb",
|
80
80
|
"test/unit/test_association_base.rb",
|
@@ -117,7 +117,7 @@ Gem::Specification.new do |s|
|
|
117
117
|
"test/functional/test_validations.rb",
|
118
118
|
"test/models.rb",
|
119
119
|
"test/support/custom_matchers.rb",
|
120
|
-
"test/support/
|
120
|
+
"test/support/timing.rb",
|
121
121
|
"test/test_helper.rb",
|
122
122
|
"test/unit/serializers/test_json_serializer.rb",
|
123
123
|
"test/unit/test_association_base.rb",
|
@@ -137,7 +137,7 @@ class ManyDocumentsAsProxyTest < Test::Unit::TestCase
|
|
137
137
|
end
|
138
138
|
|
139
139
|
should "work with conditions" do
|
140
|
-
comments = @post.comments.find(:all, :
|
140
|
+
comments = @post.comments.find(:all, :body => 'comment1')
|
141
141
|
comments.should == [@comment1]
|
142
142
|
end
|
143
143
|
|
@@ -154,7 +154,7 @@ class ManyDocumentsAsProxyTest < Test::Unit::TestCase
|
|
154
154
|
end
|
155
155
|
|
156
156
|
should "work with conditions" do
|
157
|
-
comments = @post.comments.all(:
|
157
|
+
comments = @post.comments.all(:body => 'comment1')
|
158
158
|
comments.should == [@comment1]
|
159
159
|
end
|
160
160
|
|
@@ -129,4 +129,28 @@ class ManyEmbeddedPolymorphicProxyTest < Test::Unit::TestCase
|
|
129
129
|
from_db.transports[2].icu.should == true
|
130
130
|
end
|
131
131
|
end
|
132
|
-
|
132
|
+
|
133
|
+
context "extending the association" do
|
134
|
+
should "work using a block passed to many" do
|
135
|
+
catalog = Catalog.new
|
136
|
+
medias = catalog.medias = [
|
137
|
+
Video.new("file" => "video.mpg", "length" => 3600, :visible => true),
|
138
|
+
Music.new("file" => "music.mp3", "bitrate" => "128kbps", :visible => true),
|
139
|
+
Image.new("file" => "image.png", "width" => 800, "height" => 600, :visible => false)
|
140
|
+
]
|
141
|
+
catalog.save
|
142
|
+
catalog.medias.visible.should == [medias[0], medias[1]]
|
143
|
+
end
|
144
|
+
|
145
|
+
should "work using many's :extend option" do
|
146
|
+
fleet = TrModels::Fleet.new
|
147
|
+
transports = fleet.transports = [
|
148
|
+
TrModels::Car.new("license_plate" => "ABC1223", "model" => "Honda Civic", "year" => 2003, :purchased_on => 2.years.ago.to_date),
|
149
|
+
TrModels::Bus.new("license_plate" => "XYZ9090", "max_passengers" => 51, :purchased_on => 3.years.ago.to_date),
|
150
|
+
TrModels::Ambulance.new("license_plate" => "HDD3030", "icu" => true, :purchased_on => 1.year.ago.to_date)
|
151
|
+
]
|
152
|
+
fleet.save
|
153
|
+
fleet.transports.to_be_replaced.should == [transports[1]]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -171,4 +171,29 @@ class ManyEmbeddedProxyTest < Test::Unit::TestCase
|
|
171
171
|
|
172
172
|
meg.pets.find(sparky.id).should == sparky
|
173
173
|
end
|
174
|
+
|
175
|
+
context "extending the association" do
|
176
|
+
should "work using a block passed to many" do
|
177
|
+
project = Project.new(:name => "Some Project")
|
178
|
+
addr1 = Address.new(:address => "Gate-3 Lankershim Blvd.", :city => "Universal City", :state => "CA", :zip => "91608")
|
179
|
+
addr2 = Address.new(:address => "3000 W. Alameda Ave.", :city => "Burbank", :state => "CA", :zip => "91523")
|
180
|
+
addr3 = Address.new(:address => "111 Some Ln", :city => "Nashville", :state => "TN", :zip => "37211")
|
181
|
+
project.addresses = [addr1, addr2, addr3]
|
182
|
+
project.save
|
183
|
+
project.addresses.find_all_by_state("CA").should == [addr1, addr2]
|
184
|
+
end
|
185
|
+
|
186
|
+
should "work using many's :extend option" do
|
187
|
+
project = Project.new(:name => "Some Project")
|
188
|
+
person1 = Person.new(:name => "Steve")
|
189
|
+
person2 = Person.new(:name => "Betty")
|
190
|
+
person3 = Person.new(:name => "Cynthia")
|
191
|
+
|
192
|
+
project.people << person1
|
193
|
+
project.people << person2
|
194
|
+
project.people << person3
|
195
|
+
project.save
|
196
|
+
project.people.find_by_name("Steve").should == person1
|
197
|
+
end
|
198
|
+
end
|
174
199
|
end
|
@@ -4,6 +4,7 @@ require 'models'
|
|
4
4
|
class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
5
5
|
def setup
|
6
6
|
Room.collection.clear
|
7
|
+
Message.collection.clear
|
7
8
|
end
|
8
9
|
|
9
10
|
should "default reader to empty array" do
|
@@ -181,7 +182,7 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
|
181
182
|
end
|
182
183
|
|
183
184
|
should "work with conditions" do
|
184
|
-
messages = @lounge.messages.find(:all, :
|
185
|
+
messages = @lounge.messages.find(:all, :body => 'Loungin!', :order => "position")
|
185
186
|
messages.should == [@lm1]
|
186
187
|
end
|
187
188
|
|
@@ -197,7 +198,7 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
|
197
198
|
end
|
198
199
|
|
199
200
|
should "work with conditions" do
|
200
|
-
messages = @lounge.messages.all(:
|
201
|
+
messages = @lounge.messages.all(:body => 'Loungin!', :order => "position")
|
201
202
|
messages.should == [@lm1]
|
202
203
|
end
|
203
204
|
|
@@ -213,7 +214,7 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
|
213
214
|
end
|
214
215
|
|
215
216
|
should "work with conditions" do
|
216
|
-
message = @lounge.messages.find(:first, :
|
217
|
+
message = @lounge.messages.find(:first, :body => 'I love loungin!', :order => "position asc")
|
217
218
|
message.should == @lm2
|
218
219
|
end
|
219
220
|
end
|
@@ -224,7 +225,7 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
|
224
225
|
end
|
225
226
|
|
226
227
|
should "work with conditions" do
|
227
|
-
message = @lounge.messages.first(:
|
228
|
+
message = @lounge.messages.first(:body => 'I love loungin!', :order => "position asc")
|
228
229
|
message.should == @lm2
|
229
230
|
end
|
230
231
|
end
|
@@ -235,7 +236,7 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
|
235
236
|
end
|
236
237
|
|
237
238
|
should "work with conditions" do
|
238
|
-
message = @lounge.messages.find(:last, :
|
239
|
+
message = @lounge.messages.find(:last, :body => 'Loungin!', :order => "position asc")
|
239
240
|
message.should == @lm1
|
240
241
|
end
|
241
242
|
end
|
@@ -246,7 +247,7 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
|
246
247
|
end
|
247
248
|
|
248
249
|
should "work with conditions" do
|
249
|
-
message = @lounge.messages.last(:
|
250
|
+
message = @lounge.messages.last(:body => 'Loungin!', :order => "position asc")
|
250
251
|
message.should == @lm1
|
251
252
|
end
|
252
253
|
end
|
@@ -263,6 +264,20 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
|
263
264
|
end
|
264
265
|
end
|
265
266
|
|
267
|
+
context "with query options/criteria" do
|
268
|
+
should "work with order on association" do
|
269
|
+
@lounge.messages.should == [@lm1, @lm2]
|
270
|
+
end
|
271
|
+
|
272
|
+
should "allow overriding the order provided to the association" do
|
273
|
+
@lounge.messages.all(:order => 'position desc').should == [@lm2, @lm1]
|
274
|
+
end
|
275
|
+
|
276
|
+
should "allow using conditions on association" do
|
277
|
+
@hall.latest_messages.should == [@hm3, @hm2]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
266
281
|
context "with multiple ids" do
|
267
282
|
should "work for ids in association" do
|
268
283
|
messages = @lounge.messages.find(@lm1.id, @lm2.id)
|
@@ -294,4 +309,31 @@ class ManyPolymorphicProxyTest < Test::Unit::TestCase
|
|
294
309
|
end
|
295
310
|
end
|
296
311
|
end
|
312
|
+
|
313
|
+
context "extending the association" do
|
314
|
+
should "work using a block passed to many" do
|
315
|
+
room = Room.new(:name => "Amazing Room")
|
316
|
+
messages = room.messages = [
|
317
|
+
Enter.new(:body => 'John entered room', :position => 3),
|
318
|
+
Chat.new(:body => 'Heyyyoooo!', :position => 4),
|
319
|
+
Exit.new(:body => 'John exited room', :position => 5),
|
320
|
+
Enter.new(:body => 'Steve entered room', :position => 6),
|
321
|
+
Chat.new(:body => 'Anyone there?', :position => 7),
|
322
|
+
Exit.new(:body => 'Steve exited room', :position => 8)
|
323
|
+
]
|
324
|
+
room.save
|
325
|
+
room.messages.older.should == messages[3..5]
|
326
|
+
end
|
327
|
+
|
328
|
+
should "work using many's :extend option" do
|
329
|
+
room = Room.new(:name => "Amazing Room")
|
330
|
+
accounts = room.accounts = [
|
331
|
+
Bot.new(:last_logged_in => 3.weeks.ago),
|
332
|
+
User.new(:last_logged_in => nil),
|
333
|
+
Bot.new(:last_logged_in => 1.week.ago)
|
334
|
+
]
|
335
|
+
room.save
|
336
|
+
room.accounts.inactive.should == [accounts[1]]
|
337
|
+
end
|
338
|
+
end
|
297
339
|
end
|