ampere 1.1.1 → 1.2.0

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 (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