Updating current value in ActiveRecord (Rails) - ruby-on-rails

in my application lots of objects are already preloaded by Rails.
Now I like to update some of these object attributes. Unfortunately some of these objects are related to the same object in my database. When I add a value to myObject.balance (myObject.balance += value), the attribute balance in differentButSameObject has still the same value.
One solution could be reloading the object. I would prefer to update the value like this:
UPDATE myTable SET balance = balance + 10 WHERE id = 1.
Is this possible?

You could use ActiveRecords update_all statement:
Object.where(:id => 1).update_all("balance = balance + 1")

You can add the following code to your model. It uses the update_counters which performs a proper query (that does COALESCE, for instance):
def inc(counter, by = 1)
raise "Cannot update column of a non-persisted model" unless persisted?
self.class.update_counters(id, counter => by)
increment(counter, by)
end

Related

Ruby on Rails Interating through associated collectionproxy and updating attribute

I have two models associated with each other.
For example an Order contains many Order Items.
If there is a match (ie: already an order item with the same sku), I'd like to increment an order item quantity. Is this the best way to do it in Ruby?
switch = false
order.order_items.each do |item|
if item.sku == test.sku
item.increment!(:quantity)
switch = true
end
end
I was originally trying to do something like:
if order.order_items.where(sku: test.sku).length > 0
order_item = order.order_items.where("sku = ?", test.sku).take
order_item.increment!(:quantity)
end
but I ended up having some errors.
Thanks
You can do it using one query without fetching the data and iterating through it, as below:
order.order_items.where(sku: test.sku).update_all("quantity = quantity + 1")
NB:
update_all skip the validation & callbacks

Dynamically adding to an existing key value pair in ruby

Ok, so 8 months into Ruby Hashes are still proving somewhat of an enigma.
I pull 10 records from the database, each with its own category field. Many of the records with share the same category, so I want them grouped by their categories in a hash.
I understand that the Key's are always unique, which is what makes a Hash a Hash. What I am struggling to do is add values to the existing key in the hash.
def self.categorise_events
hash = {}
self.limit(10).map do |event|
if hash.key?(event.event_type) #check if the key already exists
hash[event.event_type][event] #add the event record to that key
else
hash[event.event_type] = event #create the key if it doesn't exist and add the record
end
end
hash
end
This is more of a gist of what I am trying to achieve. I've had other compositions which have been closer but still not quite doing it.
You can add to an existing hash like
hash[key] = value
but in your case, your value is a collection of values, so it's an array
hash[key] = []
hash[key] << value
so you can add to an existing group with
unless hash.key?(event.event_type)
hash[event.event_type] = []
end
hash[event.event_type] << event
Now, this can be accomplished with the builtin method #group_by as seen on the documentations. But in your case, because it's using ActiveRecord, you can consider using #group to group records using SQL and greatly improving the performance of the grouping
self.limit(10).group(:event_type)
Check out the docs here.
Key is always uniq. So if you want to add values to a key, that value should be Array.
In this situation you can use group_by. Try
hash = self.limit(10).group_by{|e| e.event_type}
It returns a Hash whose keys are event_type and values are Array of records.

How to safely override save method in rails?

I have a RoR app (in development). I have models based on sql views.
However, I would like to create & update thoses models. They are stored in database through 2 tables (generic design pattern, that's why I use sql views).
I've heard about hooks such as before_save, but as mentioned here there is still issues saving or updating other objects in before_save callback.
So, I am wonderring how to safely override save method in rails ?
Any suggestions are welcomed.
Thanks
EDIT :
sql of my view
CREATE VIEW my_objects AS
SELECT o.* ,
at.value as "column1",
FROM
Generic_object o
LEFT JOIN (SELECT at.* FROM Another_table at ON at.genreic_object_id = o.id AND at.name = "param1" )
This lead to a "reconstructed" object from my generic table and some column from another table :
o.col1, o.col2, ..., at.param1
If my object have several parameters I have to JOIN the another table again, like this
LEFT JOIN ( SELECT at2.* FROM Another_table at2 ON at2.generic_object_id = o.id AND at2.name = "param2")
And then, i get this object :
o.col1, o.col2, ..., at.param1, at2.param2
It seems rare (weird? :p), but this pattern is my constraint.... :(
From my experience I rarely find such need to I override common methods. And I don't recommend so because there will be other code depends on the original method. Your problem must have another better solution.
However, if you really have to, you can safely override it by Ruby's alias_method, or alias_method_chain provided by ActivitySupport.
class Foo < ActiveRecord::Base
alias_method_chain :save, :my_feature
def save
blah_blah
save_without_my_feature # The original save method
end
end
def save
# new object ?
# yes
q = Array.new
qualifiers.each do |qk|
q.push(Another_table.create(value: self.read_attribute(qk),name: qk))
end
e = Generic_object.create(name: name,type: self.class,qualifiers: q)
# existing object
#bla bla
end

Rails checking if a record exists in database

What is the most efficient of way of checking if a database will return a record before processing it. Example: Truck.where("id = ?", id).select('truck_no').first.truck_no
This may or may not return a truck if the truck exists. What is the most efficient way for me to ensure the page will not crash when processing this request. How would I handle this both in the view and the controller if lets say I was using a loop to go through each truck and print out its number.
If the record does not exist I would like to be able to print out a message instead saying no records found.
If you want to check for the existence of an object why not use exists?
if Truck.exists?(10)
# your truck exists in the database
else
# the truck doesn't exist
end
The exists? method has the advantage that is not selecting the record from the database (meaning is faster than selecting the record).
The query looks like:
SELECT 1 FROM trucks where trucks.id = 10
You can find more examples in the Rails documentation for #exists?.
Here is how you can check this.
if Trucks.where(:id => current_truck.id).blank?
# no truck record for this id
else
# at least 1 record for this truck
end
where method returns an ActiveRecord::Relation object (acts like an array which contains the results of the where), it can be empty but never be nil.
OP actual use case solution
The simplest solution is to combine your DB check and retrieval of data into 1 DB query instead of having separate DB calls. Your sample code is close and conveys your intent, but it's a little off in your actual syntax.
If you simple do Truck.where("id = ?", id).select('truck_no').first.truck_no and this record does NOT exists, it will throw a nil error when you call truck_no because first may retrieve a nil record if none are found that match your criteria.
That's because your query will return an array of objects that match your criteria, then you do a first on that array which (if no matching records are found) is nil.
A fairly clean solution:
# Note: using Rails 4 / Ruby 2 syntax
first_truck = Truck.select(:truck_no).find_by(id) # => <Truck id: nil, truck_no: "123"> OR nil if no record matches criteria
if first_truck
truck_number = first_truck.truck_no
# do some processing...
else
# record does not exist with that criteria
end
I recommend using clean syntax that "comments" itself so others know exactly what you're trying to do.
If you really want to go the extra mile, you could add a method to your Truck class that does this for you and conveys your intent:
# truck.rb model
class Truck < ActiveRecord::Base
def self.truck_number_if_exists(record_id)
record = Truck.select(:truck_no).find_by(record_id)
if record
record.truck_no
else
nil # explicit nil so other developers know exactly what's going on
end
end
end
Then you would call it like so:
if truck_number = Truck.truck_number_if_exists(id)
# do processing because record exists and you have the value
else
# no matching criteria
end
The ActiveRecord.find_by method will retrieve the first record that matches your criteria or else returns nil if no record is found with that criteria. Note that the order of the find_by and where methods is important; you must call the select on the Truck model. This is because when you call the where method you're actually returning an ActiveRelation object which is not what you're looking for here.
See ActiveRecord API for 'find_by' method
General solutions using 'exists?' method
As some of the other contributors have already mentioned, the exists? method is engineered specifically to check for the existence of something. It doesn't return the value, just confirms that the DB has a record that matches some criteria.
It is useful if you need to verify uniqueness or accuracy of some piece of data. The nice part is that it allows you to use the ActiveRelation(Record?) where(...) criteria.
For instance, if you have a User model with an email attribute and you need to check if an email already exists in the dB:
User.exists?(email: "test#test.com")
The benefit of using exists? is that the SQL query run is
SELECT 1 AS one FROM "users" WHERE "users"."email" = 'test#test.com' LIMIT 1
which is more efficient than actually returning data.
If you need to actually conditionally retrieve data from the DB this isn't the method to use. However, it works great for simple checking and the syntax is very clear so other developers know exactly what you're doing. Using appropriate syntax is critical in projects with multiple developers. Write clean code and let the code "comment" itself.
If you just want to check whether the record exists or not. Go with the #cristian's answer i.e.
Truck.exists?(truck_id) # returns true or false
But if truck exists and you want to access that truck then you will have to find truck again which will lead to two database queries. If this is the case go with
#truck = Truck.find_by(id: truck_id) #returns nil or truck
#truck.nil? #returns true if no truck in db
#truck.present? #returns true if no truck in db
You could just do:
#truck_no = Truck.where("id = ?", id).pluck(:truck_no).first
This will return nil if no record is found, or truck_no of only the first record otherwise.
Then in your view you could just do something like:
<%= #truck_no || "There are no truck numbers" %>
If you want to fetch and display multiple results, then in your controller:
#truck_nos = Truck.where("id = ?", id).pluck(:truck_no)
and in your view:
<% truck_nos.each do |truck_no| %>
<%= truck_no %>
<% end %>
<%= "No truck numbers to iterate" if truck_nos.blank? %>
Rails has a persisted? method
for using like you want

How to set a counter cache value to a given one?

I am using Ruby on Rails 3.2.2 and I would like to set a counter cache value to a "custom" one. That is, at this time (in my migration file) I am trying to use the following code:
def up
add_column :articles, :comments_count, :integer, :default => 0
Article.reset_column_information
Article.find_each do |article|
# Note: The following code doesn't work (when I migrate the database it
# raises the error "comments_count is marked as readonly").
Article.update_column(:comments_count, article.custom_comments.count)
end
end
In other words, I would like to set the :comments_count value (a counter cache database table column) to a custom value (in my case that value is article.custom_comments.count - note: the custom_comments is not an ActiveRecord Association but a method stated in the Article model class; it returns an integer value as well) that is not related to a has_many associations.
Maybe, I could / should use something like
Article.reset_column_information
Article.find_each do |article|
Article.reset_counters(article.id, ...)
end
but it seems that the reset_counters method cannot work without has_many associations.
How can I set the :comments_count counter cache value to a given value that is related to a "custom association"?
The accept answer includes the iterating method, which is wrong for existing values of comment_count other than 0: update_counter sets the counter relative to it's current values. To set an absolute value, do:
Article.update_counters(article.id, comments_count: comments.count - article.comments_count)
If you have to fetch each row's correct count anyway, you can also more easily use Article.reset_counters(article.id, :comments)
To do it with far fewer queries, use this:
Author
.joins(:books)
.select("authors.id, authors.books_count, count(books.id) as count")
.group("authors.id")
.having("authors.books_count != count(books.id)")
.pluck(:id, :books_count, "count(books.id)")
.each_with_index do |(author_id, old_count, fixed_count), index|
puts "at index %7i: fixed author id %7i, new books_count %4i, previous count %4i" % [index, author_id, fixed_count, old_count] if index % 1000 == 0
Author.update_counters(author_id, books_count: fixed_count - old_count)
end
You describe comments_count as a counter cache, yet a counter cache is strictly defined as the number of associated records in a has_many relationship, which you say this isn't.
If the only way to get the value you want is via method on Article, then you're going to have to iterate over all your Article objects and update each one.
Article.find_each do |article|
article.update_attribute(:comments_count, article.custom_comments.count)
end
This is pretty inefficient, since it's loading and saving every object.
If the definition of custom_comments (which you don't actually explain) is something you can express in SQL, it would undoubtedly be faster to do this update in the database. Which might look something like this:
CREATE TEMP TABLE custom_comment_counts_temp AS
SELECT articles.id as id, count(comments.id) as custom_comments
FROM articles
LEFT JOIN comments ON articles.id = comments.article_id
WHERE <whatever condition indicates custom comments>
GROUP BY articles.id;
CREATE INDEX ON custom_comments_counts_temp(id);
UPDATE articles SET comments_count = (SELECT custom_comments FROM custom_comment_counts_temp WHERE custom_comment_counts_temp.id = articles.id);
DROP TABLE custom_comment_counts_temp;
(this assumes postgresql - if you're using mySQL or some other database, it may look different. If you're not using a relational database at all, it may not be possible)
Additionally, since it's not a counter cache according to Rails' fairly narrow definition, you'll need to write some callbacks that keep these values updated - probably an after_save callback on comment, something like this:
comment.rb:
after_save :set_article_custom_comments
def set_article_custom_comments
a = self.article
a.update_attribute(:comments_count, a.custom_comments.count)
end

Resources