RoR: first_or_initialize block doesn't save - ruby-on-rails

I have the following code, which works fine with no errors but the models never get saved...
myarray.each do |item|
r = MyModel.unscoped.where(:site_id => #site.id, :url => item['permalink_url']).first_or_initialize do |r|
r.title = 'asdasdadaddjfgnfd'
r.save!
end
end
Terminal shows the SQL SELECT statements when attempting to find the Models, but the UPDATE/INSERT statements never run.
What am I missing here?

Rails first_or_* methods invoke passed block only for initialize or create part. If the record is found, the methods just return it so passed block will never run. Check the source
So you can use block in first_or_* methods only to initialize new items, not to update existing ones. Most likely, records with such conditions exist and don't get updated.
Try to move update code, something like
myarray.each do |item|
r = MyModel.unscoped.where(:site_id => #site.id, :url => item['permalink_url']).first_or_initialize
r.title = 'asdasdadaddjfgnfd'
r.save!
end

I solved this by using:
.first_or_initialize.tap() do |r|
But the comments below are also relevant

You're looking for first_or_create. first_or_initialize just initializes the object (possibly to prep for saving, but it doesn't have to be).
You're existing code would likely work as follows:
r = MyModel.unscoped.where(:site_id => #site.id, :url => item['permalink_url']).first_or_initialize do |r|
r.title = 'asdasdadaddjfgnfd'
end
r.save!

Related

Error deleting record in console ruby on rails

I am trying to delete a record using the console. I have a model for "User". I tried several methods in the console:
a = User.where(:id => '18')
a.destroy
a.delete
User.where(:id => '18').destroy
User.where(:id => '18').delete
Using all of these methods, I got the same error: "Wrong number of arguments (0 for 1)"
Does anyone know what I am doing wrong?
Thx!
Try:
a = User.find(18)
a.destroy
When we use where, result will be ActiveRecord::Relation, means multiple records, on which you can't call destroy directly. You will need to call destroy by iterating over the result.
users = User.where(:id => 18)
users.each do |user|
user.destroy
end
I can add something here, The issue with your code that you are passing string while it expects an integer 'Number'
Your code should be as the following:
a = User.where(:id => 18).first
a.destroy
Without using first array of object will be returned and you can't use destroy method directly on it, in case you don't want to add first then your code should be like:
a = User.where(:id => 18)
a.each do |obj|
obj.destroy
end

Update on record would update all other records in model, global ordering in rails 3.1

I would like to update all records in a rails (3.1) model when i update an attribute on a single record.
Like self.update_attribute(:global_order => 1) then before or after save a would like to update all other records to update thier global_order (1, 2, 3, 4).
Right now with on after_save callback I get caught in a recursive loop, is skip callbacks the way to go? I would like the app to throw exceptions if anything seems strange in global_order.
Or are there any 3.1 gems that would solve my issue.
after_save :set_global_order
def set_global_order
#products = self.class.all(:order => :global_order)
#products.sort! {|a,b| a.global_order <=> b.global_order}
#products.reverse!
#products.each_with_index do |p, index|
p.update_attributes!({:global_order => index + 1})
end
end
Not sure if there's a gem, but you could definitely refactor this with the following considerations:
No need to pollute the object with an instance variable when a local one will do
The first three lines are sorting the same set, why not do that once?
...
def set_global_order
products = self.class.order('global_order DESC')
products.each_with_index do |p, index|
p.update_column(:global_order, index + 1)
end
end

Performance: minimize database hitting

I am using Ruby on Rails 3.0.7 and I am trying to minimize database hitting. In order to do that I retrieve from the database all Article objects related to a User and then perform a search on those retrieved objects.
What I do is:
stored_objects = Article.where(:user_id => <id>) # => ActiveRecord::Relation
<some_iterative_function_1>.each { |...|
stored_object = stored_objects.where(:status => 'published').limit(1)
...
# perform operation on the current 'stored_object' considered
}
<some_iterative_function_2>.each { |...|
stored_object = stored_objects.where(:visibility => 'public').limit(1)
...
# perform operation on the current 'stored_object' considered
}
<some_iterative_function_n>.each { |...|
...
}
The stored_object = stored_objects.where(:status => 'published') code will really avoid to hitting the database (I ask this because in my log file it seams still run a database query for each iteration)? If no, how can I minimize database hitting?
P.S.: in few words, what I would like to do is to work on the ActiveRecord::Relation (an array of ) but the where method called on it seams to hit the database.
Rails has functionality to grab chunks of the database at one time, then iterate over the rows without having to hit the database again.
See "Retrieving Multiple Objects in Batches" for more information about find_each and find_in_batches.
Once you start iterating over stored_objects (if that's what you're doing), they'll be loaded from the database. If you want to load only the users's published articles, you could do this:
stored_objects = Article.where(:user_id => id, :status => 'published')
If you instead want to load published and unpublished articles and do something different with the published ones, you could do this:
stored_objects = Article.where(:user_id => id)
stored_objects.find_all { |a| a.status == 'published' }. each do |a|
# ... do something with a published article
end
Or perhaps:
Article.where(:user_id => id).each do |article|
case article.status
when 'published'
# ... do something with a published article
else
# ... do something with an article that's not published
end
end
Each of these examples performs only one database query. Choosing which one depends on which data you really want to work with.

In Rails, what is the best way to update a record or create a new one if it doesn't exist?

I have a create statement for some models, but it’s creating a record within a join table regardless of whether the record already exists.
Here is what my code looks like:
#user = User.find(current_user)
#event = Event.find(params[:id])
for interest in #event.interests
#user.choices.create(:interest => interest, :score => 4)
end
The problem is that it creates records no matter what. I would like it to create a record only if no record already exists; if a record does exist, I would like it to take the attribute of the found record and add or subtract 1.
I’ve been looking around have seen something called find_or_create_by. What does this do when it finds a record? I would like it to take the current :score attribute and add 1.
Is it possible to find or create by id? I’m not sure what attribute I would find by, since the model I’m looking at is a join model which only has id foreign keys and the score attribute.
I tried
#user.choices.find_or_create_by_user(:user => #user.id, :interest => interest, :score => 4)
but got
undefined method find_by_user
What should I do?
my_class = ClassName.find_or_initialize_by_name(name)
my_class.update_attributes({
:street_address => self.street_address,
:city_name => self.city_name,
:zip_code => self.zip_code
})
Assuming that the Choice model has a user_id (to associate with a user) and an interest_id (to associate with an interest), something like this should do the trick:
#user = User.find(current_user)
#event = Event.find(params[:id])
#event.interests.each do |interest|
choice = #user.choices.find_or_initialize_by_interest_id(interest.id) do |c|
c.score = 0 # Or whatever you want the initial value to be - 1
end
choice.score += 1
choice.save!
end
Some notes:
You don't need to include the user_id column in the find_or_*_by_*, as you've already instructed Rails to only fetch choices belonging to #user.
I'm using find_or_initialize_by_*, which is essentially the same as find_or_create_by_*, with the one key difference being that initialize doesn't actually create the record. This would be similar to Model.new as opposed to Model.create.
The block that sets c.score = 0 is only executed if the record does not exist.
choice.score += 1 will update the score value for the record, regardless if it exists or not. Hence, the default score c.score = 0 should be the initial value minus one.
Finally, choice.save! will either update the record (if it already existed) or create the initiated record (if it didn't).
find_or_create_by_user_id sounds better
Also, in Rails 3 you can do:
#user.choices.where(:user => #user.id, :interest => interest, :score => 4).first_or_create
If you're using rails 4 I don't think it creates the finder methods like it used to, so find_or_create_by_user isn't created for you. Instead you'd do it like this:
#user = User.find(current_user)
#event = Event.find(params[:id])
for interest in #event.interests
#user.choices.find_or_create_by(:interest => interest) do |c|
c.score ||= 0
c.score += 1
end
end
In Rails 4
You can use find_or_create_by to get an object(if not exist,it will create), then use update to save or update the record, the update method will persist record if it is not exist, otherwise update record.
For example
#edu = current_user.member_edu_basics.find_or_create_by(params.require(:member).permit(:school))
if #edu.update(params.require(:member).permit(:school, :majoy, :started, :ended))

Rails find won't join when args appended

I'm hoping I'm doing something wrong here and someone can point me in the right direction...
I have written a method inside of one of my models, here is the code:
def self.find_by_user_id(user_id, *args)
self.find(:all, :joins => :case_map,
:conditions => ['case_maps.uer_id = ? and case_maps.case_id = cases.id', user_id],
*args)
end
I can call the code like this and it works as expected:
Case.find_by_user_id(some_user_id)
However, when this code is executed with any additional args, like this:
Case.find_by_user_id(some_user_id, :limit => 15)
I get back ALL cases. The query in my log file show that it executed this:
Case Load (0.6ms) SELECT * FROM `cases` LIMIT 15
I even put a logger.info message in that method to make sure it's the one that is executing... It seems that whenever *args is not nil, that it skips all of the conditions and joins I added to the find and just uses the *args.
Anyone see something that I'm doing wrong?
AR::B#find expects a variable number of arguments, but the last of those should be the options hash. It uses Array#extract_options!, and so can you:
def self.find_by_user_id(user_id, *args)
options = args.extract_options!
options.merge!(:joins => :case_map, :conditions =>
['case_maps.uer_id = ? and case_maps.case_id = cases.id', user_id])
self.find(:all, *args, options)
end
But you shouldn't. What possible values make sense in between the :all and the options fields?
What you are really after is:
def self.find_by_user_id(user_id, options)
self.find(:all, options.merge(:joins => :case_map, :conditions =>
['case_maps.user_id = ? and case_maps.case_id = cases.id', user_id]))
end
Or one better:
named_scope :find_by_user_id, lambda{|user_id|
{
:joins => :case_map,
:conditions => ['case_maps.user_id = ? and case_maps.case_id = cases.id', user_id]
}
}
The named scope will ensure all your options are cleanly merged (all your conditions are applied even if you add more later on). You can then call it with:
Case.find_by_user_id(user_id).all(:limit => 15)
(Named Scope are “Da Bom”. One should use them as much as possible. Even in general conversation. For example, if you were here in my house last night, you may have overheard this little ditty: “What do you want for your dinner?”, “Same thing I have every night, Named Scopes and chips.”. Because I'm committed and all that.)
Also, as a side note, assuming this is on your Case model, the "and case_maps.case_id = cases.id" clause is unnecessary, :joins => :case_map does that for you.

Resources