Only delete from memory not from database - ruby-on-rails

I have an ActiveRecord array containing a list of shops.
shops = Shop.find(:all)
I want to delete a record from shops without deleteting it from the database.
shops.delete(a_shop) would result in a delete SQL query. I just want the shop to be deleted from the ActiveRecord array but not the database.
Can this be done?
Thanks

Beware if you are using has_many relationship the answer by JP will not work.
Extending the example:
class City < ActiveRecord::Base
has_many :shops
end
city = City.find(:name => "Portland")
then
city.shops.delete(city.shops[0])
Will delete from the DB!
Also
theshops = city.shops
theshops.delete(ss[0])
Will delete from the DB
One way to detach from the DB is to use compact or another array function like so:
theshops = city.shops.compact
theshops.delete(ss[0])
Will not delete from the DB
Also, in all cases delete_if Will not delete from the db:
city.shops.delete_if {|s| s.id == city.shops[0]}
Cheers!
Don't forget: If you are in doubt about these sort of things script/console is your friend!

When I did some testing, calling the delete method does not actually delete the element from the database. Only from the array.
shops = Shop.find(:all)
shops.delete(shops[0]) #deletes the first item from the array, not from the DB
To delete from the DB:
shops = Shop.find(:all)
Shop.delete(shops[0].id)
See the distinction? At least, this how it works on my Rails 2.1 setup.
-JP

I don't think you can remove the item from the array.
However, you should be able to modify your find to return the array without that shop initially. Something like:
shops = Shop.find(:all, :conditions => "id NOT #{a_shop.id}")
should be along the lines of what you want. That way you never get the shop in the container in the first place, but it doesn't affect the database.

I would like to suggest such way to delete AR object in memory.
Add an attribute to the appropriate model that is responsible for marking AR object as deleted (e.g. deleted attribute):
class OrderItem < ActiveRecord::Base
...
attr_accessor :deleted
def deleted
#deleted || 'no'
end
end
Mark the appropriate object as deleted:
o.order_items {|oi| oi.deleted = 'yes' if oi.id == 1029}
Filter order_items set only not deleted rows:
o.order_items do |oi|
unless oi.deleted == 'yes'
...
end
end

Related

How to validate uniqueness of a field without saving in Rails?

Suppose you have User has_many Books. Each book has a name field.
The user enters their books and it is submitted to the app as an array of names. The array of names will replace any existing books.
If the update fails, then the books should not be changed.
class Book
belongs_to :user
validates_uniquness_of :name, scope: [:user]
How to check the validity of each book without saving?
For example:
['Rails Guide', 'Javascript for Dummies'] would be valid.
['Javascript for Dummies', 'Javascript for Dummies'] would not be valid.
params[:books].each{| b | Book.new(b).valid? } will not work because the book has to be saves to get the uniqueness.
Mongoid
You can use an Active Record Transaction. Start the transaction, call save, and if it fails then the entire transaction will be rolled back. For example:
Book.transaction do
params[:books].each{ |b| Book.new(b).save! }
end
The entire transaction is aborted if there is an exception. You should handle this case by catching ActiveRecord::RecordInvalid.
You can use Array#map to convert array of books attributes to array of books names. Then use Array#uniq to remove duplicates from array of books names, and then check if resulting array has the same size as the original array of books attributes:
are_books_uniq = params[:books].map{|b| b[:name]}.uniq.size == params[:books].size
This way you can perform your check, without touching the database. But to be on a safe side, you should save all the books inside a transaction (see #Aaron's answer).
This turned out to be much more complicated than I imagined.
The solution I came up with looks like:
def update params
names = params.delete( :books )
new_books = names.map{| title | Book.new( name:name )}
validate_books_for new_books
return false if errors.present?
return false unless super( params )
self.books = new_books
self
end
Most of the complexity comes from the coupling of the 2 models. I can see why it is not a good idea to couple models. Perhaps a better design would be to store the books as an array.

How to rewrite this each loop in ruby?

I have this loop:
stations = Station.where(...)
stations.all.each do |s|
if s.city_id == city.id
show_stations << s
end
end
This works well, but because of looping the all the data, I think it's kinda slow. I've tried to rewrite it with using select, like this:
show_stations << stations.select { |station| station.city_id == city.id}
But the amount of saved data into show_stations is different compared to the each version and then, the data are in different format (array/object).
Is there any better/faster way to rewrite the loop version?
The fastest version of this maybe the built-in rails ActiveRecord method for finding associated objects.
So provided your Station model contains this:
class Station < ActiveRecord::Base
belongs_to :city
And your City model contains this:
class City < ActiveRecord::Base
has_many :stations
Then rails automatically generates the method city.stations which automatically fetches the stations which contain that city's id from the database. It should be pretty optimized.
If you want to make it even faster then you can add add_index :stations, :city_id to your table in a migration and it will retrieve faster. Note that this only saves time when you have a lot of stations to search through.
If you need to make it an array you can just convert it after with city.stations.to_a. And if you wanted to narrow it further, just use the select method and add the conditions that you wanted to previously add in your Station.where(...) statement.
(e.g. city.stations.to_a.select { |item| your_filter })
You should also cache the query results like
stations ||= Station.where("your where").where(:city_id => city.id)
Maybe you need to include into the where clause the city parameter:
stations = Station.where("your where").where(:city_id => city.id)
or the same
stations = Station.where("your where").where('city_id = ?', city.id)
Station appears to be an active record model. If that is the case, and you don't need all the stations, you can add the city.id filter to your where statement.
The issue you're having now is that you're adding the array returned from select as the last item of show_stations. If you want show_stations to only contain stations that match city.id then use show_stations = ... rather than show_stations << .... If you want show_stations to contain what it already contains plus the stations that match city.id then use show_stations + stations.select { |station| station.city_id == city.id }. (There are a number of other approaches for adding two arrays together.)

See if one person is before another in the alphabet, ruby, rails

I'm doing an app for a membership database.
Each person may have a partner. When it comes to displaying the list, I only want to have one row for each family, so at the moment I'm comparing first names and not displaying the row if the person's name is second. Like this
person.first_name != [person.first_name, person.partner.first_name].sort[0]
This means each family only gets displayed once, not twice - once for each partner.
And I'm doing this in the view.
There must be a better way of doing this, and it'd be really great if I could do it at the database level. I'm using postgresql if that makes a difference.
Edit
Sorry if it was unclear.
Say Person 1 has the first_name "Edward" and Person 2 has the first_name "Fay". Edward and Fay are married.
I only want to show them once in my list - I want a row to look like this
Surname First name Address etc
Mysurname Edward ....
Fay
I don't want to display it again with Fay first because I've got both Fay and Edward in list of people, so I use the ruby in the first part of the question to check if I should display the row - it compares their first names and only does the row if the person has a fist name that's before his/her partner's first name.
Here's the relevant part of my person model
class Person < ActiveRecord::Base
has_one :relationship_link, :foreign_key => :person_id, :dependent => :destroy, :include => :partner
has_one :partner, :through => :relationship_link, :source => :person_b, :class_name => "Person"
I hope that's clearer
You need to use DISTINCT ON or GROUP BY. In postgres you need to be careful to group by everything that you are selecting. If you only need to get the last names you can select("DISTINCT ON(last_name) last_name").pluck("last_name"). You will only get an array of last names though.
Maybe you can get records if you order by every other fields in your table, like this:
select("DISTINCT ON(people.last_name) people.*").order("people.last_name ASC, people.first_name ASC, people.field2 DESC, people.field3 ASC...")
You need to order by every attribute so the result is not ambigious.
For this case, i would create a data structure (a Hash) to store people instances given a specific surname. Something like this:
def build_surnames_hash(people_array)
surnames_hash = {}
people_array.each do |person|
last_name = person.last_name
surnames_hash[last_name] ||= []
surnames_hash[last_name] << person
end
surnames_hash
end
That way, you can iterate over the hash and display people using their surnames stored as hash's keys:
surnames_hash = build_surnames_hash(Person.all)
surnames_hash.each do |surname, person_instances_array|
# display the surname once
# iterate over person_instances_array displaying their properties
end

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

ActiveRecord query returns an incorrect model

I have been scratching my head over this one for a little while, and though I'm sure its a stupid mistake, I've reached the point where I must consult SO if I am to preserve the hair follicles I have left.
I've written a function in Rails (3.1.2) which should return an array populated with ActiveRecord model objects (users, in this case) which meet a certain criterion. The criterion is that the user's current list (denoted by the field active_list_id) must not be nil. The code follows:
def build_list_array
#lists = Array.new
User.all.each do |user|
#active_list_id = user.active_list_id
#lists<< List.find(#active_list_id) if #active_list_id != nil #TODO WHAT?!? WHY IS THIS RETURNING USERS?
end
end
As you can see, I'm initializing an empty array, cycling through all users and adding their active list to the array if the relevant reference on the user record is not nil. The problem is, this is returning user objects, not list objects.
Here are the associations from the user and list models:
user model:
has_many :lists
has_many :tasks
list model:
belongs_to :user
A brief word about the reference to active_list: A user can have many lists, but only one is active at any time. Therefore, I need to reference that list on the user record. The active list is not a foreign key in the typical sense, then.
I appreciate any help you can give me...Thanks =)
As it stands, your build_list_array will return an array of User because of the behavior of each. When iterating over a collection using each, the call to each returns the original collection.
For example,
list = []
# returns => []
[1,2,3,4,5].each { |number| list << number * 10 }
# returns => [1, 2, 3, 4, 5]
list
# returns => [10, 20, 30, 40, 50]
In your code, the last statement in your build_list_array method is the each call, meaning the return value of each is what is returned by the method. If you simply added a return statement at the end of the method you would be good to go.
def build_list_array
#lists = Array.new
User.all.each do |user|
#active_list_id = user.active_list_id
#lists<< List.find(#active_list_id) if #active_list_id
end
return #lists # Actually return #lists
end
That being said, you should probably use something like Bradley's answer as a basis for more "correct" Rails code.
each always returns the collection it iterates on (no matter what happens inside the block). Sounds like you want to return #lists at the end of your method.
You seem to be making a curious use of instance variables. You could also fetch this in one query via a join, something along the lines of
List.joins('inner join users on active_list_id =lists.id')
Activerecord's Arel is your friend here:
User.where(:active_list_id.not_eq => nil)
Extending Steven's answer, to get the Lists
class User
belongs_to :active_list, :class_name => "List"
def build_list_array
#lists = User.where('active_list_id is not null').map(&:active_list).compact

Resources