I have a situation where a set of items, known to be in the database, is recovered from a serialised json array, including their ids, so they can be reshaped to ActiveRecord model instances just calling new, without access to the database:
for itm in a do
item = Item.new(itm)
itmlist << item
emd
Now, the problem is, how to tell ActiveRecord that these elements are already persisted and not new? If item.new_record? is true, a item.save will fail because Rails will insert instead of update.
The goal is to make sure that Rails does update, without any extra queries to the database. The closest thing I have got is
item = Item.new(itm)
item.instance_variable_set(:#new_record, false)
with plays with ActiveRecord Internals
Not sure I completely understand the question but if you just want to update all the items the following will work
a.each do |item_hash|
Item.find(item_hash["id"]).update(item_hash.except("id"))
end
If the Item may or may not exist then
a.each do |item|
item = Item.find(item_hash["id"]) || Item.new
item.update(item_hash.except("id"))
end
Neither one of these options will handle validation failures. Depending on your usage the following could be useful
all_items = a.map do |item_hash|
item = Item.find(item_hash["id"]) || Item.new
item.assign_attributes(item_hash.except("id"))
end
pass,fail = all_items.partition(&:save)
If you only care about the failures you can change this to: fail = all_items.reject(&:save)
If there are a substantial number of items there are more performant alternatives as well that avoid so many queries. e.g. Item.where(id: a.map {|i| i["id"]})
Apparently, reload-ing works:
thing = Thing.last
thing_attributes = thing.attributes
same_thing = Thing.new(thing_attributes)
same_thing.new_record? # => true
same_thing.reload
same_thing.new_record? # => false
From the question, I see that your concern is only about ActiveRecord performing an INSERT query instead of the intended UPDATE, so reloading shouldn't be a problem. But, if my guess is wrong and you don't even want to reload, then it might be difficult without fiddling with the internals of ActiveRecord since it doesn't provide any way to instantiate already persisted records.
Possible alternate solution
Pardon me if the solution won't work in your case, but instead of serialising the entire objects, just serialise an array of IDs. So that you can re-fetch them in one go:
Thing.where(id: the_array_of_ids)
Related
I know that find_each has been designed to consume smaller memory than each.
I found some code that other people wrote long ago. and I think that it's wrong.
Think about this codes.
users = User.where(:active => false) # What this line does actually? Nothing?
users.find_each do |user|
# update or do something..
user.update(:do_something => "yes")
end
in this case, It will store all user objects to the users variable. so we already populated the full amount of memory space. There is no point using find_each later on.
Am I correct?
so in other words, If you want to use find_each, you always need to use it with ActiveRecord::Relation object. Like this.
User.where(:active => false).find_each do |user|
# do something...
end
What do you think guys?
Update
in users = User.where(:active => false) line,
Some developer insists that rails never execute query unless we don't do anything with that variable.
What if we have a class with initialize method that has query?
class Test
def initialize
#users = User.where(:active => true)
end
def do_something
#user.find_each do |user|
# do something really..
end
end
end
If we call Test.new, what would happen? Nothing will happen?
users = User.where(:active => false) doesn't run a query against the database and it doesn't return an array with all inactive users. Instead, where returns an ActiveRecord::Relation. Such a relation basically describes a database query that hasn't run yet. The defined query is only run against the database when the actual records are needed. This happens for example when you run one of the following methods on that relation: find, to_a, count, each, and many others.
That means the change you did isn't a huge improvement, because it doesn't change went and how the database is queried.
But IMHO that your code is still slightly better because when you do not plan to reuse the relation then why assign it to a variable in the first place.
users = User.where(:active => false)
users.find_each do |user|
User.where(:active => false).find_each do |user|
Those do the same thing.
The only difference is the first one stores the ActiveRecord::Relation object in users before calling #find_each on it.
This isn't a Rails thing, it applies to all of Ruby. It's method chaining common to most object-oriented languages.
array = Call.some_method
array.each{ |item| do_something(item) }
Call.some_method.each{ |item| do_something(item) }
Again, same thing. The only difference is in the first the intermediate array will persist, whereas in the second the array will be built and then eventually deallocated.
If we call Test.new, what would happen? Nothing will happen?
Exactly. Rails will make an ActiveRecord::Relation and it will defer actually contacting the database until you actually do a query.
This lets you chain queries together.
#inactive_users = User.where(active: false).order(name: :asc)
Later you can to the query
# Inactive users whose favorite color is green ordered by name.
#inactive_users.where(favorite_color: :green).find_each do |user|
...
end
No query is made until find_each is called.
In general, pass around relations rather than arrays of records. Relations are more flexible and if it's never used there's no cost.
find_each is special in that it works in batches to avoid consuming too much memory on large tables.
A common mistake is to write this:
User.where(:active => false).each do |user|
Or worse:
User.all.each do |user|
Calling each on an ActiveRecord::Relation will pull all the results into memory before iterating. This is bad for large tables.
find_each will load the results in batches of 1000 to avoid using too much memory. It hides this batching from you.
There are other methods which work in batches, see ActiveRecord::Batches.
For more see the Rails Style Guide and use rubocop-rails to scan your code for issues and make suggestions and corrections.
I have a simple ActiveRecord query along the lines of this:
similar_changes = Notification.where(change_owner: 'foo1', change_target: 'foo2', change_cancelled: false)
Each notification object has a field change_type and I have another function that checks one Notification's change_type with one other Notification for inverse changes (changes that undo each other in the context of my application).
I need to take this Notification's change_type and compare it against all others in the array. I have to reference the objects like so: similar_changes[0]['change_type'] where the first index is each ActiveRecord in the array and the second is the dictionary that specifies which property in the Notification object.
I have a feeling I could do this manually with two nested loops and if statements, but I also know Ruby and I feel like this is something that it should have built in.
Am I wrong, or is there a better way to do this?
Here is the code (note all this code isn't quite finished so bear with me if it's not perfect):
def self.group_similar_changes(owner, target, change_type)
# long query where it selects all rows where change_owner and change_target
# are the same as original
# also where cancelled is false
# determine if cancelled (yaml)
# if cancelled (do nothing)
similar_changes = Notification.where(
change_owner: owner,
change_target: target,
change_cancelled: false
)
similar_changes.each do |change|
cancel_inverse_change(change, change.change_type)
if change.cancelled?
similar_changes.delete(change)
end
end
end
end
def cancel_inverse_change(change, change_type)
if change.inverse?(change_type)
change.cancel
end
end
def inverse?(possible_inverse_change)
is_inverse = false
change_types = YAML.load_file(File.join(NotificationManager::Engine.root, 'config/change_types.yaml'))
if self.change_type == change_types[possible_inverse_change]['inverse']
is_inverse = true
end
return is_inverse
end
Yes, your loop over similar_changes can be improved.
It's confusing to modify the array you're looping over. I don't even know if it's reliable, because I never do it!
It's also not idiomatic Ruby to rely on the return value of each. each is normally used to do something to the elements of an Enumerable that already exists, so using its return value seems strange.
I'd write it as
similar_changes.reject do |change|
cancel_inverse_change(change, change.change)
change.cancelled?
end
How can i remove the last element from an ActiveRecord_Relation in rails?
e.g. if I set:
#drivers = Driver.all
I can add a another Driver object called #new_driver to #drivers by doing:
#drivers << #new_driver
But how can I remove an object from #drivers?
The delete method doesn't seem to work, i.e.
#drivers.delete(0)
You can use the reject! method, this will remove the object from the collection without affecting the db
for example:
driver_to_delete = #driver.first # you need the object that you want removed
#drivers.reject!{|driver| driver == driver_to_delete}
Very late too, but I arrived here looking for a fast answer and finished by thinking by myself ;)
Just to clarify about the different answers and the Rails 6.1 comment on accepted answer:
The OP wanted to remove one entry from a query, but NOT remove it from database, so any answer with delete or destroy is just wrong (this WILL delete data from your database !!).
In Ruby (and therefore Rails) convention, shebang methods (ending with !) tend to alter the given parameter. So reject! would imply modifying the source list ... but an ActiveRecord_Relation is basically just a query, NOT an array of entries !
So you'd have 2 options:
Write your query differently to specifically say you don't want some id:
#drivers.where.not(id: #driver_to_remove) # This still is an ActiveRecord_Relation
Use reject (NO shebang) on your query to transform it into an Array and "manually" remove the entry you don't want:
#drivers.reject{ |driver| driver == #driver_to_remove}
# The `reject` forces the execution of the query in DB and returns an Array)
On a performance point of view, I would personally recommend the first solution as it would be just a little more complex against the DB where the latter implies looping on the whole (eventually large) array.
Late to the question, but just had the same issue and hope this helps someone else.
reject!did not work for ActiveRecord_Relation in Rails 4.2
drop(1) was the solution
In this case #drivers.drop(0) would work to drop the first element of the relation
Since its an array of objects, have you tried to write something like #drivers.delete(#new_driver) or #drivers.delete(id: #new_driver.id) ?
This is the documentation you need:
#group.avatars << Avatar.new
#group.avatars.delete(#group.avatars.last)
--
.destroy
The problem you've got is you're trying to use collection methods on a non-collection object. You'll need to use the .destroy ActiveRecord method to get rid of the record from the database (and consequently the collection):
#drivers = Driver.all
#drivers.last.destroy
--
Scope
.delete will remove the record from the DB
If you want to pull specific elements from the db to populate the #drivers object, you'll need to use a scope:
#app/models/driver.rb
Class Driver < ActiveRecord::Base
scope :your_scope, -> { where column: "value" }
end
This will allow you to call:
#app/controllers/drivers_controller.rb
def index
#drivers = Driver.your_scope
end
I think you're getting the MVC programming pattern confused - data manipulation is meant to happen in the model, not the controller
As stated above, reject! doesn't work in Rails 4.2, but delete does, so #drivers.delete(#new_driver) works, and more generally:
#drivers.delete(Driver.where(your condition))
I'm working on an audit trail of sorts for an app so that the user can see what is being changed throughout the system.
I have a hash of changes from ActiveRecord Dirty, like follows:
{"ingredient_type_id"=>[nil, 199575006], "name"=>[nil, "asdfg"], "amount"=>[nil, 3.0], "unit"=>[nil, "x"], "notes"=>[nil, "asdf"]}
This works great and I can parse what I need to output and create database records with the info.
I just have one question - How can I get associated objects from this? In this case, the ingredient_type? I actually want to output something like:
"Ingredient type was changed to #{IngredientType.find(199575006).name}."
But I'm not sure how I would parse that hash on a dynamic basis to do that.
Pretty much the way you've suggested I'd have thought, But you don't need to parse the hash for the changes, Dirty gives you much more than that
if ingredient_type_id_changed?
unless ingredient_type_id.blank?
ingredient_name = IngredientType.find(ingredient_type_id).name
else
ingredient_name = 'blank'
end
end
You might even be able to do ingredient_type.name, Not sure at that point if active record dirty will let you go through the association. If you test it (or if anyone else knows) let me know
I have a simple find statement as such:
m = MyModel.find(1, :include => :my_children)
With m.mychildren being an Array; is there anyway to find a particular record from within the array without having to iterate over the entire thing. If I do mychildren.find(1), a new DB query is issues, which doesn't make sense, since they are all loaded already
It looks like there's a little Rails magic going on here. Where Enumerable#find is being overridden by ActiveRecord::Base#find on methods created for associations.
On the upside Enumerable#find is aliased to Enumerable#detect.
Unfortunately Enumerable#find/Enumerable#detect have significantly different syntax from ActiveRecord::Base#find.
So you can't just do mychildren.find(1), instead you've got to do mychildren.detect{|c| c.id == 1} if you want to avoid hitting the database again. You may also want to consider extending Array for a more DRY way of doing this.
class Array
def id_find id
self.detect{|element| element.id == id}
end
end
I'm not quite sure what your asking, but have you tried select:
m.mychildren.select{ |child| child == <<some_statement>> }
This won't hit the database assuming you've used the :include option as you stated in your question.
Alternatively, if you know the number of the child you want, you should be able to just use
m.mychildren[1]