How do you get the id of a rails model before it is saved?
For example, if I create a new model instance, how can I get its ID before it is saved?
I know that the id is created onsave and according to the database but is there a workaround?
I was looking for this too, and I found an answer:
Let's suppose model name is "Model" and table name is "models"
model.rb
before_save {
next_id=Model.connection.select_value("Select nextval('models_id_seq')")
}
This will output the value your record will take for id IF it gets saved
Usually when people think they need to do this they actually do not need to do it. Like John says, explain what you are trying to do and someone can probably suggest a way to do it without having to know the id in advance.
This is less a Rails question and more a database question. This is a problem that will present itself in any web application framework, and the solution is the same in all places. You have to use a database transaction.
The basic flow will work like this.
Open a transaction
Save your model
Use the ID assigned by the database
If it turns out you actually don't want to keep this model in the database, roll back the transaction.
If it turns out you want to keep the model in the database, commit the transaction.
The main thing you will notice from this approach is that there will be gaps in your IDs where you rolled back the transaction.
Using the default Rails convention of an auto-incrementing integer primary key, there's no way to get the ID of a model before it's saved because it's generated by the RDBMS when the new row is inserted in the relevant table.
What problem are you actually trying to solve?
Most of the time when I needed an id can be grouped into a short list.
When creating nested associations or connectin of the associations through.
Let's assume we have: :user that have :pets through :user_pets association, where we will save their type.
If we have a properly configured "has_many: through Association" we can just
User.pets.create(name: "Rex") but this is too simplistic, as we want to creat :pet type in :user_pets.
u = User.create(name: "Cesar")
u.id # => 1 # works fine
p = u.pets.create(name: 'Rex')
# => rails will create UserPets => {id: 1, user_id: 1, pet_id: 1} for us
# But now we have a problem, how do we find :user_pets of our :pet?
# remember we still need to update the :type, the ugly (wrong) way:
up = p.user_pets.first
up.type = 'dog'
up.save! # working but wrong
# Do you see the problems here? We could use an id
P = Pet.new( name: "Destroyer" )
p.id # will not work, as the pet not saved yet to receive an id
up = UserPet.new( user_id: U.id, pet_id: p.id )
# => UserPet {id: 2, user_id: 1, pet_id: nil} # as we expected there is no id.
# What solutions do we have? Use nested creation!
# Good
up = UserPet.new(user_id: u.id, type: "dog")
# even better
up = u.user_pets.new(type: "dog")
# it's just a shortcut for the code above,
# it will add :user_id for us, so let's just remember it.
# Now lets add another 'new' from our creatd 'user_pet'
p = up.pets.new(name: "Millan")
user.save!
# => UserPet: {id: 3, user_id: 1, pet_id: 2, type: 'dog'} # => Pet: {id: 2, name: "Sam"}
# everything is working! YEY!
# we can even better, than writing in the beginning "User.create",
# we can write "User.new" and save it with all the nested elements.
You saw how this created all the ids for us? Let's move to something even more complex!
Now we have an additional table :shampoo that exactly as :user_pet, belongs to a :pet and a :user
We need to create it without knowing the id of the :user and :pet
u = User.new('Mike')
up = u.user_pets.new(type: "cat")
p = up.pets.new(name: "Rowe")
# But what are we doing now?
# If we do:
s = u.shampoos.new(name: "Dirty Job")
# => Shampoo: {user_id: 2, pet_id: nil, name: "..."}
# We get "pet_id: nil", not what we want.
# Or if we do:
s = p.shampoos.new(name: "Schneewittchen")
# => Shampoo: {user_id: nil, pet_id: 3, name: "..."}
# We get "user_id: nil", in both cases we do not get what we want.
# So we need to get the id of not created record, again.
# For this we can just do as in the first example (order is not important)
s = u.shampoos.new(name: "Mission Impossible")
# => Shampoo: {id: 3, user_id: 2, pet_id: nil, name: "..."}
s.pet = p # this will give the missing id, to the shampoo on save.
# Finish with save of the object:
u.save! #=> Shampoo: {id: 3, user_id: 2, pet_id: 3, name: '...'} # => Pet: ...
# Done!
I tried to cover most common causes when you need element id, and how to overcome this. I hope it will be useful.
I don't believe there are any workarounds since the id is actually generated by the database itself. The id should not be available until after the object has been saved to the database.
Consider doing what you want right after the instance is created.
after_create do
print self.id
end
First understand the structure of database.
Id is generated using sequence
increment done by 1 (specified while creating sequence)
Last entry to database will have highest value of id
If you wanted to get id of record which is going to be saved,
Then you can use following:
1. id = Model.last.id + 1
model = Model.new(id: id)
model.save
# But if data can be delete from that dataabse this will not work correctly.
2. id = Model.connection.select_value("Select nextval('models_id_seq')")
model = Model.new(id: id)
model.save
# Here in this case if you did not specified 'id' while creating new object, record will saved with next id.
e.g.
id
=> 2234
model = Model.new(id: id)
model.save
# Record will be created using 'id' as 2234
model = Model.new()
model.save
# Record will be created using next value of 'id' as 2235
Hope this will help you.
I just ran into a similar situation when creating a data importer. I was creating a bunch of records of different types and associating them before saving. When saving, some of the records threw validation errors because they had validate_presence_of a record that was not yet saved.
If you are using postgres, active record increments the id it assigns to a Model by keeping a sequence named models_id_seq (sales_id_seq for Sale etc.) in the database. You can get the next id in this sequence and increment it with the following function.
def next_model_id
ActiveRecord::Base.connection.execute("SELECT NEXTVAL('models_id_seq')").first["nextval"].to_i
end
However, this solution is not good practice as there is no guarantee that active record will keep id sequences in this way in the future. I would only use this if it was used only once in my project, saved me a lot of work and was well documented in terms of why it should not be used frequently.
I know it's an old question, but might as well throw my answer in case anyone needs to reference it
UserModel
class User < ActiveRecord::Base
before_create :set_default_value
def set_default_value
self.value ||= "#{User.last.id+1}"
end
Related
I have two models:
#user.rb
has_and_belongs_to_many :groups
#group.rb
has_and_belongs_to_many :users
I want to associate groups to users such that many users can be associated to many groups.
Front end side of the application is sending the user_ids and group_ids to the update method.
So in the UsersController, i've found out each user and associated that user with the groups.
def update
users = User.where(id: [325, 326])
users.each { |user| user.update(group_ids: [1, 2])}
end
I need to know the standard way of updating the associated records and what is the most efficient way of updating associated records?
There is a method update_all
def update
users = User.where(id: [325, 326])
users.update_all group_ids: [1, 2]
end
The simplest ActiveRecord-native approach is to create a model class for your join table and use that, e.g.:
class GroupsUser < ActiveRecord::Base; end
(Note that the model class is GroupsUser so that by convention ActiveRecord will know it's for the groups_users table. It's a little goofy but it works.)
Then you can use insert_all on the join table:
user_ids = [325, 326]
group_ids = [1, 2]
values = user_ids.map do |uid|
group_ids.map do |gid|
{ user_id: uid, group_id: gid }
end
end.flatten
# => [{user_id: 325, group_id: 1}, {user_id: 325, group_id: 2}, {user_id=>326, group_id: 1}, {user_id: 326, group_id: 2}]
GroupsUser.insert_all(values)
Note though that this is is insert_all, not update_all -- update_all doesn't really make sense for a join table. We're adding values, not replacing them. (At least on PostgreSQL and SQLite, if there's a unique index on the join table, duplicates will be ignored -- on MySQL you might need to jump through some more hoops to exclude existing relationships, unless you're deleting those anyway as below.)
If you want to replace any existing associations, you'll need to delete those explicitly:
ActiveRecord::Base.transaction do
GroupsUser.delete_all(user_id: user_ids)
# and/or GroupsUser.delete_all(group_id: group_ids), depending
GroupsUser.insert_all(values)
end
Also worth noting: there's no need to put GroupsUser in app/models/groups_user.rb; if this is the only code that uses it, you can put it right in the file where it's used. This is especially useful in migrations.
I want to search a table with multiple conditions in Rails. I am using Active record and rails version 3.1.0.
I have Movies object, and want to achieve the equivalent of the following in rails:
Select * from Movies where rating = 'R' OR rating = 'PG'
I tried the following but it does not work
#filtered = Movies.find(:all, :conditions => { :rating => 'R', :rating => 'PG' })
Can you please provide help to write an equivalent of SQL query mentioned above.
One way would be to build an "IN" condition with:
#filtered = Movie.where(:rating => ['R', 'PG']).all
EDIT: I changed your class to "Movie" from "Movies". I assume that's what you will want.
In Rail 4, find with multiple conditions for example consider find Profile with first_name and last_name
Profile.find_by first_name: 'stack', last_name: 'flow'
Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
If no record is found, returns nil
Profile.find_by! first_name: 'stack', last_name: 'flow'
Like find_by, except that if no record is found, raises an ActiveRecord::RecordNotFound error.
For more information read Rails Finder Method
1: http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find_byIn Rail 4, find with multiple conditions for example consider find Profile with first_name and last_name
Profile.find_by first_name: 'stack', last_name: 'flow'
Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
If no record is found, returns nil
Profile.find_by! first_name: 'stack', last_name: 'flow'
Like find_by, except that if no record is found, raises an ActiveRecord::RecordNotFound error.
For more information read Rails Finder Method
i guess that would be
Movie.where("rating = ? OR rating = ?", 'R', 'PG')
have a look at the guides for more info: http://guides.rubyonrails.org/active_record_querying.html#conditions
i would recommend using an IN statement instead.
You can do it using:
Movie.where(:rating => ['R','PG'])
There's a form on my site allowing to update an object (i.e. Book).
The book contains the field author_id which references the Author class.
The form allows changing the author.
When saving the book, validations are run,
but when a certain validation calls self.author, it will receive the previous author and not the new one that was chosen.
To deal with this problem, I always have to start the validations with reloading the new author in the following way:
def some_validation
author = Author.find(self.author_id)
...
end
Why won't the validation see the new author, and how can I make it see it,
without having to reload the new referenced object every time?
Thanks!
This is a problem you can duplicate in the console:
b = Book.new
=> #<Book id:nil, etc...>
b.author_id = 1
=> 1
b.author
=> #<Author id:1, etc...>
b.author_id = 2
=> 2
b.author
=> #<Author id:1, etc...>
so... changing the association ID (which is what the form update_attributes does) doesn't change the loaded associated object.
but, if you nullify the object first, the associated object does reload:
b.author = nil
=> nil
b.author_id = 2
=> 2
b.author
=> #<Author id:2, etc...>
So you can (note the italics, because I don't know what is the best solution) set the object to nil in the controller if the association id is in the params hash, or continue using your method, but add a guard to only reload if necessary
author = Author.find(self.author_id) if self.author_id_changed?
(sorry if that was a lot of rambling that essentially didn't answer your question ;-)
I have a method in rails that is doing something like this:
a = Foo.new("bar")
a.save
b = Foo.new("baz")
b.save
...
x = Foo.new("123", :parent_id => a.id)
x.save
...
z = Foo.new("zxy", :parent_id => b.id)
z.save
The problem is this takes longer and longer the more entities I add. I suspect this is because it has to hit the database for every record. Since they are nested, I know I can't save the children before the parents are saved, but I would like to save all of the parents at once, and then all of the children. It would be nice to do something like:
a = Foo.new("bar")
b = Foo.new("baz")
...
saveall(a,b,...)
x = Foo.new("123", :parent_id => a.id)
...
z = Foo.new("zxy", :parent_id => b.id)
saveall(x,...,z)
That would do it all in only two database hits. Is there an easy way to do this in rails, or am I stuck doing it one at a time?
Since you need to perform multiple inserts, database will be hit multiple times. The delay in your case is because each save is done in different DB transactions. You can reduce the latency by enclosing all your operations in one transaction.
class Foo
belongs_to :parent, :class_name => "Foo"
has_many :children, :class_name => "Foo", :foreign_key=> "parent_id"
end
Your save method might look like this:
# build the parent and the children
a = Foo.new(:name => "bar")
a.children.build(:name => "123")
b = Foo.new("baz")
b.children.build(:name => "zxy")
#save parents and their children in one transaction
Foo.transaction do
a.save!
b.save!
end
The save call on the parent object saves the child objects.
You might try using Foo.create instead of Foo.new. Create "Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not."
You can create multiple objects like this:
# Create an Array of new objects
parents = Foo.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
Then, for each parent, you can also use create to add to its association:
parents.each do |parent|
parent.children.create (:child_name => 'abc')
end
I recommend reading both the ActiveRecord documentation and the Rails Guides on ActiveRecord query interface and ActiveRecord associations. The latter contains a guide of all the methods a class gains when you declare an association.
insert_all (Rails 6+)
Rails 6 introduced a new method insert_all, which inserts multiple records into the database in a single SQL INSERT statement.
Also, this method does not instantiate any models and does not call Active Record callbacks or validations.
So,
Foo.insert_all([
{ first_name: 'Jamie' },
{ first_name: 'Jeremy' }
])
it is significantly more efficient than
Foo.create([
{ first_name: 'Jamie' },
{ first_name: 'Jeremy' }
])
if all you want to do is to insert new records.
One of the two answers found somewhere else: by Beerlington.
Those two are your best bet for performance
I think your best bet performance-wise is going to be to use SQL, and bulk insert multiple rows per query. If you can build an INSERT statement that does something like:
INSERT INTO foos_bars (foo_id,bar_id) VALUES (1,1),(1,2),(1,3)....
You should be able to insert thousands of rows in a single query. I didn't try your mass_habtm method, but it seems like you could to something like:
bars = Bar.find_all_by_some_attribute(:a)
foo = Foo.create
values = bars.map {|bar| "(#{foo.id},#{bar.id})"}.join(",")
connection.execute("INSERT INTO foos_bars (foo_id, bar_id) VALUES
#{values}")
Also, if you are searching Bar by "some_attribute", make sure you have that field indexed in your database.
OR
You still might have a look at activerecord-import. It's right that it doesn't work without a model, but you could create a Model just for the import.
FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]]
Cheers
you need to use this gem "FastInserter" -> https://github.com/joinhandshake/fast_inserter
and inserting a large number and thousands of records is fast because this gem skips active record, and only uses a single sql raw query
You don't need a gem to hit DB fast and only once!
Jackrg has worked it out for us:
https://gist.github.com/jackrg/76ade1724bd816292e4e
I would like to know if it is possible to get the types (as known by AR - eg in the migration script and database) programmatically (I know the data exists in there somewhere).
For example, I can deal with all the attribute names:
ar.attribute_names.each { |name| puts name }
.attributes just returns a mapping of the names to their current values (eg no type info if the field isn't set).
Some places I have seen it with the type information:
in script/console, type the name of an AR entity:
>> Driver
=> Driver(id: integer, name: string, created_at: datetime, updated_at: datetime)
So clearly it knows the types. Also, there is .column_for_attribute, which takes an attr name and returns a column object - which has the type buried in the underlying database column object, but it doesn't appear to be a clean way to get it.
I would also be interested in if there is a way that is friendly for the new "ActiveModel" that is coming (rails3) and is decoupled from database specifics (but perhaps type info will not be part of it, I can't seem to find out if it is).
Thanks.
In Rails 3, for your model "Driver", you want Driver.columns_hash.
Driver.columns_hash["name"].type #returns :string
If you want to iterate through them, you'd do something like this:
Driver.columns_hash.each {|k,v| puts "#{k} => #{v.type}"}
which will output the following:
id => integer
name => string
created_at => datetime
updated_at => datetime
In Rails 5, you can do this independently of the Database. That's important if you use the new Attributes API to define (additional) attributes.
Getting all attributes from a model class:
pry> User.attribute_names
=> ["id",
"firstname",
"lastname",
"created_at",
"updated_at",
"email",...
Getting the type:
pry> User.type_for_attribute('email')
=> #<ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString:0x007ffbab107698
#limit=255,
#precision=nil,
#scale=nil>
That's sometimes more information than needed. There's a convenience function that maps all these types down to a core set (:integer, :string etc.)
> User.type_for_attribute('email').type
=> :string
You can also get all that data in one call with attribute_types which returns a 'name': type hash.
You can access the types of the columns by doing this:
#script/console
Driver.columns.each {|c| puts c.type}
If you want to get a list of all column types in a particular Model, you could do:
Driver.columns.map(&:type) #gets them all
Driver.columns.map(&:type).uniq #gets the unique ones
In rails 5 this will give you a list of all field names along with their data type:
Model_Name.attribute_names.each do |k| puts "#{k} = #{Model_Name.type_for_attribute(k).type}" end
Rails 5+ (works with virtual attributes as well):
Model.attribute_types['some_attribute'].type
This snippet will give you all the attributes of a model with the associated database data types in a hash. Just replace Post with your Active Record Model.
Post.attribute_names.map {|n| [n.to_sym,Post.type_for_attribute(n).type]}.to_h
Will return a hash like this.
=> {:id=>:integer, :title=>:string, :body=>:text, :created_at=>:datetime, :updated_at=>:datetime, :topic_id=>:integer, :user_id=>:integer}
Assuming Foobar is your Active Record model. You can also do:
attributes = Foobar.attribute_names.each_with_object({}) do |attribute_name, hash|
hash[attribute_name.to_sym] = Foobar.type_for_attribute(attribute_name).type
end
Works on Rails 4 too
In Rails 4 You would use Model.column_types.