ampere 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/CHANGELOG.md +15 -0
  2. data/Gemfile +2 -2
  3. data/Gemfile.lock +36 -26
  4. data/README.md +25 -11
  5. data/Rakefile +10 -0
  6. data/VERSION +1 -1
  7. data/ampere.gemspec +83 -9
  8. data/example/.gitignore +5 -0
  9. data/example/.rspec +1 -0
  10. data/example/Gemfile +21 -0
  11. data/example/Gemfile.lock +143 -0
  12. data/example/README +261 -0
  13. data/example/Rakefile +7 -0
  14. data/example/app/assets/images/rails.png +0 -0
  15. data/example/app/assets/javascripts/application.js +9 -0
  16. data/example/app/assets/javascripts/posts.js.coffee +3 -0
  17. data/example/app/assets/stylesheets/application.css +7 -0
  18. data/example/app/assets/stylesheets/posts.css.scss +3 -0
  19. data/example/app/assets/stylesheets/scaffolds.css.scss +56 -0
  20. data/example/app/controllers/application_controller.rb +3 -0
  21. data/example/app/controllers/posts_controller.rb +83 -0
  22. data/example/app/helpers/application_helper.rb +2 -0
  23. data/example/app/helpers/posts_helper.rb +2 -0
  24. data/example/app/mailers/.gitkeep +0 -0
  25. data/example/app/models/.gitkeep +0 -0
  26. data/example/app/models/post.rb +7 -0
  27. data/example/app/views/layouts/application.html.erb +14 -0
  28. data/example/app/views/posts/_form.html.erb +25 -0
  29. data/example/app/views/posts/edit.html.erb +6 -0
  30. data/example/app/views/posts/index.html.erb +25 -0
  31. data/example/app/views/posts/new.html.erb +5 -0
  32. data/example/app/views/posts/show.html.erb +15 -0
  33. data/example/config/ampere.yml +11 -0
  34. data/example/config/application.rb +54 -0
  35. data/example/config/boot.rb +6 -0
  36. data/example/config/environment.rb +5 -0
  37. data/example/config/environments/development.rb +30 -0
  38. data/example/config/environments/production.rb +60 -0
  39. data/example/config/environments/test.rb +39 -0
  40. data/example/config/initializers/backtrace_silencers.rb +7 -0
  41. data/example/config/initializers/inflections.rb +10 -0
  42. data/example/config/initializers/mime_types.rb +5 -0
  43. data/example/config/initializers/secret_token.rb +7 -0
  44. data/example/config/initializers/session_store.rb +8 -0
  45. data/example/config/initializers/wrap_parameters.rb +10 -0
  46. data/example/config/locales/en.yml +5 -0
  47. data/example/config/routes.rb +60 -0
  48. data/example/config.ru +4 -0
  49. data/example/db/seeds.rb +7 -0
  50. data/example/lib/assets/.gitkeep +0 -0
  51. data/example/lib/tasks/.gitkeep +0 -0
  52. data/example/log/.gitkeep +0 -0
  53. data/example/public/404.html +26 -0
  54. data/example/public/422.html +26 -0
  55. data/example/public/500.html +26 -0
  56. data/example/public/favicon.ico +0 -0
  57. data/example/public/index.html +241 -0
  58. data/example/public/robots.txt +5 -0
  59. data/example/script/rails +6 -0
  60. data/example/spec/controllers/posts_controller_spec.rb +164 -0
  61. data/example/spec/helpers/posts_helper_spec.rb +15 -0
  62. data/example/spec/models/post_spec.rb +5 -0
  63. data/example/spec/requests/posts_spec.rb +11 -0
  64. data/example/spec/routing/posts_routing_spec.rb +35 -0
  65. data/example/spec/spec_helper.rb +34 -0
  66. data/example/spec/views/posts/edit.html.erb_spec.rb +20 -0
  67. data/example/spec/views/posts/index.html.erb_spec.rb +23 -0
  68. data/example/spec/views/posts/new.html.erb_spec.rb +20 -0
  69. data/example/spec/views/posts/show.html.erb_spec.rb +17 -0
  70. data/example/vendor/assets/stylesheets/.gitkeep +0 -0
  71. data/example/vendor/plugins/.gitkeep +0 -0
  72. data/lib/ampere/collection.rb +6 -0
  73. data/lib/ampere/keys.rb +37 -0
  74. data/lib/ampere/model.rb +117 -58
  75. data/lib/ampere/timestamps.rb +23 -0
  76. data/lib/ampere.rb +17 -1
  77. data/lib/rails/generators/ampere/config/config_generator.rb +15 -0
  78. data/lib/rails/generators/ampere/config/templates/ampere.yml +11 -0
  79. data/lib/rails/generators/ampere/model/model_generator.rb +27 -0
  80. data/lib/rails/generators/ampere/model/templates/model.rb.tt +10 -0
  81. data/lib/rails/railtie.rb +49 -0
  82. data/lib/rails/tasks/ampere.rake +7 -0
  83. data/spec/models/model_spec.rb +12 -2
  84. data/spec/models/timestamps_spec.rb +45 -0
  85. data/spec/module/collections_spec.rb +5 -0
  86. data/spec/spec_helper.rb +1 -0
  87. metadata +153 -29
data/lib/ampere/model.rb CHANGED
@@ -1,14 +1,26 @@
1
1
  module Ampere
2
+ # Including the `Ampere::Model` module into one of your classes mixes in all
3
+ # the class and instance methods of an Ampere model. See individual methods
4
+ # for more information.
2
5
  module Model
3
-
4
6
  def self.included(base)
5
7
  base.extend(ClassMethods)
6
8
 
9
+ base.extend(Keys)
10
+
7
11
  base.class_eval do
12
+ extend(::ActiveModel::Callbacks)
13
+ define_model_callbacks :create, :update, :save
14
+
8
15
  include(::ActiveModel::Validations)
16
+ include(Rails.application.routes.url_helpers) if defined?(Rails)
17
+ include(ActionController::UrlFor) if defined?(Rails)
18
+
19
+ include(Ampere::Keys)
9
20
 
10
21
  attr_reader :id
11
-
22
+ attr_reader :destroyed
23
+
12
24
  attr_accessor :fields
13
25
  attr_accessor :field_defaults
14
26
  attr_accessor :indices
@@ -27,9 +39,9 @@ module Ampere
27
39
  # or have been stored and have the same ID, then they are equal.
28
40
  def ==(other)
29
41
  super or
30
- other.instance_of?(self.class) and
42
+ (other.instance_of?(self.class) and
31
43
  not id.nil? and
32
- other.id == id
44
+ other.id == id)
33
45
  end
34
46
 
35
47
  # Returns a Hash with all the fields and their values.
@@ -43,9 +55,15 @@ module Ampere
43
55
 
44
56
  # Deletes this instance out of the database.
45
57
  def destroy
58
+ @destroyed = true
46
59
  self.class.delete(@id)
47
60
  end
48
61
 
62
+ # Returns true if this record has been deleted
63
+ def destroyed?
64
+ @destroyed
65
+ end
66
+
49
67
  # Delegates to ==().
50
68
  def eql?(other)
51
69
  self == other
@@ -61,18 +79,27 @@ module Ampere
61
79
  #
62
80
  # Post.new :title => "Kitties: Are They Awesome?"
63
81
  def initialize(hash = {}, unmarshal = false)
82
+ @destroyed = false
83
+
64
84
  hash.each do |k, v|
65
85
  if k == 'id' then
66
86
  @id = unmarshal ? Marshal.load(v) : v
87
+ elsif k =~ /_id$/
88
+ self.send("#{k}=", v.to_i)
67
89
  else
68
- self.send("#{k}=", (unmarshal and not k =~ /_id$/) ? Marshal.load(v) : v)
90
+ self.send("#{k}=", unmarshal ? Marshal.load(v) : v)
69
91
  end
70
92
  end
71
93
  end
72
94
 
73
95
  # Returns true if this record has not yet been saved.
74
96
  def new?
75
- @id.nil? or not Ampere.connection.exists(@id)
97
+ @id.nil? or not Ampere.connection.exists(key_for_find(self.class, @id))
98
+ end
99
+ alias :new_record? :new?
100
+
101
+ def persisted?
102
+ not @id.nil?
76
103
  end
77
104
 
78
105
  # Reloads this record from the database.
@@ -82,9 +109,9 @@ module Ampere
82
109
  end
83
110
 
84
111
  self.class.fields.each do |k|
85
- v = Ampere.connection.hget(@id, k)
112
+ v = Ampere.connection.hget(key_for_find(self.class, @id), k)
86
113
  if k =~ /_id$/ then
87
- self.send("#{k}=", v)
114
+ self.send("#{k}=", v.to_i)
88
115
  else
89
116
  self.send("#{k}=", Marshal.load(v))
90
117
  end
@@ -94,50 +121,67 @@ module Ampere
94
121
 
95
122
  # Saves this record to the database.
96
123
  def save
97
- self.class.unique_indices.each do |idx|
98
- # For each uniquely-indexed field, look up the index for that field,
99
- # and throw an exception if this record's value for that field is in
100
- # the index already.
101
- if Ampere.connection.hexists("ampere.index.#{self.class.to_s.downcase}.#{idx}", instance_variable_get("@#{idx}")) then
102
- raise "Cannot save non-unique value for #{idx}"
103
- end
104
- end
105
-
106
- # Grab a fresh GUID from Redis by incrementing the "__guid" key
107
- if @id.nil? then
108
- @id = "#{self.class.to_s.downcase}.#{Ampere.connection.incr('__guid')}"
109
- end
110
-
111
- self.attributes.each do |k, v|
112
- Ampere.connection.hset(@id, k, k =~ /_id$/ ? v : Marshal.dump(v))
113
- end
114
-
115
- self.class.indices.each do |index|
116
- if index.class == String or index.class == Symbol then
117
- Ampere.connection.hset(
118
- "ampere.index.#{self.class.to_s.downcase}.#{index}",
119
- instance_variable_get("@#{index}"),
120
- ([@id] | (Ampere.connection.hget("ampere.index.#{self.class.to_s.downcase}.#{index}", instance_variable_get("@#{index}")) or "")
121
- .split(/:/)).join(":")
122
- )
123
- elsif index.class == Array then
124
- key = index.map{|i| instance_variable_get("@#{i}")}.join(':')
125
- val = ([@id] | (Ampere.connection.hget("ampere.index.#{self.class.to_s.downcase}.#{index}", key) or "")
124
+ run_callbacks :save do
125
+ run_callbacks :create do
126
+ self.class.unique_indices.each do |idx|
127
+ # For each uniquely-indexed field, look up the index for that field,
128
+ # and throw an exception if this record's value for that field is in
129
+ # the index already.
130
+ if Ampere.connection.hexists(key_for_index(idx), instance_variable_get("@#{idx}")) then
131
+ raise "Cannot save non-unique value for #{idx}"
132
+ end
133
+ end
134
+
135
+ # Grab a fresh GUID from Redis by incrementing the "__guid" key
136
+ if @id.nil? then
137
+ @id = Ampere.connection.incr('__guid')
138
+ end
139
+
140
+ self.attributes.each do |k, v|
141
+ Ampere.connection.hset(key_for_find(self.class, @id), k, k =~ /_id$/ ? v : Marshal.dump(v))
142
+ end
143
+
144
+ self.class.indices.each do |index|
145
+ if index.class == String or index.class == Symbol then
146
+ Ampere.connection.hset(
147
+ key_for_index(index),
148
+ instance_variable_get("@#{index}"),
149
+ ([@id] | (Ampere.connection.hget(key_for_index(index), instance_variable_get("@#{index}")) or "")
126
150
  .split(/:/)).join(":")
127
- Ampere.connection.hset(
128
- "ampere.index.#{self.class.to_s.downcase}.#{index.join(':')}",
129
- key,
130
- val
131
- )
151
+ )
152
+ elsif index.class == Array then
153
+ key = index.map{|i| instance_variable_get("@#{i}")}.join(':')
154
+ val = ([@id] | (Ampere.connection.hget(key_for_index(index), key) or "")
155
+ .split(/:/)).join(":")
156
+ Ampere.connection.hset(
157
+ key_for_index(index.join(':')),
158
+ key,
159
+ val
160
+ )
161
+ end
162
+ end
163
+ self
132
164
  end
133
165
  end
134
- self
166
+ end
167
+
168
+ def to_key #:nodoc:
169
+ # @id.nil? ? [] : [@id.to_i]
170
+ if destroyed?
171
+ [ @id.to_i ]
172
+ else
173
+ persisted? ? [ @id.to_i ] : nil
174
+ end
175
+ end
176
+
177
+ def to_param #:nodoc:
178
+ @id.to_s
135
179
  end
136
180
 
137
181
  def update_attribute(key, value)
138
- raise "Cannot update a nonexistent field!" unless self.class.fields.include?(key)
182
+ raise "Cannot update nonexistent field '#{key}'!" unless self.class.fields.include?(key.to_sym)
139
183
  self.send("#{key}=", value)
140
- Ampere.connection.hset(@id, key, Marshal.dump(value))
184
+ Ampere.connection.hset(key_for_find(self.class, @id), key, Marshal.dump(value))
141
185
  end
142
186
 
143
187
  def update_attributes(hash = {})
@@ -146,13 +190,14 @@ module Ampere
146
190
 
147
191
  # The inefficient way I know how to do right now:
148
192
  hash.each do |k, v|
149
- update_attribute(k, v)
193
+ self.send("#{k}=", v)
150
194
  end
195
+ self.save
151
196
  end
152
197
 
153
198
  ### Class methods
154
- module ClassMethods
155
- # Returns an array of all the records that have been stored.
199
+ module ClassMethods #:nodoc:
200
+ # Returns a lazy collection of all the records that have been stored.
156
201
  def all
157
202
  Ampere::Collection.new(self, Ampere.connection.keys("#{to_s.downcase}.*"))
158
203
  end
@@ -176,10 +221,13 @@ module Ampere
176
221
  def create(hash = {})
177
222
  new(hash).save
178
223
  end
224
+ alias :create! :create
179
225
 
180
226
  # Deletes the record with the given ID.
181
227
  def delete(id)
182
- Ampere.connection.del(id)
228
+ record = find(id)
229
+ Ampere.connection.del(key_for_find(self, id))
230
+ record
183
231
  end
184
232
 
185
233
  # Declares a field. See the README for more details.
@@ -229,7 +277,9 @@ module Ampere
229
277
 
230
278
  # Finds the record with the given ID, or the first that matches the given conditions
231
279
  def find(options = {})
232
- if options.class == String then
280
+ if options.class == String or options.is_a?(Fixnum) then
281
+ options = key_for_find(self, options)
282
+
233
283
  if Ampere.connection.exists(options) then
234
284
  new(Ampere.connection.hgetall(options), true)
235
285
  else
@@ -240,7 +290,11 @@ module Ampere
240
290
  raise "Cannot find by #{options.class} yet"
241
291
  end
242
292
  end
243
-
293
+
294
+ def first
295
+ all.first
296
+ end
297
+
244
298
  # Defines a has_one relationship with another model. See the README for more details.
245
299
  def has_one(field_name, options = {})
246
300
  referred_klass_name = (options[:class] or options['class'] or field_name)
@@ -268,16 +322,16 @@ module Ampere
268
322
  my_klass_name = to_s.downcase
269
323
 
270
324
  define_method(:"#{field_name}") do
271
- (Ampere.connection.smembers("#{to_s.downcase}.#{self.id}.has_many.#{field_name}")).map do |id|
325
+ (Ampere.connection.smembers(key_for_has_many(to_s.downcase, self.id, field_name))).map do |id|
272
326
  eval(klass_name.to_s.capitalize).find(id)
273
327
  end
274
328
  end
275
329
 
276
330
  define_method(:"#{field_name}=") do |val|
277
331
  val.each do |v|
278
- Ampere.connection.sadd("#{to_s.downcase}.#{self.id}.has_many.#{field_name}", v.id)
332
+ Ampere.connection.sadd(key_for_has_many(to_s.downcase, self.id, field_name), v.id)
279
333
  # Set pointer for belongs_to
280
- Ampere.connection.hset(v.id, "#{my_klass_name}_id", self.send("id"))
334
+ Ampere.connection.hset(key_for_find(v.class, v.id), "#{my_klass_name}_id", self.send("id"))
281
335
  end
282
336
  end
283
337
  end
@@ -309,7 +363,11 @@ module Ampere
309
363
  def indices
310
364
  @indices
311
365
  end
312
-
366
+
367
+ def last
368
+ all.last
369
+ end
370
+
313
371
  def unique_indices
314
372
  @unique_indices
315
373
  end
@@ -328,11 +386,11 @@ module Ampere
328
386
  unless indexed_fields.empty?
329
387
  indexed_fields.map {|key|
330
388
  if key.class == String or key.class == Symbol then
331
- Ampere.connection.hget("ampere.index.#{to_s.downcase}.#{key}", options[key]).split(/:/) #.map {|id| find(id)}
389
+ Ampere.connection.hget(key_for_index(key), options[key]).split(/:/) #.map {|id| find(id)}
332
390
  else
333
391
  # Compound index
334
392
  Ampere.connection.hget(
335
- "ampere.index.#{to_s.downcase}.#{key.join(':')}",
393
+ key_for_index(key.join(':')),
336
394
  key.map{|k| options[k]}.join(':')
337
395
  ).split(/:/) #.map {|id| find(id)}
338
396
  end
@@ -364,11 +422,12 @@ module Ampere
364
422
 
365
423
  private
366
424
 
367
- def compound_indices_for(query)
425
+ def compound_indices_for(query) #:nodoc:
368
426
  compound_indices.select{|ci|
369
427
  (query.keys - ci).empty?
370
428
  }
371
429
  end
430
+
372
431
  end
373
432
 
374
433
  end
@@ -0,0 +1,23 @@
1
+ module Ampere
2
+ module Timestamps
3
+ def self.included(base)
4
+ base.class_eval do
5
+ field :created_at
6
+ field :updated_at
7
+
8
+ define_model_callbacks :create, :update, :save
9
+
10
+ before_create do
11
+ @updated_at = Time.now
12
+ if @created_at.nil? then
13
+ @created_at = Time.now
14
+ end
15
+ end
16
+
17
+ before_save do
18
+ @updated_at = Time.now
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/ampere.rb CHANGED
@@ -3,26 +3,42 @@ require "active_record"
3
3
  require "redis"
4
4
  require "pp"
5
5
 
6
+ # The Ampere module contains methods to connect/disconnect and gives access to
7
+ # the Redis connection directly (though you really shouldn't need to use it).
6
8
  module Ampere
7
9
  @@connection = nil
8
10
 
11
+ # Open a new Redis connection. `options` is passed directly to the Redis.connect
12
+ # method.
9
13
  def self.connect(options = {})
10
14
  @@connection = Redis.connect(options)
11
15
  end
12
16
 
17
+ # Closes the Redis connection.
13
18
  def self.disconnect
14
19
  return unless connected?
15
20
  @@connection.quit
16
21
  @@connection = nil
17
22
  end
18
23
 
24
+ # Returns `true` if the Redis connection is active.
19
25
  def self.connected?
20
26
  !! @@connection
21
27
  end
22
28
 
29
+ # Gives access to the Redis connection object.
23
30
  def self.connection
24
31
  @@connection
25
32
  end
33
+
34
+ # Alias for Ampere.redis.flushall
35
+ def self.flush
36
+ @@connection.flushall if connected?
37
+ end
38
+
26
39
  end
27
40
 
28
- Dir[File.join(File.dirname(__FILE__), 'ampere', '**', '*.rb')].each {|f| require f}
41
+ Dir[File.join(File.dirname(__FILE__), 'ampere', '**', '*.rb')].each {|f| require f}
42
+
43
+ require File.join(File.dirname(__FILE__), "rails", "railtie.rb") if defined?(Rails)
44
+
@@ -0,0 +1,15 @@
1
+ module Ampere
2
+ module Generators
3
+ class ConfigGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../templates", __FILE__)
5
+
6
+ desc "Installs a skeleton Ampere config file for you."
7
+
8
+ argument :prefix, :type => :string, :optional => true
9
+
10
+ def create_config_file
11
+ template 'ampere.yml', File.join('config', "ampere.yml")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ development:
2
+ host: 127.0.0.1
3
+ port: 6379
4
+
5
+ test:
6
+ host: 127.0.0.1
7
+ port: 6379
8
+
9
+ production:
10
+ host: 127.0.0.1
11
+ port: 6379
@@ -0,0 +1,27 @@
1
+ module Ampere #:nodoc:
2
+ module Generators #:nodoc:
3
+ class ModelGenerator < Rails::Generators::NamedBase #:nodoc:
4
+ source_root File.expand_path("../templates", __FILE__)
5
+
6
+ desc "Creates an Ampere model"
7
+ argument :attributes, :type => :array, :default => [], :banner => "field field"
8
+
9
+ check_class_collision
10
+
11
+ def create_model_file
12
+ template "model.rb.tt", File.join("app/models", class_path, "#{file_name}.rb")
13
+ end
14
+
15
+ hook_for :test_framework
16
+
17
+ unless methods.include?(:module_namespacing)
18
+
19
+ # This is only defined on Rails edge at the moment, so include here now
20
+ # as per: https://github.com/mongoid/mongoid/issues/744
21
+ def module_namespacing(&block)
22
+ yield if block
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,10 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %>
3
+ include Ampere::Model
4
+
5
+ <% attributes.reject{|attr| attr.reference?}.each do |attribute| -%>
6
+ field :<%= attribute.name %>
7
+ <% end -%>
8
+
9
+ end
10
+ <% end -%>
@@ -0,0 +1,49 @@
1
+ require 'rails'
2
+
3
+ module Ampere
4
+ class Railtie < Rails::Railtie
5
+ if config.respond_to?(:app_generators) then
6
+ config.app_generators.orm :ampere, :migration => false
7
+ else
8
+ config.generators.orm :ampere, :migration => false
9
+ end
10
+
11
+ console do
12
+ Ampere.connect
13
+ puts "[ampere] Connected."
14
+ end
15
+
16
+ initializer 'railtie.initialize_redis_connection' do |app|
17
+ config_file = Rails.root.join("config", "ampere.yml")
18
+
19
+ options = {
20
+ 'development' => {
21
+ 'host' => '127.0.0.1',
22
+ 'port' => 6379
23
+ },
24
+ 'test' => {
25
+ 'host' => '127.0.0.1',
26
+ 'port' => 6379
27
+ },
28
+ 'production' => {
29
+ 'host' => '127.0.0.1',
30
+ 'port' => 6379
31
+ },
32
+ }
33
+
34
+ if config_file.file?
35
+ options = YAML.load_file(config_file)
36
+ end
37
+
38
+ Rails.logger.info "[ampere] Initializing redis connection redis://#{options[Rails.env]['host']}:#{options[Rails.env]['port']}"
39
+
40
+ Ampere.connect options[Rails.env]
41
+ end
42
+
43
+ rake_tasks do
44
+ load File.join(File.dirname(__FILE__), '..', 'rails', 'tasks', 'ampere.rake')
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,7 @@
1
+ namespace :ampere do
2
+ desc "Flush ALL data out of Redis (only call this if you know what you're doing!)"
3
+ task :flush do
4
+ Redis.new.flushall
5
+ end
6
+
7
+ end
@@ -177,7 +177,9 @@ describe "Base models", :model => true do
177
177
  :content => "and it doesn't even make sense."
178
178
  id = post.id
179
179
  post.should_not be_nil
180
- Post.delete(id).should == 1
180
+ deleted_post = Post.delete(id)
181
+ deleted_post.should eq(post)
182
+ deleted_post.title.should eq(post.title)
181
183
  Post.find(id).should be_nil
182
184
  end
183
185
 
@@ -186,7 +188,7 @@ describe "Base models", :model => true do
186
188
  :byline => "Just seems like one big",
187
189
  :content => "non sequitor."
188
190
  id = another_post.id
189
- another_post.destroy.should == 1
191
+ another_post.destroy.should eq(another_post)
190
192
  Post.find(id).should be_nil
191
193
  end
192
194
 
@@ -207,9 +209,17 @@ describe "Base models", :model => true do
207
209
  post = Post.create :title => "Another title",
208
210
  :byline => "Max",
209
211
  :content => "Some other content"
212
+ post.should be_persisted
210
213
  Post.find(post.id).should == post
211
214
  end
212
215
 
216
+ it "should return an integer for its ID" do
217
+ post = Post.create :title => "On Motorcycles and Robots",
218
+ :byline => "Max",
219
+ :content => "Motorcycles and robots are awesome"
220
+ post.id.should be_a(Fixnum)
221
+ end
222
+
213
223
  # # #
214
224
 
215
225
  after :all do
@@ -0,0 +1,45 @@
1
+ describe Ampere::Timestamps do
2
+ before :all do
3
+ Ampere.connect
4
+
5
+ Ampere.connection.flushall
6
+
7
+ class Comment
8
+ include Ampere::Model
9
+ include Ampere::Timestamps
10
+
11
+ field :body
12
+ end
13
+ end
14
+
15
+ context 'when included in models' do
16
+ it 'sets created_at for newly-created record' do
17
+ Timecop.freeze(Time.now) do
18
+ time = Time.now
19
+
20
+ c = Comment.create body: "I am intrigued by your ideas, and would like to subscribe to your newsletter."
21
+ c.created_at.should eq(time)
22
+ c.updated_at.should eq(time)
23
+ end
24
+ end
25
+
26
+ it 'sets updated_at when changing records' do
27
+ c = Comment.create body: "I am intrigued by your ideas, and would like to subscribe to your newsletter."
28
+ created_at = c.created_at
29
+
30
+ time = 0
31
+
32
+ Timecop.freeze(Time.now + 30) do
33
+ time = Time.now
34
+
35
+ c.body = "Theodore Roosevelt riding a moose, therefore your argument is invalid."
36
+ c.save
37
+ end
38
+
39
+ c.updated_at.should eq(time)
40
+ c.created_at.should eq(created_at)
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -72,6 +72,11 @@ describe 'Collections', :collections => true do
72
72
  republicans.to_a.map(&:name).should == ["Ulysses S. Grant", "Abraham Lincoln"]
73
73
  end
74
74
 
75
+ it 'should give its first and last elements non-lazily' do
76
+ President.first.name.should eq("Millard Fillmore")
77
+ President.last.name.should eq("Jimmy Carter")
78
+ end
79
+
75
80
  ###
76
81
 
77
82
  after :all do
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,7 @@ end
7
7
  require 'rspec'
8
8
  require 'rspec/autorun'
9
9
  require 'shoulda'
10
+ require 'timecop'
10
11
 
11
12
  require 'ampere'
12
13