Mongoid - set a field to true for at least one document - ruby

I want there always to be at least one document in database which has a field titled "selected" set to true. How do I do that? Most probably I have to use callbacks, but which one: before (or after) _create, _upsert, _update?
And how can I ensure that it will be set to true whatever operation executes: create, update, upsert...? I guess that would not be right to create a callback for each of them.

after_save always runs after create & update
so you could do:
after_save do |your_class|
your_class.update_column(:selected, true) unless YourClass.where(selected: true).exists?
end
NB./ update_column should not fire the after_save again!

Related

Is save method mandatory in rails?

Model.find(ids).each { |model| model.is_active = false } unless ids.empty?
Here, is model.save method necessary or not ? Because without that also, it's working.
Column is defined like this
t.integer :is_active, limit: 1
The short answer to your question is: Yes.
Running model.is_active = false, by itself, will not persist the change in the database. Try re-fetching the database record after running model.is_active = false, and you'll see that the value has not actually changed; you also need to run model.save to do that.
However, for scenarios like this (assuming you want to immediately save the change to the record!) there's a more concise way of saving the data, instead of running two commands:
model.update_attribute(:is_active, false)
This will update the value in the database, if validations pass, and also run any callbacks (e.g. after_save).
If you want to update the value without running validations and callbacks (which is typically faster, but more "dangerous") then you can instead use:
model.update_column(:is_active, false)

Rails 4 update Type when migrating to Single Table Inheritance

Rails 4.0.4, Ruby 2.1.2
I want to use STI like so:
User < ActiveRecord::Base
Admin < User
But currently I have:
User < ActiveRecord::Base
Info < ActiveRecord::Base
So I changed my models, and then start writing my migration. In my migration, I first add a column to allow STI:
add_column :users, :type, :string
Then I want to update the Users currently in the database to be Admin
# Place I'm currently stuck
Then I move all my Info records into the Users table
Info.all.each { |info| User.create(name: info.name, email: info.email) }
Everything seems to work except turning the previous Users into Admins. Here are some things I've tried:
# Seems to work, but doesn't actually save type value
User.each do |user|
user.becomes!(Admin)
user.save! # evaluates to true, doesn't have any errors
end
# Seems to work, but doesn't actually save type value
# I've also tried a combo of this one and the above one
User.each do |user|
user.type = "Admin"
user.save! # evaluates to true, doesn't have any errors
end
User.each do |user|
user = user.becomes!(Admin)
user.save! # evaluates to true, doesn't have any errors
end
# Seems to work, but doesn't actually save type value
User.each do |user|
user.update_attributes(type: "Admin")
end
Each time the local user variables seems to have the correct type ("Admin"), along with save evaluating to true, but when I check Admin.count or check Users type value, it is always nil. I know you're not supposed to change them, but this is just to migrate the data over to STI and then I'll be able to start creating Users or Admin with the proper class.
At the very least I think Rails should raise an error, set an error or somehow let the developer know it's failing the save calls.
It turns out that while update_attributes doesn't work for type (I haven't researched why yet), update_column does work.
So the migration simply becomes:
User.each do |user|
user.update_columns(type: "Admin")
end
The reason this works and other updates don't can probably be traced back to either callbacks or validations not being run. I have no callbacks that would prevent it, but maybe there are default Rails ones for type
http://apidock.com/rails/ActiveRecord/Persistence/update_columns
If you had more rows in the database User.each would become quite slow as it makes an SQL call for each user.
Generally you could use User.update_all(field: value) to do this in one SQL call but there is another reason to avoid this: if the User model is later removed the migration will no longer run.
One way to update all rows at once without referencing the model is to use raw SQL in the migration:
def up
execute "UPDATE users SET type = 'Admin' WHERE type IS NULL"
end

Does adding a new object in a has_one relationship, not update the association?

I have 2 models, an example:
class Report ...
belongs_to :answer_sheet
end
class AnswerSheet ...
has_one :report
end
When I do a:
#answersheet.report = Report.create(:data => 'bleah')
#answersheet.save
# and then create another report and assign it to the same #answersheet
# assuming at this stage #answersheet is already reloaded
#answersheet.report = Report.create(:data => 'new data')
#answersheet.save
# (irb) #answersheet.report returns the first report with the data 'bleah' and not
# the one with the new data.
Is this supposed to be the correct behavior?
If I want to update the association to the later report, how should I go about doing it?
It took me a few tries to see what you were talking about. But I got it now.
Take a look at the SQL and you'll find ActiveRecord is doing a select and then adding ASC and LIMIT 1. There can be more than one report records that refer to the same answer_sheet.
You can prevent this situation by adding a validation that checks for uniqueness of answer_sheet_id.
You should also start using save! and create! (note the bang operators) so exceptions are thrown during validation.
Lastly, calling Report.create followed by #answersheet.save performs two database transactions, whereas Report.new followed by #answersheet.save would perform just one.

Odd behaviour when saving instances in Ruby DataMapper

having just started to look at DataMapper for Ruby ORM I've run into an issue that confuses me to no end.
The default behaviour when saving instances in DataMapper (through DataMapper::Resource.save) is, as far as I understand, to silently fail, return false from the save method and collect any errors in the errors collection. So far so good, this works as expected. The issue I see is with natural primary keys, where setting duplicate ones will throw an exception instead of silently returning false from the save method, blatantly disregarding the current setting of raise_on_save_failure. Consider the following snippet;
require 'data_mapper'
class Thing
include DataMapper::Resource
property :name , String, :key=> true
property :description, String, length: 2..5
end
DataMapper.setup :default, "sqlite:memory"
DataMapper.finalize
DataMapper.auto_migrate!
#Create a thing and have it properly silently fail saving
t1=Thing.new(:name=>"n1",:description=>"verylongdescription" )
t1.save #ok
puts("t1 is saved:"+t1.saved?.to_s)
t1.errors.each do |e|
puts e
end
#Create two things with the same key, and fail miserably in silently failing the second save...
t1=Thing.new(:name=>"n1",:description=>"ok" )
t2=Thing.new(:name=>"n1",:description=>"ok" )
t1.save #ok
t2.save #not ok, raises Exception event though it shouldn't?
puts("t2 is saved:"+t1.saved?.to_s)
t2.errors.each do |e|
puts e
end
The first save, on an instance failing a validation rule for the :description property behaves as expected. The third save, of an instance with duplicate keys, however does not, since it raises an execption instead of nicely just returning false.
Why is this? It is obviously possible to work around, but it doesn't feel very clear. Is the case that the silent behaviour will only apply to validation errors in the DataMapper layer, and any hard errors from the underlying datastore will be propagated as exceptions to the caller?
Thanks!
As another user pointed out in a comment, this happens because setting raise_on_save_failure to false doesn't mean that no exceptions will occur. It will always return false (with no exceptions) when validation errors occur.
Database errors will blow up, as you noticed, in the form of an exception. Such errors could happen due to a large number of factors (the connection failed, no disk space), including mundane ones like a duplicate key. DataMapper can't know whether the object you're trying to save possesses a duplicate key, so it just validates it and sends to the database, where the error actually occurs.

Cannot access session array from view in Rails 3.1

So this one has me stumped; hopefully a kind soul can help me out. As part of logic to display certain buttons for users who have performed an action, I store the id of the object they manipulated in a session array called "prayed_for". (The unique part of this problem is that it deals with sessions and not an array persisted in a database.) In the show action of my controller, I evaluate whether of not the current id of the entry being requested is present in the session array "prayed_for". I assign this boolean value to the session variable #already_prayed_for. Below is the logic for that:
#already_prayed_for = (session[:prayed_for] ||= []).include? params[:id]
But here's the problem: I cannot evaluate this in my partial. For example if I attempt to evaluate the following (in HAML), where "entry" is a variable representing the entry at hand and "id" is it's id, which should be stored in the session "prayed_for" variable, it will always evaluate to false:
-if (session[:prayed_for] ||= []).include? entry.id
I've come to the conclusion that I may be evaluating something wrong in my partial when evaluating whether or not an id is present in a session array. Additionally this same concept worked perfectly in a controller action (but I can't use that solution this time around, it has to be evaluated in the partial) but it also failed in the ApplicationHelper. Any help in resolving this problem is much appreciated!
UPDATE:
Here's the code where I set the session in another action:
if #entry.save
(session[:prayed_for] ||= []) << params[:id]
end
params[:id] may be a String, while entry.id is a Fixnum. Verify that the objects you're comparing (via include?) are of the same type.
You might want to make a helper out of the logic you're using, for example:
def already_prayed_for?(entry_id)
(session[:prayed_for] ||= []).include? entry_id.to_i
end

Resources