next available record id - ruby-on-rails

#user = User.new
#user.id returns nil but i need to know it before i save. Is it possible ?

YES you can!
I had the same question and investigated the docs.
The ability to solve this question is very related to your database type in fact.
Oracle and Postgresql do have useful functions to easily solve this.
For MySQL(oracle) or SkySQL(open-source) it seems more complicated (but still possible). I would recommend you avoid using these (MySQL/SkySQL) databases if you need advanced database tools.
First you must try to avoid this situation as much as possible in your application design, as it is dangerous to play with IDs before they get saved.
There may be situation where you don't have any other choice:
For instance when two tables are referencing themselves and for security reason you don't allow DELETE or UPDATE on these tables.
When this is the case, you can use the (PostgreSQL, Oracle) database nextval function to generate the next ID number without actually inserting a new record.
Use it in conjunction with the find_by_sql rails method.
To do this with postgreSQL and Rails for instance, choose one of your rails models and add a class method (not an instance method!).
This is possible with the "self" word at the beginning of the method name.
self tells Ruby that this method is usable only by the class, not by its instance variables (the objects created with 'new').
My Rails model:
class MyToy < ActiveRecord::Base
...
def self.my_next_id_sequence
self.find_by_sql "SELECT nextval('my_toys_id_seq') AS my_next_id"
end
end
When you generate a table with a Rails migration, by default Rails automatically creates a column called id and sets it as the primary key's table. To ensure that you don't get any "duplicate primary key error", Rails automatically creates a sequence inside the database and applies it to the id column. For each new record (row) you insert in your table, the database will calculate by itself what will be the next id for your new record.
Rails names this sequence automatically with the table name append with "_id_seq".
The PostgreSQL nextval function must be applied to this sequence as explained here.
Now about find_by_sql, as explained here, it will create an array containing new objects instances of your class. Each of those objects will contain all the columns the SQL statement generates. Those columns will appear in each new object instance under the form of attributes. Even if those attributes don't exist in your class model !
As you wisely realized, our nextval function will only return a single value.
So find_by_sql will create an array containing a single object instance with a single attribute.
To make it easy to read the value of this very attribute, we will name the resulting SQL column with "my_next_id", so our attribute will have the same name.
So that's it. We can use our new method:
my_resulting_array = MyToy.my_next_id_sequence
my_toy_object = my_resulting_array[0]
my_next_id_value = my_toy_object.my_next_id
And use it to solve our dead lock situation :
my_dog = DogModel.create(:name => 'Dogy', :toy_id => my_next_id_value)
a_dog_toy = MyToy.new(:my_dog_id => my_dog.id)
a_dog_toy.id = my_next_id_value
a_dog_toy.save
Be aware that if you don't use your my_next_id_value this id number will be lost forever. (I mean, it won't be used by any record in the future).
The database doesn't wait on you to use it. If somewhere at any time, your application needs to insert a new record in your my_table_example (maybe at the same time as we are playing with my_next_id_sequence), the database will always assign an id number to this new record immediately following the one you generated with my_next_id_sequence, considering that your my_next_id_value is reserved.
This may lead to situations where the records in your my_table_example don't appear to be sorted by the time they were created.

No, you can't get the ID before saving. The ID number comes from the database but the database won't assign the ID until you call save. All this is assuming that you're using ActiveRecord of course.

I had a similar situation. I called the sequence using find_by_sql on my model which returns the model array. I got the id from the first object of the arry. something like below.
Class User < ActiveRecord::Base
set_primary_key 'user_id'
alias user_id= id=
def self.get_sequence_id
self.find_by_sql "select TEST_USER_ID_SEQ.nextval as contact_id from dual"
end
end
and on the class on which you reference the user model,
#users = User.get_sequence_id
user = users[0]

Normally the ID is filled from a database sequence automatically.
In rails you can use the after_create event, which gives you access to the object just after it has been saved (and thus it has the ID). This would cover most cases.
When using Oracle i had the case where I wanted to create the ID ourselves (and not use a sequence), and in this post i provide the details how i did that. In short the code:
# a small patch as proposed by the author of OracleEnhancedAdapter: http://blog.rayapps.com/2008/05/13/activerecord-oracle-enhanced-adapter/#comment-240
# if a ActiveRecord model has a sequence with name "autogenerated", the id will not be filled in from any sequence
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
alias_method :orig_next_sequence_value, :next_sequence_value
def next_sequence_value(sequence_name)
if sequence_name == 'autogenerated'
# we assume id must have gotten a good value before insert!
id
else
orig_next_sequence_value(sequence_name)
end
end
end
while this solution is specific to Oracle-enhanced, i am assuming the other databases will have a similar method that you could redefine.
So, while it is definitely not advised and you want to be absolutely sure why you would not want to use an id generated by a sequence, if it is needed it is most definitely possible.
It is why I love ruby and Ruby on Rails! :)

In Oracle you can get your current sequence value with this query:
SELECT last_number FROM user_sequences where sequence_name='your_sequence_name';
So in your model class, you can put something like this:
class MyModel < ActiveRecord::Base
self.sequence_name = 'your_sequence_name'
def self.my_next_id_sequence
get_data = self.find_by_sql "SELECT last_number FROM user_sequences where sequence_name='your_sequence_name'"
get_data[0].last_number
end
end
And finally, in controller you can get this value with this:
my_sequence_number = MyModel.my_next_id_sequence
So, there is no need to get your next value by using NEXTVAL and you won't lose you ID.

What you could do is User.max(id). which will return the highest ID in the database, you could then add 1. This is not reliable, although might meet your needs.

Since Rails 5 you can simply call next_sequence_value
Note: For Oracle when self.sequence_name is set, requesting next sequence value creates side effect by incrementing sequence value

Related

mongoid pluck - not returning correct values with default values in model

I have a User model where there is a department field which has a default value of "Engineering".
This field was introduced 2 months after our website went live,and since its mongo, there was no migration.
When I try to get the object using where, correct value is returned
User.find_by(:name => "John).department
However, if I try to pluck values, it returns nil and not the default value.
User.limit(2).pluck(:department)
returns
[nil,"Finance"]
I researched a bit and came across this blog post http://ahmadsherif.com/blog/2013/01/29/mongoid-default-fields-can-give-you-hard-time/
I think I am facing the same issue. Is there any work around for this? I chose to go with pluck because it's not memory intensive and saves time.
Basically the behaviour here can be explained by the difference in how MongoDB deals with default values vs a traditional relational database like Postgres.
In the SQL world you set defaults via the database schema and the DB will fill a NULL field everytime you insert a row.
Since MongoDB is schemaless document fields have a default value of nil, which cannot be changed* since there is no schema where we could define defaults on the database level. Instead defaults are implemented on the application level. For Mongoid this means when you initialize a new model instance it will fill in default values if they are nil.
In ActiveRecord terms it would look like this:
class Thing < ActiveRecord::Base
after_initialize :set_default_foo, if: -> { self.foo.nil? }
private
def set_default_foo
self.foo = "bar"
end
end
However if you have existing documents and add a new field or defaults to an existing field Mongoid does not update the existing documents for you!
So how does this explain these two cases?
User.find_by(:name => "John").department
User.limit(2).pluck(:department)
In the first case you a pulling a document out of storage and using it to inialize a model instance. When the model instance is initialized a callback is run which sets the default values.
When .pluck is called on the hand Mongoid pulls the values directly from the store without initializing any model instances. Thus for any "legacy" documents it will return a nil value.
To remedy this you you need to set the default value for any document with a nil.
User.where(department: nil).update_all(department: 'engineering')

what is the difference between an object_id, and id?

for any model instance, there is an #object_id and #id - I know that they are not the same.
However, I'm not quite sure what makes them different and how they would each be used in context.
Please help clear this up!!
Thanks!
In Rails, an ActiveRecord model instance has an id property that maps to the value stored in the id column of the database. This may be nil if the record hasn't been saved.
In Ruby, object_id is a value that represents the identity of the object in question. It is always populated with something since everything in Ruby is an object.
These two are not related. There may be several independent instances of a model, each with their own object_id value but an identical id.
If two variables refer to something with the same object_id, then they refer to exactly the same object.
It's rare to see object_id used in code, it's a Ruby internal that's hardly ever needed. Mostly it's to establish if you're talking about identical objects, or just equivalent ones.
You will, on the other hand, see id and similar values used frequently since that's the glue that holds your relational database together.
Everything (and i mean everything) in Ruby is an object. Each of these objects has an object_id, which is a value used to track them in memory, basically.
In Rails, model instances are automatically set up with methods to return their value from the corresponding column in the database. .id is one of these.
As far as using in context, generally you would not use object_id in your code, ever: it's an under-the-hood thing.
EDIT - as an aside, a common issue seen in older versions of ruby/rails (where the object_id method was actually called id, and was overridden by rail's id method) was caused by the fact that the object_id of nil is 4. So you would call id on a variable which you expected to be a model instance, but was actually nil, thus getting "4" back when you expected to get the id of a record from the database.
In short, :id is the default primary_key of a table.And object_id could be the foriegn_key unless you set a custom foreign_key.
For example,take these two table users and posts.The relation would be
user => has_many posts and
post => belongs to user
so in the posts table,we should create a foreign_key(in this case user_id) to make the relations works.
Hope it helps!
id is specific for ActiveModel record and it relates to id column in database. object_id is defined on Object and is unique for every single object created in the memory.

STI in Rails: How do I change from a superclass to a subclass without accessing the "type" attribute directly?

So, I have the following:
class Product < ActiveRecord::Base
# Has a bunch of common stuff about assembly hierarchy, etc
end
class SpecializedProduct < Product
# Has some special stuff that a "Product" can not do!
end
There's a manufacturing and assembly process in which data is captured about Products. At the time of capture the eventual product type is not known. after the Product record has been created in the database (perhaps days later) it may be necessary to turn that product into a specialized product and fill in the additional information. Not all products will become specialized, however.
I've been trying to use the following:
object_to_change = Product.find(params[:id])
object_to_change.becomes SpecializedProduct
object_to_change.save
Then, when I do a SpecializedProduct.all the resulting set does not include object_to_change. Instead object_to_change is still listed in the database as Product
UPDATE "products" SET "type" = ?, "updated_at" = ? WHERE "products"."type" IN ('SpecializedProduct') AND "products"."id" = 30 [["type", "Product"], ["updated_at", Fri, 17 May 2013 10:28:06 UTC +00:00]]
So, after the call to .becomes SpecializedProduct the .save method is now using the right type, but it's not able to update the record because the WHERE clause of the update is too specific.
Do I really need to access the type attribute of the model directly? I'd really rather not.
Looking at the source of becomes and becomes!, it doesn't mutate the original object. You need to assign it to a new variable:
some_product = Product.find(params[:id])
specialized_product = some_product.becomes SpecializedProduct
specialized_product.save
Not sure how this will handle the primary key of the record, though, so you may need to do some additional finagling to make sure your relations don't get mangled.
You just need the bang version of the becomes method with (!) and save.
The difference between the two methods: becomes creates a new instance of the new class with all the same attribute values of the original object. becomes! also updates the type column.
object_to_change = Product.find(params[:id])
object_to_change.becomes! SpecializedProduct
object_to_change.save
I think no! Check the sorces of becomes and becomes! methods!
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L199
Looks like you need to use becomes! because it is wrapper around becomes that also changes the instance's sti column value.
UPD: https://github.com/rails/rails/blob/4-0-stable/activerecord/test/cases/persistence_test.rb#L279
This is a test case for your code.
UPD2:
I think you can try to create another class sort of DefaultProject which is subclass of Project and then you can change each from DefaultProject to SpecializedProduct and vice versa
I have a similar issue where I want to change from one subclass to another. Unfortunately Rails doesn't do this gracefully because it wants to limit the save with a "where type='NewSubclass'". ie:
UPDATE parents SET type='NewSubclass' WHERE type IN 'NewSubclass' AND id=1234
Digging into rails, it appears that the method in ActiveRecord's lib/active_record/inheritance.rb named "finder_needs_type_condition?" is called and the caller isn't smart enough to realize you are changing the type field so it obviously isn't already that value.
I "resolved" this in a round about way. I used the ActiveRecord core as a basis for how to load an instance of whichever class I want with attributes and save it without having to go through the full ActiveRecord find stack.
old_instance = Parent.find(id) #returns OldSubclass instance
tmp = Parent.allocate.init_with('attributes' => old_instance.attributes)
tmp.type = 'NewSubclass'
tmp.save
Parent.find(id) #returns NewSubclass instance
It's real ugly and I hate it. Hopefully someone will think about fixing this in ActiveRecord. I believe it would be useful for objects to change subclasses in STI over time. I have a single table with 5 subclasses because it cleans up the model quite a bit.
This is the only ugliness I had to live with. Be sure to write proper tests so that when ActiveRecord breaks this "workaround" you can detect it.

Rails - How can I save a field value (that uses a record's ID) though a callback?

I want to create a hash that combines the creating user's user_id + the record's ID to make a MD5 hash, but only on record creation. (Reasons are long-winded but this extracts it).
I am trying:
class BlogPost < ActiveRecord::Base
after_create :hash_this
private
def hash_this
self.info_md5_hashed = (id.to_str + creator_user_id).my_MD5_hash_method
end
end
How can I make sure that the info_md5_hashed field actually gets saved to the database?
If I use before_create I would assume that the ID is not yet available? :(
If I use after_create I would assume that the ID is now available
- but do I need to do another save somehow to store the newly calculated info_md5_hashed field value?
Yes, you will have to save the record twice, since ID generation happens in the database. The only way around that is to pre-sequence an ID, but there's almost certainly no point and you should just accept that you will have to save the record twice ;)
Just call the second save from inside your after_create hook- it's ok to do this (i.e. will not be a looping recurrence issue) because the second save won't re-trigger that hook again ;)
n.b. You could always base the hash on something you know to be unique, such as the username or email, if you want to avoid a double-save.

Type method interfering with database type column problem

I'm using Ruby on Rails and the paths_of_glory gem
I need to access the types of achievements that a user accomplishes in order to display a picture along with each particular achievement. When I try to access it via #user.achievements.type, I get an error that says that it wants to return "array" (as in achievements is an array) instead of actually returning the elements in the type column of my database.
Since every ruby object has a method called type, my call to access the type column of the database fails. When I try to change the entry in the table, the paths_of_glory gem says it needs a type column in order to function properly.
I'm not quite sure where to go from here in order to access that column in the database. Any suggestions?
Not entirely sure what you're asking here, but maybe this will help.
For the first thing, #user.achievements is an array because you have multiple achievements, and the type method is for individual elements of #user.achievements which is why that won't work just like that. You'll have to do something like this:
#user.achievements.each do |achievement|
# Do stuff here
end
Regarding the type column, type is a reserved column in Rails used specifically for Single Table Inheritance, where multiple Rails models use a single database table. So you can't access it directly. I assume that paths_of_glory uses STI in some manner. You can access the model's class with something like achievement.class, then if you want just the name of it you can try achievement.class.to_s.
#user.achievements.each do |achievement|
model = achievement.class # => MyAwesomeAchievementClass
#image = model.picture # You could write some method in the model like this
end

Resources