Multiple after_commit for the same method - ruby-on-rails-3

How can I specify multiple callbacks to the method?
after_commit :assign_awards, on: [:create, :destroy]
private
def assign_awards
AwardsWorker.perform_async(self.id, self.class.name, self.user_id)
end
Error:
/activesupport-3.2.13/lib/active_support/callbacks.rb:404: syntax error, unexpected '[', expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END
...(transaction_include_action?(:[:create, :destroy]))
For some reason, the callback isn't executed with this either:
after_commit :assign_awards, on: :create
after_commit :assign_awards, on: :destroy
It only works if I two different methods like:
after_commit :assign_awards_create, on: :create
after_commit :assign_awards_destroy, on: :destroy
private
def assign_awards_create
AwardsWorker.perform_async(self.id, self.class.name, self.user_id)
end
def assign_awards_destroy
AwardsWorker.perform_async(self.id, self.class.name, self.user_id)
end

Hmm, this answer shows the opposite:
https://stackoverflow.com/a/10356903/1023609.
Maybe it is a problem with the Rails version you are using.

Related

Execution order of multiple after_commit callbacks (Rails)

I recently found that multiple after_commits defined in the same model get called in reverse order. For example
after_commit method1, :on => :create
after_commit method2, :on => :create
method2 gets called before method1.
Is it always called in FILO order?
This behaviour is still present in Rails 5.2.2.1.
My solution:
after_commit :after_commit_callbacks, :on => :create
def after_commit_callbacks
method1
method2
end

How to invoke a rails sweeper in this scenario?

As you can see from code below. I am caching the show action. I also have the following method in the show action View.create_for(#song).
I would like to make it so, when View.create_for(#song) is called, it clears out the respective cache.
How would I go about this? Do I have to manually invoke the rails sweeper in the View model? If so, how?
My controller:
class SongsController < ApplicationController
caches_action :show, :cache_path => (proc do
song_path(params[:id], :user_id => user_signed_in? ? current_user.id : nil)
end)
# I tried with the following line, but this is not what I want. I only want to call the sweeper when `View.create_for(#song)` is called:
# cache_sweeper :views_sweeper, :only => [:show]
def show
#song = Song.find(params[:id])
View.create_for(#song)
end
end
My Sweeper:
class SongsSweeper < ActionController::Caching::Sweeper
observe Song
def after_save(song)
expire_fragment(/songs\/#{song.id}\/.*?/)
end
end
I think you should be referring to the songs_sweeper, not the views_sweeper:
cache_sweeper :songs_sweeper, :only => [:show]
I'm not sure of your requirements, but you could also be more specific in your SongsSweeper by changing after_save to after_create:
class SongsSweeper < ActionController::Caching::Sweeper
observe Song
def after_create(song)
expire_fragment(/songs\/#{song.id}\/.*?/)
end
end

Rails 3 recursive polymorphic comments model, how to do redirect to top-most parent

I'm still earning my stripes in Rails and have ran into a problem I can use some help on. I'm building an app that has various models (stories, photos, artwork, etc.) that a user can comment on, as well as the comments themselves. I have 98-99% of the functionality working but am stuck on getting the redirect to redirect to the top-most parent (a story, photo, etc.) after the comment has been created.
My comment model look like this:
# /app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
has_many :comments, :as => :commentable
end
...I have several models that a user can comment on, for example a story model:
# /app/models/story.rb
class Story < ActiveRecord::Base
has_many :comments, :as => :commentable
end
My comments controller looks like this at this point:
# /app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def index
#commentable = find_commentable
#comments = #commentable.comments
end
def new
#commentable = find_commentable
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
redirect_to :back
else
render :action => 'new'
end
end
protected
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
...And of course my routes look like this:
# /config/routes.rb
resources :comments do
resources :comments
end
resources :stories do
resources :comments
end
The particular line I need to change is the redirect_to :back line (everything else, the polymorphism, the recursion of comments, etc. works fine). The current code works as intended when a user is commenting on a story but it's not ideal when a user is commenting on a comment because the form for that functionality is not on the story "show" page (perhaps it needs to be?).
What I have tried to do (and what I suspect the solution might be) is a method that finds the parent object and recurses when that object is a comment. My previous attempts at doing this has not been clean at all and I have yet to get a working prototype working.
I used this railscasts episode to base the majority of my code but the redirect_to :id => nil doesn't work for me as the create comment method is somehow called and it results in a NilClass error when it attempts to build comments (perhaps something is wrong with my routing as I don't see how the index action would call create?).
So Rails experts, what am I doing wrong? What do I need to do here to get this working? I feel like I'm 99% there but that last 1% is driving me crazy.
Thanks in advance...
OK i had to read this a couple times...
#comment.commentable
would return an instance of Story or whatever object that did the comment.
Solved this...It's probably not the cleanest but it works:
I first added a method in comments controller...
def get_master
#parent = #comment.commentable
if #parent.respond_to?('commentable_type')
#comment = #parent
get_master
else
return #parent
end
end
Then I changed my redirect_to to call this method in the create controller.
The key was understanding that #object.respond_to? was what I needed to do check if a method is defined.
Here's a full example of how it works: http://t.co/N6WIGzuW

Rails 3 routing - :delete method on :collection

I want to create a route to allow deleting all shares. RESTful way would be to use verb DELETE. How can I create a routing that points to:
DELETE /shares
I tried in the routes:
resources :shares do
delete :on => :collection
end
But this yielded an error that rails can't turn nil into a symbol.
For now I have:
resources :shares do
delete 'delete_all', :on => :collection
end
EDIT: I had a typo in controller action name and this latter way works, but produces URL /shares/delete_all which is not very RESTful.
How can I drop the _delete_all_ part?
For Rails 3 you can do it this way and have nice resourceful GET/DELETE collection actions pointing to index and delete_all respectively:
resources :shares do
delete :index, on: :collection, action: :delete_all
end
If you're using Rails 4 you can make use of concerns to DRY this up and apply it to many resources:
concern :deleteallable do
delete :index, on: :collection, action: :delete_all
end
resources :shares, concerns: :deleteallable
resources :widgets, concerns: :deleteallable
What am I missing?
match 'shares', :to => 'shares#delete_all', :via => :delete
more info: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/
<subjective opinion>
This is generally a bad idea and a code/design smell. The need to be deleting all records via a RESTful interface should really be behind a protected (authenticated) action and/or the action should be scoped to the user somehow.
The right way for your case, in routes:
resources :shares, except: :destroy
resource :share, only: :destroy
Please, pay attention, that I wrote word resource for destroy action
Then redefine "destroy" in shares_controller:
def destroy
respond_to do |format|
if Share.destroy_all
format.html { redirect_to root_path, notice: 'Share collection successfully deleted' }
else
format.html { redirect_to root_path, notice: 'Share collection cannot be deleted.' }
end
end
end
Here is the non REST way of doing it:
resources :shares do
collection do
delete :destroy_all
end
end
Then in your controller you will need something like this:
def destroy_all
Share.delete_all
end
Then this is what you want to do:
resources :shares do
collection do
delete :index
end
end
Then in your controller you will need something like this:
def index
if request.method == delete #delete might need to be a string here, I don't know
Share.delete_all
else
#shares = Share.all
end
end
There's a slightly simpler syntax for this that works in Rails 4.2 at least:
resources :shares do
delete on: :collection, action: :destroy_all
end
resources :shares do
collection do
delete '/', :to => :delete_all
end
end

Posting with Nested Resources

I have nested my resources (see below) and when I try to create a new entity, I get the following error. Does anyone know why I'm getting this error and how to solve it?
undefined method `applications' for nil:NilClass
resources careers do
resources applications
end
Within the 'Applications' controller I have:
before_filter [[:authenticate, :except => :new], :load_career]
def create
# The following line is where the error originates
#application = #career.applications.new(params[:application])
respond_to do |format|
...
end
end
private
def load_career
#career = Career.find(params[:career_id])
end
The Career and Application models have has_many :applications and belongs_to :career respectively.
And the '*_create_applications' migration has a career_id field.
I have never seen before_filters defined that way. I just tried it in Rails 3 and it doesn't seem to do anything. I would give each callback it's own before_filter call:
before_filter :authenticate, :except => :new
before_filter :load_career