in my controller I have following sequence of commands:
SAVE DATA INTO FIRST TABLE
_get ID of inserted item into table from first step_
SAVE DATA INTO SECOND TABLE WITH ID FROM FIRST COMMAND
if FIRST.save && SECOND.save
do something
And I am wondering, how to get id of item, which is immediately inserted into database... I tried to googling, but I can't find this information...
Thanks in advance for your hints
# SAVE DATA INTO FIRST TABLE
first_instance = FirstModel.new( :foo => :bar )
first_save = first_instance.save
# _get ID of inserted item into table from first step_
first_instance_id = first_instance.id
# SAVE DATA INTO SECOND TABLE WITH ID FROM FIRST COMMAND
second_save = SecondModel.new( :first_model_id => first_instance_id ).save
if first_save && second_save
# do something
end
After saving a model, you can access it's id variable:
#user = User.new
puts #user.id
# => nil
#user.save
puts #user.id
# => 1
Could you just search your database by the updated_at field in your model?
To get the most recent record:
#model1 = Model1.order("updated_at DESC").limit(1)
or better yet, upon saving Model1 in the first place:
#model1 = model1.save
To assign:
#model2.model1_id = #model1.id
Note: if you actually want to save the ID of a specific record, finding the last isn't the best way to go.
This is because another record could be inserted by a different user, right after you inserted Model1 and right before you call Model2.
If you want the two to save together or not at all, you can look into transactions: http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
If you're happy with Model1 saving on its own before worrying about Model2, then simply assign the variables as I did above.
Related
I'd like to update a table with Django - something like this in raw SQL:
update tbl_name set name = 'foo' where name = 'bar'
My first result is something like this - but that's nasty, isn't it?
list = ModelClass.objects.filter(name = 'bar')
for obj in list:
obj.name = 'foo'
obj.save()
Is there a more elegant way?
Update:
Django 2.2 version now has a bulk_update.
Old answer:
Refer to the following django documentation section
Updating multiple objects at once
In short you should be able to use:
ModelClass.objects.filter(name='bar').update(name="foo")
You can also use F objects to do things like incrementing rows:
from django.db.models import F
Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
See the documentation.
However, note that:
This won't use ModelClass.save method (so if you have some logic inside it won't be triggered).
No django signals will be emitted.
You can't perform an .update() on a sliced QuerySet, it must be on an original QuerySet so you'll need to lean on the .filter() and .exclude() methods.
Consider using django-bulk-update found here on GitHub.
Install: pip install django-bulk-update
Implement: (code taken directly from projects ReadMe file)
from bulk_update.helper import bulk_update
random_names = ['Walter', 'The Dude', 'Donny', 'Jesus']
people = Person.objects.all()
for person in people:
r = random.randrange(4)
person.name = random_names[r]
bulk_update(people) # updates all columns using the default db
Update: As Marc points out in the comments this is not suitable for updating thousands of rows at once. Though it is suitable for smaller batches 10's to 100's. The size of the batch that is right for you depends on your CPU and query complexity. This tool is more like a wheel barrow than a dump truck.
Django 2.2 version now has a bulk_update method (release notes).
https://docs.djangoproject.com/en/stable/ref/models/querysets/#bulk-update
Example:
# get a pk: record dictionary of existing records
updates = YourModel.objects.filter(...).in_bulk()
....
# do something with the updates dict
....
if hasattr(YourModel.objects, 'bulk_update') and updates:
# Use the new method
YourModel.objects.bulk_update(updates.values(), [list the fields to update], batch_size=100)
else:
# The old & slow way
with transaction.atomic():
for obj in updates.values():
obj.save(update_fields=[list the fields to update])
If you want to set the same value on a collection of rows, you can use the update() method combined with any query term to update all rows in one query:
some_list = ModelClass.objects.filter(some condition).values('id')
ModelClass.objects.filter(pk__in=some_list).update(foo=bar)
If you want to update a collection of rows with different values depending on some condition, you can in best case batch the updates according to values. Let's say you have 1000 rows where you want to set a column to one of X values, then you could prepare the batches beforehand and then only run X update-queries (each essentially having the form of the first example above) + the initial SELECT-query.
If every row requires a unique value there is no way to avoid one query per update. Perhaps look into other architectures like CQRS/Event sourcing if you need performance in this latter case.
Here is a useful content which i found in internet regarding the above question
https://www.sankalpjonna.com/learn-django/running-a-bulk-update-with-django
The inefficient way
model_qs= ModelClass.objects.filter(name = 'bar')
for obj in model_qs:
obj.name = 'foo'
obj.save()
The efficient way
ModelClass.objects.filter(name = 'bar').update(name="foo") # for single value 'foo' or add loop
Using bulk_update
update_list = []
model_qs= ModelClass.objects.filter(name = 'bar')
for model_obj in model_qs:
model_obj.name = "foo" # Or what ever the value is for simplicty im providing foo only
update_list.append(model_obj)
ModelClass.objects.bulk_update(update_list,['name'])
Using an atomic transaction
from django.db import transaction
with transaction.atomic():
model_qs = ModelClass.objects.filter(name = 'bar')
for obj in model_qs:
ModelClass.objects.filter(name = 'bar').update(name="foo")
Any Up Votes ? Thanks in advance : Thank you for keep an attention ;)
To update with same value we can simply use this
ModelClass.objects.filter(name = 'bar').update(name='foo')
To update with different values
ob_list = ModelClass.objects.filter(name = 'bar')
obj_to_be_update = []
for obj in obj_list:
obj.name = "Dear "+obj.name
obj_to_be_update.append(obj)
ModelClass.objects.bulk_update(obj_to_be_update, ['name'], batch_size=1000)
It won't trigger save signal every time instead we keep all the objects to be updated on the list and trigger update signal at once.
IT returns number of objects are updated in table.
update_counts = ModelClass.objects.filter(name='bar').update(name="foo")
You can refer this link to get more information on bulk update and create.
Bulk update and Create
I have a User model that has all the queried fields with the existing data in the database. When I execute the following query-
#user = User.find(4, :select => 'user_fname, user_lname')
Rails throws the following error for the above line
Couldn't find all Users with 'user_id': (4, {:select=>"user_fname, user_lname"}) (found 1 results, but was looking for 2)
What's going wrong?
You can try this. I hope this will help.
#user = User.where("id = ?", 4).select( "user_fname, user_lname")
Rails 4 : use pluck as a shortcut to select one or more attributes without loading a bunch of records just to grab the attributes you want.
Try:
> User.where(id: 4).pluck(:user_fname , :user_lname).first
#=> ["John", "Smith"] # this is just sample of output
You are using #find incorrectly. It takes IDs as arguments, not SQL. It's trying to use that second argument as an ID, which clearly won't work.
http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find
find only finds records by id. You can pass in an array of id's but it doesn't take any options. So it thinks the hash you are passing is an id, and it bombs because it can't find a record with that ID. I think what you want is something like:
#user = User.find(4)
fname = #user.fname
lname = #user.lname
How can I achieve looping through a database table's values for a specific column within a model?
Give a model Hub.rb, here is a method I have:
# Methods
def self.update_feed(feed_url)
feed = Feedzirra::Feed.fetch_and_parse(feed_url)
# Updates only the new articles from last run
updated_feed = Feedzirra::Feed.update(feed)
add_entries(updated_feed.new_entries) if updated_feed.updated?
end
I then have a table in my database called "feeds" which has a schema of: id, name, description, url.
What I want to do is loop through each row in the "url" field from the feeds table and do it in the Hub.rb model. It will be an outer wrapper of the function shown above and will pass the feeds.url field from the feeds table into the "feed_url" argument in the Hub.rb method.
Something like this:
# Methods
def self.update_feed
loop do
feed_url = LOOP THROUGH URL COLUMN DATA
feed = Feedzirra::Feed.fetch_and_parse(feed_url)
# Updates only the new articles from last run
updated_feed = Feedzirra::Feed.update(feed)
add_entries(updated_feed.new_entries) if updated_feed.updated?
end
end
This way I can just call Hub.update_feed from the terminal and it will go through all of the URLs I have listed in the feeds table.
How about:
Feed.where("url IS NOT NULL").each do |f|
Hub.update_feed(f.url)
end
(Using jruby-1.6.8, Rails 2.3.14)
I have a Ruby object, experiment, I'm trying to use to update an existing row in a mysql database with .save!, and even though save! and valid? both return true on the object, the db row is unchanged after doing:
>> result = experiment.save!
=> true
Has anybody ever run into this problem? Also, it works fine in development; production is where the problem is occurring. How can that break it? There is no error to indicate that the row was not updated by save!. The only thing that changes is that the row's updated_at column gets a later time written to it.
I added code later on that grabs the object's attributes individually and does an sql UPDATE with those. This works, but I want to get to the root of the save! problem:
sql = "UPDATE experiment SET attr1 = #{experiment.attr1}, attr2 = #{experiment.attr2} WHERE experimentID = #{experiment.experimentID}"
Experiment.connection.execute(sql)
Here is what the experiment object looks like:
>> puts "#{experiment.inspect}"
=> #<Experiment experimentID: 177, attr1: 13, attr2: 13, attr3: nil ... >
SELECT output of the database row before doing save!:
>> test_sql = "SELECT * FROM experiment WHERE experimentID = #{experiment.experimentID}"
>> sql_hash = Experiment.connection.execute(test_sql)[0]
>> puts "before save: SQL_TEXT: #{sql_hash.inspect}"
=> before save: SQL_TEXT: {"experimentID"=>177, "attr1"=>0, "attr2"=>0, "attr3"=>"", ... }
Then, experiment.save! should update attr1 and attr2 to 13. However, they are still 0.
Here's some of the applicable class declaration:
class Experiment < ActiveRecord::Base
set_table_name :experiment
set_primary_key :experimentID
validates_presence_of :attr1, :attr2
...
end
EDIT: Per commenter's suggestion, I tried setting production logger level to debug and looking at the actual SQL being queried by experiment.save!:
Experiment Update (0.0ms) UPDATE `experiment` SET `updated_at` = '2013-09-02 19:56:11' WHERE `experimentID` = 178
Why would save! choose to not update any of the attributes???
Please let me know if there are any other details I can provide.
Active Record tracks which columns have changed and only saves these to the database. This change tracking is not all knowing, for example
user.name.gsub!(...)
wouldn't be detected as a change prior to rails 4.2.
There are 2 ways around this. One is never to change any Active Record object attribute in place. If you can't do this then you must tell Active Record what you have done, for example
user.name.gsub!(...)
user.name_will_change!
lets Active Record know that the name attribute has changed
I don't know if I'm just looking in the wrong places here or what, but does active record have a method for retrieving a random object?
Something like?
#user = User.random
Or... well since that method doesn't exist is there some amazing "Rails Way" of doing this, I always seem to be to verbose. I'm using mysql as well.
Most of the examples I've seen that do this end up counting the rows in the table, then generating a random number to choose one. This is because alternatives such as RAND() are inefficient in that they actually get every row and assign them a random number, or so I've read (and are database specific I think).
You can add a method like the one I found here.
module ActiveRecord
class Base
def self.random
if (c = count) != 0
find(:first, :offset =>rand(c))
end
end
end
end
This will make it so any Model you use has a method called random which works in the way I described above: generates a random number within the count of the rows in the table, then fetches the row associated with that random number. So basically, you're only doing one fetch which is what you probably prefer :)
You can also take a look at this rails plugin.
We found that offsets ran very slowly on MySql for a large table. Instead of using offset like:
model.find(:first, :offset =>rand(c))
...we found the following technique ran more than 10x faster (fixed off by 1):
max_id = Model.maximum("id")
min_id = Model.minimum("id")
id_range = max_id - min_id + 1
random_id = min_id + rand(id_range).to_i
Model.find(:first, :conditions => "id >= #{random_id}", :limit => 1, :order => "id")
Try using Array's sample method:
#user = User.all.sample(1)
In Rails 4 I would extend ActiveRecord::Relation:
class ActiveRecord::Relation
def random
offset(rand(count))
end
end
This way you can use scopes:
SomeModel.all.random.first # Return one random record
SomeModel.some_scope.another_scope.random.first
I'd use a named scope. Just throw this into your User model.
named_scope :random, :order=>'RAND()', :limit=>1
The random function isn't the same in each database though. SQLite and others use RANDOM() but you'll need to use RAND() for MySQL.
If you'd like to be able to grab more than one random row you can try this.
named_scope :random, lambda { |*args| { :order=>'RAND()', :limit=>args[0] || 1 } }
If you call User.random it will default to 1 but you can also call User.random(3) if you want more than one.
If you would need a random record but only within certain criteria you could use "random_where" from this code:
module ActiveRecord
class Base
def self.random
if (c = count) != 0
find(:first, :offset =>rand(c))
end
end
def self.random_where(*params)
if (c = where(*params).count) != 0
where(*params).find(:first, :offset =>rand(c))
end
end
end
end
For e.g :
#user = User.random_where("active = 1")
This function is very useful for displaying random products based on some additional criteria
Strongly Recommend this gem for random records, which is specially designed for table with lots of data rows:
https://github.com/haopingfan/quick_random_records
Simple Usage:
#user = User.random_records(1).take
All other answers perform badly with large database, except this gem:
quick_random_records only cost 4.6ms totally.
the accepted answer User.order('RAND()').limit(10) cost 733.0ms.
the offset approach cost 245.4ms totally.
the User.all.sample(10) approach cost 573.4ms.
Note: My table only has 120,000 users. The more records you have, the more enormous the difference of performance will be.
UPDATE:
Perform on table with 550,000 rows
Model.where(id: Model.pluck(:id).sample(10)) cost 1384.0ms
gem: quick_random_records only cost 6.4ms totally
Here is the best solution for getting random records from database.
RoR provide everything in ease of use.
For getting random records from DB use sample, below is the description for that with example.
Backport of Array#sample based on Marc-Andre Lafortune’s github.com/marcandre/backports/ Returns a random element or n random elements from the array. If the array is empty and n is nil, returns nil. If n is passed and its value is less than 0, it raises an ArgumentError exception. If the value of n is equal or greater than 0 it returns [].
[1,2,3,4,5,6].sample # => 4
[1,2,3,4,5,6].sample(3) # => [2, 4, 5]
[1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size
[].sample # => nil
[].sample(3) # => []
You can use condition with as per your requirement like below example.
User.where(active: true).sample(5)
it will return randomly 5 active user's from User table
For more help please visit : http://apidock.com/rails/Array/sample