Skip to content

Instantly share code, notes, and snippets.

@ShogunPanda
Forked from masatomo/sequence.rb
Created July 16, 2011 11:15
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ShogunPanda/1086265 to your computer and use it in GitHub Desktop.
Save ShogunPanda/1086265 to your computer and use it in GitHub Desktop.
adds a feature to set sequence number to Mongoid::Document
# encoding: utf-8
module Mongoid
# Include this module to add automatic sequence feature (also works for _id field, so SQL-Like autoincrement primary key can easily be simulated)
# usage:
# class KlassName
# include Mongoid::Document
# include Mongoid::Sequence
# ...
# field :number, :type=>Integer
# sequence :number
# ...
module Sequence
extend ActiveSupport::Concern
module ClassMethods
def sequence(_field)
# REPLACE FIELD DEFAULT VALUE
_field = _field.to_s
field(_field, fields[_field].options.merge(:default => lambda{ self.class.set_from_sequence(_field)}))
end
def set_from_sequence(_field)
sequences = self.db.collection("__sequences")
counter_id = "#{self.class.name.underscore}_#{_field}"
# Increase the sequence value and also avoids conflicts
catch(:value) do
value = nil
begin
value = sequences.find_and_modify(
:query => {"_id" => counter_id},
:update=> {"$inc" => {"value" => 1}},
:new => true,
:upsert => true
).send("[]", "value")
end while self.first({:conditions => {_field => value}})
throw :value, value
end
end
def reset_sequence(_field)
sequences = self.db.collection("__sequences")
counter_id = "#{self.class.name.underscore}_#{_field}"
sequences.find_and_modify(:query => {"_id" => counter_id}, :update=> {"$set" => {"value" => 0}}, :new => true, :upsert => true)
end
end
end
end
@tomash
Copy link

tomash commented Feb 1, 2012

Hmm, I'm getting

ruby-1.9.2-head :001 > Band.new
NoMethodError: undefined method `set_from_sequence' for #<Band:0xbc33ccc>

while being sure that the module has been loaded

class Band
   include Mongoid::Document
   include Mongoid::Timestamps
   include Mongoid::Sequence

@tomash
Copy link

tomash commented Feb 1, 2012

OK, replacing line #19 with

field(_field, fields[_field].options.merge(:default => lambda{ self.class.set_from_sequence(_field)}))

solves the problem.

This should become a gem! :)

@ShogunPanda
Copy link
Author

Well done! ^^

This module is now included in https://github.com/ShogunPanda/cowtech-rails (in which I've just included your fix), which includes also many other improvements.
Hope you'll like it!

@ShogunPanda
Copy link
Author

Gist updated!

@tomash
Copy link

tomash commented Feb 1, 2012

thanks!

in the end (10 minutes ago ;)) i actually went with the original gist that inspired you -- https://gist.github.com/730677 -- because yours generates a new number on every save, and this is a bug/misfeature that's well beyond my mongo knowledge to fix it.

@paulsfds
Copy link

In the comments, it says that this code supports the default _id field, but when I try inserting sequence :_id in my model code, it does not simulate the SQL-Like autoincrement primary keys. Anyone else have this issue?

@ShogunPanda
Copy link
Author

@paulsdfs:
Can you post your model class to make us check it?

@paulsfds
Copy link

So I put this code in a file called mongoid_sequence.rb and put it in the Rails.root/config/initializers so that it will load on server start. Here is my model class:

class Event
  include Mongoid::Document
  include Mongoid::Paperclip
  include Mongoid::Sequence

  sequence :_id

  field :name, type: String
  field :start_time, type: DateTime

  attr_accessible :name, :start_time

  has_mongoid_attached_file :banner_image

  validates_presence_of :name, allow_nil: false
end

After restarting the server, it continues to create new Event objects with IDs such as 4f49f1f21d41c8696b000002, but I was hoping it would generate IDs in a MySQL fashion such as 1, 2, 3, etc.

Thanks for your help!

@ShogunPanda
Copy link
Author

Have you tried to specifiy _id field as Integer?

@paulsfds
Copy link

Yes, I tried that as well. The IDs still get generated in a non-MySQL fashion and I have problems with the Event.find method. When I try to view the object by accessing it through /events/, I get the following error:

  app/controllers/events_controller.rb:17:in`show'

```
Where line 17 is @event = Event.find(params[:id]).
```

@ShogunPanda
Copy link
Author

ShogunPanda commented Feb 26, 2012 via email

@goncalossilva
Copy link

Just packaged something similar to this into a gem: https://github.com/goncalossilva/mongoid-sequence

@paulsfds: I believe you won't have ID issue with this gem.

@ShogunPanda
Copy link
Author

Well done! Thanks!

@paulsfds
Copy link

@goncalossilva - Thanks for your reply. I installed your gem, but I am getting errors. I always get the error, "ActionController::RoutingError (uninitialized constant Mongoid::Sequence)." I also tried one of your test classes (http://github.com/goncalossilva/mongoid-sequence/blob/master/test/models/id_sequenced_model.rb), and I also get the same error.

In my Gemfile I have gem "mongoid-sequence", and I did do a bundle install, so I am not sure what is wrong.

@goncalossilva
Copy link

@paulsfds That's odd. Are you using "bundle exec rails server"? If yes, is your app open source (for me to take a look)?

@paulsfds
Copy link

@goncalossilva Yes, I did use bundle exec rails server. I uploaded a sample project on my github account here: http://github.com/paulsfds/MongoidSequenceTest. When I start up the server and try to access the /samples route, I get the following error:

Started GET "/samples" for localhost at 2012-03-19 15:53:32 -0700

ActionController::RoutingError (uninitialized constant Mongoid::Sequence):
app/models/sample.rb:3:in <class:Sample>' app/models/sample.rb:1:in<top (required)>'
app/controllers/samples_controller.rb:1:in `<top (required)>'

@goncalossilva
Copy link

@paulsfds I just clone your project, ran bundle install, ran bundle exec rails s, hit /samples and everything worked fine. Could you update your bundler and see if the problem disappears?

@paulsfds
Copy link

@goncalossilva I don't know what the problem is, but I'm still getting the same error. I am using Bundler version 1.0.21. I tried uninstalling the gem (gem uninstall mongoid-sequence -a) and deleting the Gemfile.lock. I also tried bundle update, but the gem is up to date already. I wonder if anyone else is having this issue or is it just me?

@goncalossilva
Copy link

@paulsfds I'm using ruby 1.9.3p125 and bundler 1.1.1 here.

@goncalossilva
Copy link

@paulsfds I'm 99% confident that you won't have that issue anymore with mongoid-sequence 0.1 (released a few minutes ago). Try it! :)

@paulsfds
Copy link

@goncalossilva I'm playing around with this gem again (I had forgotten about it for a month), and it seems to be working but I noticed one issue. The model's find() method only takes an integer, and it errors out if you try and pass it a string.

1.9.2-p290 :022 > Sample.find(1)
=> #<Sample _id: 1, _type: nil, name: nil>

1.9.2-p290 :024 > Sample.find("1")
BSON::InvalidObjectId: illegal ObjectId format: 1

The default Mongoid implementation of find takes only a string. A workaround is to just force the param to be an integer like so:
@sample = Sample.find(params[:id].to_i)

I noticed that in your gem, the id actually becomes an integer whereas in Mongoid, the id is a BSON::ObjectId object. This may be the problem, is there a way to store the sequential numbers (for ids) as BSON::ObjectId instead of as in integer?

1.9.2-p290 :027 > Sample.find(1)._id
=> 1
1.9.2-p290 :028 > Sample.find(1)._id.is_a?(Integer)
=> true

1.9.2-p290 :009 > Event.first._id
=> BSON::ObjectId('4f98e4271d41c827cb000007')
1.9.2-p290 :010 > Event.first._id.is_a?(Integer)
=> false

@goncalossilva
Copy link

@paulsfds I believe that's the default behavior when you field :my_id, :type => Integer... the sequencing code doesn't play with any of that. Please open an issue with this if you think it's something else :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment