I'm trying to cancel the save (gracefully) of a has_many element if is repeated. This is what I mean:
class AdmininstratorRole < ActiveRecord::Base
has_many :permissions, before_add: :avoid_repetition
def avoid_repetition(permission)
raise "Repeated Permission" if self.permissions.where(klass: permission.klass, action: permission.action).count > 0
end
end
If it wouldn't be an associated callback I could just return false and that would cancel the save. But since is an associated callback I need to raise an exception to cancel the saving as explained here. The problem is that I don't know how to recover gracefully from that exception.
Any ideas???
You need to catch the exception from whatever code is adding the permission role.
begin
role.permissions.add(permission)
rescue
# do whatever should happen here.
end
Although, you may want to check that the permission exists before trying to add it.
Related
I was pulling my hair out trying to figure out why my call to Warden::Manager.before_logout was throwing a NoMethodError for NilClass when I tried to call 'user.my_method'. Then I added a debug puts call to the before_logout block and discovered that it was being called TWICE on each logout - the first time with the user being nil, and then immediately after, with the user object supplied. So I was able to inelegantly get around the exception by changing the call to 'user.my_method if user', but I'm still not comfortable with not knowing why before_logout is getting called twice. Has anyone else seen this? Is it perhaps one of those happens-in-development-only environment anomalies?
Devise.setup do |config|
Warden::Manager.after_authentication do |user,auth,opts|
user.update_attributes(is_active: true)
end
Warden::Manager.before_logout do |user,auth,opts|
user.update_attributes(is_active: false) if user
end
This is old, but you probably have two models (scopes in Warden's case): one for User and another for AdminUser. You can use an if statement for just the model/scope you want:
Warden::Manager.before_logout do |user, auth, opts|
if opts[:scope] == :user
user.current_sign_in_at = nil
user.save!
end
end
solve it by adding this in the config/initializers/devise.rb. Add code for what ever you want to do on logout.
Warden::Manager.before_logout do |user,auth,opts|
# store what ever you want on logout
# If not in initializer it will generate two records (strange)
end
I have searched a lot for this issue with no luck...
I have a model with a before_update method, I can't replace it with a validator because the actual method checks the existance of many things and update many others. My question is, How can I raise an error if a process from the before_update method fails? Something like:
def update_status
if !(many verifications and updates)
self.errors[:base] << "Could not update if ...."
end
end
With the abobe code I get the update notice from the controller after the page loads, but I want to show the error from the before_update method. How can I show the error to the user?
Thanks a lot!!
def update_status
if !(many verifications and updates)
raise "Could not update if ...."
end
end
Now do show the error message on the controller you could do something like this:
begin
#myObject.update_status
rescue => ex
flash[:notice] = ex.message
end
I'm trying to figure out a way to partially delete/destroy dependent models in rails.
Code looks something like this:
class User < ActiveRecord::Base
has_many :subscriptions
has_many :photos, :dependent => :destroy
has_many :badges, :dependent => :destroy
before_destroy :partial_destroy
def partial_destroy
self.photos.destroy_all
self.badges.destroy_all
return false if self.subscriptions.any?
end
...
Essentially, I want to destroy the photos and badges, but if the user has any subscriptions, I want to keep those, and also keep the user from being destroyed.
I tried with .each { |obj| obj.destroy } and using delete and delete_all, but it seems to not matter.
It looks like rails is performing some kind of a rollback whenever the before_destroy returns false. Is there a way to destroy part of the dependents but not others?
This is old so I expect you've forgotten it, but I stumbled across it.
I'm not surprised delete and delete_all didn't work, since those bypass callbacks.
You're exactly right that Rails performs a rollback if any before_ callback returns false. Because Rails wraps the entire callback chain in a transaction, you're not going to be able to perform database calls (like destroys) inside the chain. What I would recommend is putting a conditional in the callback:
If the user has subscriptions, kick off a background job which will do this partial delete later (outside the callback transaction), and return false from the callback.
If they don't have subscriptions, you don't start the background job, return true from the callback, and destroy your model as usual.
I ended up doing the following:
override destroy on the User model (see below)
not actually deleting the User, but rather destroying the dependants that are not needed, and blanking any fields on the User model itself, e.g. email.
I created a UserDeleter class that takes the user and performs all clearing operations, just to keep things cleaner / having some kind of single-responsibility
overriding destroy
def destroy
run_callbacks(:destroy) do
UserDeleter.new(self).delete
end
end
deleting dependants and clearing data on User
class UserDeleter
def initialize(user)
#user = user
end
def delete
delete_photos
delete_badges
clear_personal_data
# ...
end
private
def delete_photos
#user.photos.destroy_all
end
def clear_personal_data
#user.update_attributes!(
:email => deleted_email,
:nickname => '<deleted>')
end
def deleted_email
"deleted##{random_string}.com"
end
def random_string(length = 20)
SecureRandom.hex(length)[0..length]
end
#...
end
What I want to do is basically have a user obtain the lock on a record and have it for a specific amount of time so they can make changes to it, like wikipedia. So lets say a wikipedia article gives the user an hour to edit it before other users may edit it.
How could I achieve that with Rails 3? I have read up and found that pessimistic locking is what I should use for the lock. Given that... What kind of mechanism would I use for releasing the lock say after an hour?
My stack is Rails 3, Heroku, PostgreSQL.
Thanks for any answers and I love to see code if you can that would be so awesome!
Here's an example that creates locks, but doesn't delete them.
I leave that up to you.
The locks do expire after an hour in this example, but to complete the app they should automatically be deleted on a successful update of a post.
working example
or read the
relevant commit
You can do this with acts_as_lockable_by gem.
Imagine you have a patient (ActiveRecord) class that can only be edited by one user and it should be locked to this user till he decides to release it:
class Patient < ApplicationRecord
acts_as_lockable_by :id, ttl: 30.seconds
end
Then you can do this in your controller:
class PatientsController < ApplicationController
def edit
if patient.lock(current_user.id)
# It will be locked for 30 seconds for the current user
# You will need to renew the lock by calling /patients/:id/renew_lock
else
# Could not lock the patient record which means it is already locked by another user
end
end
def renew_lock
if patient.renew_lock(current_user.id)
# lock renewed return 200
else
# could not renew the lock, it might be already released
end
end
private
def patient
#patient ||= Patient.find(params[:id])
end
end
Add a field called "editable_until":datetime and set a specific date (Time.now + 30.min f.e.) when creating your record. And simply query this field to find out if the user has the right to update the record or not.
class Post << AR
before_validation :set_editable_time
validate :is_editable
def editable?
self.editable_until.nil? || self.editable_until >= Time.now
end
protected
def is_editable
self.errors[:editable_until] << "cannot be edited anymore" unless editable?
end
def set_editable_time
self.editable_until ||= Time.now + 30.min
end
end
Post.create(:params....)
=> <Post, ID:1, :editable_until => "2011-10-13 15:00:00">
Post.first.editable?
=> true
sleep 1.hour
Post.first.editable?
=> false
Even though I'm pretty sure I know why this error gets raised, I don't seem to know why or how my session is exceeding the 4KB limit...
My app was working fine, but once I deliberately started adding bugs to see if my transactions were rolling back I started getting this error.
To give some background, I'm busy coding a tournament application that (in this section) will create the tournament and then add some tournament legs based on the number of teams as well as populate the the tournament with some 'ghost fixtures' once the legs have been created.
The flash[:tournament] was working correctly before; using a tournament object, I have access to any AR validation errors as well as data that has been entered on the previous page to create the tournament.
TournamentController.rb
begin
<other code>
Tournament.transaction do
tournament.save!
Tournament.generate_legs tournament
Tournament.generate_ghost_fixtures tournament
end
flash[:notice] = "Tournament created!"
redirect_to :action => :index
rescue Exception => e
flash[:tournament] = tournament
redirect_to :action => :new, :notice => "There was an error!"
end
Tournament.rb
self.generate_ghost_fixtures(tournament)
<other code>
#Generate the ghost fixtures
#tournament_legs is a has_many association
tournament_legs_array = tournament.tournament_legs
tournament_legs_array.each do |leg|
number_of_fixtures = matches[leg.leg_code]
#For the first round of a 32 team tournament, this block will run 16 times to create the matches
number_of_fixtures.times do |n|
Fixture.creatse!(:tournament_leg_id => leg.id, :match_code => "#{leg.leg_code}-#{n+1}")
end
end
end
I can do nothing but speculate as to why my session variable is exceeding 4KB??
Is it possible that the tournament object I pass through the flash variable contains all the associations as well?
Here is the dump of my session once I get the error.
Hope this is enough info to help me out :)
Thanks
Session Dump
_csrf_token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
flash: {:tournament=>#<Tournament id: nil, tournament_name: "asd", tournament_description: "asdasd", game_id: 1, number_of_teams: 16, start_date: "2011-04-30 00:00:00", tournament_style: "single elimination", tournament_status: "Drafting", active: true, created_at: "2011-04-30 10:07:28", updated_at: "2011-04-30 10:07:28">}
player_id: 1
session_id: "4e5119cbaee3d5d09111f49cf47aa8fa"
About dependencies, it is possible. Also save an ActiveRecord instance in the session is not a recommended aproach. You should save only the id. If you need it in all your requests use a before filter to retrieve it.
You can read more why is a bad idea at: http://asciicasts.com/episodes/13-dangers-of-model-in-session
The generally accepted and recommended approach is to not use a redirect on error, but a direct render instead. The standard "controller formula" is this:
def create
#tournament = Tournament.new(params[:tournament])
if #tournament.save
redirect ...
else
render 'new' # which will have access to the errors on the #tournament object and any other instance variable you may define
end
end
class Tournament < ActiveRecord::Base
before_create :set_up_legs
end
On successful saving, you can drop all instance variables (thereby wiping the in-memory state) and redirect to another page. On failure (or exception) you keep the object in memory and render a view template instead (typically the 'new' or 'edit' form page). If you're using standard Rails validation and error handling, then the object will have an errors array that you can just display.
I'd also recommend you use ActiveRecord associations which automatically give you transactions. If you push all this into the model, e.g. a "set_up_legs" method or something, then you can use ActiveRecord error handling. This is part of the "skinny controller, fat model" paradigm.
in session_store.rb, uncomment the last line with :active_record_store
Now restart the server
I would convert the exception to string before assigning it to flash[:tournament] with 'to_s'.
I had the same error and it seems assigning an exception object to a session variabla like flash means it takes the whole stack trace with it into the session. Try it, worked for me.