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

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

Related

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 model with accepts nested attributes not updating children

I've got a model setup where a user can create a quiz with many questions and many answers on each question
The models look like this:
model Page < AR::Base
end
model Quiz < Page
has_many :questions
accepts_nested_attributes_for :questions, :allow_destroy => true
end
model Question < AR::Base
belongs_to :quiz
has_many :answers
accepts_nested_attributes_for :answers, :allow_destroy => true
end
model Answer < AR::Base
belongs_to :question
end
And my form looks like this:
= form_for #quiz do |f|
f.fields_for :questions do |qf|
# fields omitted, have fields for id, content, etc
qf.fields_for :answers do |af|
# fields omitted, have fields for id, answer, etc
f.submit 'save'
Everything works wonderfully when I edit just the quiz or when I add new questions and answers, but when I edit existing questions and answers, the changes aren't persisted in the DB. I can see the correct nested parameters being sent into the controller and when inspected the #quiz after calling update_attributes it shows the updated questions and answers but they aren't being persisted after the page is updating.
I've never had this sort of issue before and am having trouble spotting the cause, can anyone share some insight?
Thanks!
As requested, controller code: (Quiz is an STI subclass of Page)
PagesController < ApplicationController
def update
#page = #section.pages.find(params[:id])
if #page.update_attributes(params[#page.type.downcase.underscore])
redirect_to online_course_section_pages_path(#online_course, #section), :notice => "Your page has been updated"
else
render :edit
end
end
end
EDIT:
Found the problem was because of using #page.type.downcase.underscore instead of #page.type.underscore.downcase so update attributes was being passed nil instead of the actual data
Found the problem was because of using #page.type.downcase.underscore instead of #page.type.underscore.downcase so update attributes was being passed nil instead of the actual data

Ruby on Rails nested resource routing error

I have a user, comment, and route model as shown:
class User < ActiveRecord::Base
has_many :routes, :dependent => :destroy
has_many :comments, :dependent => :destroy
end
class Route < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :user
end
I have the routes.rb file nesting comments and routes within user as shown:
MyApp::Application.routes.draw do
resources :users do
resources :comments
resources :routes
end
When I run 'rake routes', the route to the Routes_controller index appears as so:
user_routes GET /users/:user_id/routes(.:format) {:action =>"index", :controller=>"routes"}
Yet for some reason when a user is signing in, I get a routing error saying that the routes controller cannot be found. This happens when the system is posting a new session in the session controller. I know that it attempts to sign in the user, but fails on the redirect. Any suggestions?
class SessionsController < ApplicationController
...
def create
user = User.authenticate(params[:session][:email],
params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
else
sign_in user
redirect_to user_routes_path
end
end
...
end
For some reason, the stack trace wasn't displayed when I redirect to user_routes_path, so I have it direct to root_path and the same thing happens. Here is the trace for that:
app/views/layouts/_header.html.erb:3:in
`_app_views_layouts__header_html_erb___917786942_46449696_315190'
app/views/layouts/application.html.erb:11:in
`_app_views_layouts_application_html_erb__423035099_46500948_0'
I will give it a try, after reading Fernandez: The rails 3 way about redirect_to.
When you look at the output from rake routes, you have the output:
user_routes GET /users/:user_id/routes(.:format) {:action =>"index", :controller=>"routes"}
The methods you may use to that route are:
user_routes_url: Full URL (with protocol and everything)
user_routes_path: Relative URL to the host
But your user_routes tells you another thing: the URL has to contain a user_id, and this user_id has to come from somewhere. So to call the different url and path methods, you have to look at the arguments:
users_path: no argument, shows all users
user_path(#user): one argument, because the information about the user is needed. Could be the user, or the user_id
`user_routes_path(#user): needs the user, so that all routes (index view) for one user could be shown.
So include in you source code in the controller:
...
else
sign_in user
redirect_to user_routes_path(user)
end
...
I don't understand the error message you have appended, but I think you should first correct the path call.

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

Routes in Rails3 - Controller and routes for 2 post functions

I'm trying to write an app in rails 3 and I'm having some trouble figuring out the routes and controllers for a test that I want the user to take. The basic requirements for this app are:
Users, Tests and Questions are all in separate models.
A User has_many Tests. A Test has_many Questions
Provide a link on the user_profile page to /test/new to create the test record.
Provide a link on /test/new to /test/:id/part1 (where :id is the test_id) so that the user can complete the first part of the test. Questions will be retrieved from the db and presented on this page.
Provide a link on /test/:id/part1 to /test/:id/part2 so that the user can complete the second part of the test. Again, questions are retrieved from the db.
Provide a link on /test/:id/part2 to submit the test and return to the user's profile.
I've completed the models, which even pass their tests, so I think I have finished parts 1 and 2.
user.rb
Class User < ActiveRecord::Base
has_many :tests
end
test.rb
Class Test < ActiveRecord::Base
belongs_to :user
has_many :questions
end
question.rb
Class Question < ActiveRecrod::Base
belongs_to :test
end
My issues start when I try to put these models together using routes and controllers.
routes.rb
resources :users
resources :tests do
member do
post 'part1'
post 'part2'
end
end
users/show.html.erb
<%= link_to "Start The Test", new_test_path %>
tests/new.html.erb
<%= link_to "Part 1", part1_test_path(#test) %>
tests_controler.rb
class TestsController < ApplicationController
def new
#test = Test.new(current_user)
end
def part1
# still just a stub
end
end
I'm getting this error when I click on the link to take Part 1 of the test:
No route matches {:action=>"part1", :controller=>"tests", :id=>#<Test id: nil, taken_at: nil, user_id: nil, created_at: nil, updated_at: nil>}
Any help on this would be greatly appreciated.
By defining a member of the routes it's expecting an existent test, ie. one which is saved and has an id.
e.g.
part1_test_path = /test/123/part1
What you need is a collection route.
resources :tests do
collection do
post 'part1'
end
member do
post 'part2'
end
end
e.g.
part1_test_path = /test/part1
edit
Suggested solution:
resources :test, :path_names => { :new => 'part_1', :edit => 'part_2' } *1
def new
#test = Test.new
#new view
form_for #test do
...
def create
#test = Test.new params[:test]
if #test.save
redirect_to edit_test_path #test
def edit
#test = Test.find params[:id]
#edit view
form_for #test do
def update
#test = Test.find params[:id]
if #test.update_attributes params[:test]
redirect_to test_path #test
def show # test results
#test = Test.find params[:id]
if #test.incomplete *2
redirect_to edit_test_path #test
*1 See rails guide on routing. This will give you urls like this
test/part1
test/123/part2
You should put all of your validation in the model; your requirements of test data. Conditional validation will be required, depending on whether it's a new_record? or not ie if you're at part 1 or 2.
*2
add a method to your model which checks test completeness.
def incomplete
self.some_test_field.blank?
Let me know if you don't understand anything.