Rails 3 Engine Problem with Routes - ruby-on-rails

I have an engine, with this routes file:
Rails.application.routes.draw do
resources :comments, :controller => 'opinio/comments'
end
When I run the rake routes task, I get the correct output
comments GET /comments(.:format) {:action=>"index", :controller=>"opinio/comments"}
POST /comments(.:format) {:action=>"create", :controller=>"opinio/comments"}
new_comment GET /comments/new(.:format) {:action=>"new", :controller=>"opinio/comments"}
edit_comment GET /comments/:id/edit(.:format) {:action=>"edit", :controller=>"opinio/comments"}
comment GET /comments/:id(.:format) {:action=>"show", :controller=>"opinio/comments"}
PUT /comments/:id(.:format) {:action=>"update", :controller=>"opinio/comments"}
DELETE /comments/:id(.:format) {:action=>"destroy", :controller=>"opinio/comments"}
My controller is pretty simple:
class Opinio::CommentsController < ApplicationController
include Opinio::Controllers::InternalHelpers
def index
resource.comments.page(params[:page])
end
def create
#comment = resource.comments.build(params[:comment])
#comment.user = current_user
if #comment.save
flash[:notice] = I18n.translate('opinio.comment.sent', :default => "Comment sent successfully.")
else
flash[:error] = I18n.translate('opinio.comment.error', :default => "Error sending the comment.")
end
end
end
But when I try using any action that goes to the engine's controller I get the following error:
uninitialized constant Comment::CommentsController
I sincerely don't know where Rails is magically adding this Comment namespace on the controller, and I don't have a clue of how to solve this.

Wow, this deserves an answer so nobody ever do such stupidity like I did.
Basically, I added this to my engine's module:
mattr_accessor :name
##name = "Comment"
and internally, there is already a method name on every module, which I accidentally overrided, and causing all the errors. AS tried to load the missing constant, but when called for name inside my Opinio model, it got "Comment" instead of Opinio.
A reminder for myself and any others out there.
Don`t use obvious names and attributes without checking if they already exist first.

Related

How to get url_for working with a module?

I have mounted FullcalendarEngine in my routes.rb:
mount FullcalendarEngine::Engine , at: "/fullcalendar_engine"
Unfortunately, even though I have this in routes.rb:
resources :events, module: 'fullcalendar_engine'
And the generated routes:
fullcalendar_engine_path /fullcalendar_engine FullcalendarEngine::Engine
events_path GET /events/index(.:format) fullcalendar_engine/events#index
event_path GET /events/:id(.:format) fullcalendar_engine/events#show
events_path GET /events(.:format) fullcalendar_engine/events#index
POST /events(.:format) fullcalendar_engine/events#create
new_event_path GET /events/new(.:format) fullcalendar_engine/events#new
edit_event_path GET /events/:id/edit(.:format) fullcalendar_engine/events#edit
event_path GET /events/:id(.:format) fullcalendar_engine/events#show
PATCH /events/:id(.:format) fullcalendar_engine/events#update
PUT /events/:id(.:format) fullcalendar_engine/events#update
DELETE /events/:id(.:format) fullcalendar_engine/events#destroy
I still cannot use url_for with it (which I need to do in order to get it working with will_paginate):
Error:
RailsDevise::Application.routes.url_for({ controller: 'events', action: 'index'})
=> ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"events"}
FullcalendarEngine::Engine.routes.url_for({ controller: 'events', action: 'index'})
=> ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"events"}
When I inspect the module's routes:
FullcalendarEngine::Engine.routes.routes.collect {|journey| journey.defaults }
=> [{:controller=>"fullcalendar_engine/events", :action=>"index"}, {:action=>"get_events", :controller=>"fullcalendar_engine/events"}, {:action=>"move", :controller=>"fullcalendar_engine/events"}, {:action=>"resize", :controller=>"fullcalendar_engine/events"}, {:action=>"index", :controller=>"fullcalendar_engine/events"}, {:action=>"create", :controller=>"fullcalendar_engine/events"}, {:action=>"new", :controller=>"fullcalendar_engine/events"}, {:action=>"edit", :controller=>"fullcalendar_engine/events"}, {:action=>"show", :controller=>"fullcalendar_engine/events"}, {:action=>"update", :controller=>"fullcalendar_engine/events"}, {:action=>"update", :controller=>"fullcalendar_engine/events"}, {:action=>"destroy", :controller=>"fullcalendar_engine/events"}]
Notice it has this:
#defaults={:controller=>"fullcalendar_engine/events", :action=>"index"}
Notice the namespace for the controller value. This does not work:
FullcalendarEngine::Engine.routes.url_for({:controller=>"events", :action=>"index"})
=> ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"events"}
But if I try to namespace it, it gives this error:
FullcalendarEngine::Engine.routes.url_for({:controller=>"fullcalendar_engine/events", :action=>"index"})
ArgumentError: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
Even adding a host generates the wrong url:
FullcalendarEngine::Engine.routes.url_for({:controller=>"fullcalendar_engine/events", :action=>"index", host: "localhost"})
=> "http://localhost/fullcalendar_engine/"
How can I get url_for to recognize the route?
Your routes expect an id parameter, so you need to give it one:
url_for(controller: 'events', action: 'show', id: 5) # < Notice the `id`
Additionally, the routes you posted do not include a route for the index, so it may not exist. If it did, it probably wouldn't require an id
For anyone else that encounters this. I stepped through the Rails code and this is what fixed it for me:
FullcalendarEngine::Engine.routes.url_for({:action=>"index", host: "localhost"}, 'fullcalendar_engine_path')
It looks that a question asked 5 years ago may still be pretty relevant. I'm hoping that you have already found a solution. :)
I have been hit by similar problem in Rails 6.0.3 (a little old, isn't it?), where the url_for was exploding in a very similar manner for a controller from a non-isolated engine, inheriting from ::ApplicationController
I stumbled upon this chain of issues and comments:
https://github.com/rails/rails/pull/37927#issuecomment-695934757
https://github.com/rails/rails/pull/40263
https://github.com/rails/rails/pull/40264 (closed as stale)
https://github.com/rails/rails/pull/41463 (merged, backported to Rails 6.1)
Long story short, this ugly hack may save your hide for a while, until you upgrade to newer Rails:
class Foo::BarController < ::ApplicationController
# your normal stuff
private
def _routes # <- this overrides an inherited method, so keep the name!
#_routes || my_engine_routes
end
def my_engine_routes # <- and this you can name as you want
#_my_engine_routes ||= Foo::Engine.routes
end
end
Just be sure to carefully test whether it works well for your case, and do not forget to remove it once you upgrade. It may cause nasty conflicts with some deep magic in Rails!

Delete call on nested resource does not work for integration test?

I have been beating my brain against the wall on this problem I am having with Rails 3.2. I have a simple integration test that does this.
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
fixtures :posts, :comments
test "should delete comment" do
post = posts(:one)
comment = comments(:one)
delete post_comment_path, :post_id => post.id, :id => comment.id
assert_response :redirect
assert_assign (:post)
end
end
You may recognize the models from the Rails guide. Anyway, when I call rake:integration I end up with this.
test_should_delete_comment(UserFlowsTest):
ActionController::RoutingError: No route matches {:action=>"show", :controller=>"comments"}
C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/routing/route_set.rb:533:in `raise_routing_error'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/routing/route_set.rb:529:in `rescue in generate'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/routing/route_set.rb:521:in `generate'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/routing/route_set.rb:562:in `generate'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/routing/route_set.rb:587:in `url_for'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/routing/url_for.rb:148:in `url_for'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/routing/route_set.rb:213:in `post_comment_path'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/testing/integration.rb:382:in `method_missing'
C:/dev/blog/test/integration/user_flows_test.rb:9:in `block in <class:UserFlowsTest>'
My routes are defined as such.
root / home#index
posts_search GET /posts/search(.:format) posts#search
home_index GET /home/index(.:format) home#index
post_comments GET /posts/:post_id/comments(.:format) comments#index
POST /posts/:post_id/comments(.:format) comments#create
new_post_comment GET /posts/:post_id/comments/new(.:format) comments#new
edit_post_comment GET /posts/:post_id/comments/:id/edit(.:format) comments#edit
post_comment GET /posts/:post_id/comments/:id(.:format) comments#show
PUT /posts/:post_id/comments/:id(.:format) comments#update
DELETE /posts/:post_id/comments/:id(.:format) comments#destroy
posts GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
new_post GET /posts/new(.:format) posts#new
edit_post GET /posts/:id/edit(.:format) posts#edit
post GET /posts/:id(.:format) posts#show
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
What am I doing wrong?
EDIT:
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment])
redirect_to post_path(#post)
end
def destroy
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.destroy
redirect_to post_path(#post)
end
end
Not sure what could be wrong here.
I haven't used Test::Unit regularly in quite a while (using RSpec these days) but I don't recall having much need for integration testing. The bulk of the tests were unit and functional.
This particular test isn't really an integration test. It's not testing the flow of information across domains. It's simply ensuring that there is a redirect after destroying a nested resource. Here's what I'd try. Pardon any outdated syntax. Assuming a file test/functional/comments_controller_test.rb:
require 'test_helper'
class CommentsControllerTest < ActionController::TestCase
fixtures :posts, :comments
def test_destroy
post = posts(:one)
comment = comments(:one)
delete :destroy, :id => comment.id, :post_id => post.id
assert_redirected_to post_path(post)
# and whatever else you want to test
end
end

Ruby on Rails: link_to action, no route matches

I'm getting into Rails and trying to add a "vote" feature on a blog setup from here: http://guides.rubyonrails.org/getting_started.html
In app/controllers/posts_controller.rb I created this:
def incvotes
#post = Post.find(params[:id])
post.update_attributes(:upvotes => 1 )
format.html { redirect_to(#post, :notice => 'Vote counted.') }
format.xml { head :ok }
end
In app/views/posts/index.html.erb I created this:
<%= link_to 'Vote', :controller => "posts", :action => "incvotes", :id => post.id %>
But the link is giving the error
No route matches {:controller=>"posts", :action=>"incvotes", :id=>1}
I'm missing something here, but not sure what.
rake routes:
incvotes_post POST /posts/:id/incvotes(.:format) {:action=>"incvotes", :controller=>"posts"}
posts GET /posts(.:format) {:action=>"index", :controller=>"posts"}
POST /posts(.:format) {:action=>"create", :controller=>"posts"}
new_post GET /posts/new(.:format) {:action=>"new", :controller=>"posts"}
edit_post GET /posts/:id/edit(.:format) {:action=>"edit", :controller=>"posts"}
post GET /posts/:id(.:format) {:action=>"show", :controller=>"posts"}
PUT /posts/:id(.:format) {:action=>"update", :controller=>"posts"}
DELETE /posts/:id(.:format) {:action=>"destroy", :controller=>"posts"}
home_index GET /home/index(.:format) {:action=>"index", :controller=>"home"}
root /(.:format) {:action=>"index", :controller=>"home"}
try
= link_to "vote", incvotes_post_path(post), :method=>:post
and if that doesn't work, try changing the method to :put
My guess is that you probably do not have a definition in your routes file for the action you just defined in the controller. Both an action in the controller and an action in the routes file must be defined for Rails to generate urls correctly.
Your routes file probably has something like this:
resources :posts
But you want to add more than the standard actions generated by the resources keyword, so try something like this:
resources :posts do
member do
post 'incvotes'
end
end
This tells routes that you have another action in your posts controller called incvotes that accepts HTTP post requests as long as they are pointed at a member route with the correct action (/posts/14 is a member route, while /posts/ is a 'collection' route). So you will have a new route probably like /posts/14/incvotes that you can post a form to and everything should start working properly.
EDIT:
Actually I guess since you are just adding 1 to an attribute on a model, you don't need a POST action (which are normally associated with posting forms as with create and update). To send a post, you might need to change the HTML in the view to include a form and have it post to the correct url. So you can try that, or you can change your routes file to read get 'incvotes' instead of post 'incvotes'. Sorry for the confusion, hope that helps!
The incvotes_post route only accepts a HTTP POST, and a link always produces a HTTP GET.
Use a form with a button instead (or do a POST using AJAX).
Try using button_to instead link_to:
In your view:
<%= button_to 'Vote', incvotes_post_path(post) %>
In your config/routes.rb add the route to incvotes action as post:
resources :posts do
member do
post 'incvotes'
end
end
And in your controller, create the incvotes action:
def incvotes
# Something
redirect_to :posts
end

Rails: URL after validation fails when creating new records via form

Lets say I am creating a new Foo using a form and a standard Rails restful controller, which looks something like this:
class FoosController < ApplicationController
...
def index
#foos = Foo.all
end
def new
#foo = Foo.new
end
def create
#foo = Foo.create(params[:foo])
if #foo.save
redirect_to foos_path, :notice => 'Created a foo.'
else
render 'new'
end
end
...
end
So, if I use the standard restful controller (as above), then when I'm creating the Foo I am at example.com/foos/new, and if I submit the form and it saves correctly I'm at example.com/foos showing the index action. However, if the form is not filled correctly the form is rendered again and error messages are shown. This is all plain vanilla.
However, if errors are shown, the form page will be rendered but the URL will be example.com/foos, because the CREATE action posts to that url. However, one would expect to find Foos#index at example.com/foos, not the form they just submitted now with error messages added.
This seems to be Rails standard behavior, but it doesn't make a lot of sense to me. Obviously I could redirect back to new instead of rendering new from the create action, but the problem with that is the error messages etc. would be lost along with the partially complete Foos in memory.
Is there a clean solution for this problem, a way to send people back to example.com/foos/new when there are errors in the new Foo form they submitted?
Thanks!
To answer your comment on another answer:
I'm wondering if there's a way, without rewriting the controller at all, to tell rails that you want the URL to match the rendered template, rather than the controller action that called it.
I don't think so; URLs are tied directly to routing, which is tied into a controller and action pair--the rendering layer doesn't touch it at all.
To answer your original question, here's information from another similar question I answered.
As you've found, by default when you specify resources :things, the POST path for creating a new thing is at /things. Here's the output for rake routes:
things GET /things(.:format) {:action=>"index", :controller=>"things"}
POST /things(.:format) {:action=>"create", :controller=>"things"}
new_thing GET /things/new(.:format) {:action=>"new", :controller=>"things"}
edit_thing GET /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
thing GET /things/:id(.:format) {:action=>"show", :controller=>"things"}
PUT /things/:id(.:format) {:action=>"update", :controller=>"things"}
DELETE /things/:id(.:format) {:action=>"destroy", :controller=>"things"}
It sounds like you want something more like this:
create_things POST /things/new(.:format) {:action=>"create", :controller=>"things"}
things GET /things(.:format) {:action=>"index", :controller=>"things"}
new_thing GET /things/new(.:format) {:action=>"new", :controller=>"things"}
edit_thing GET /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
thing GET /things/:id(.:format) {:action=>"show", :controller=>"things"}
PUT /things/:id(.:format) {:action=>"update", :controller=>"things"}
DELETE /things/:id(.:format) {:action=>"destroy", :controller=>"things"}
Although not recommended, you can get this result with the following route:
resources :things, :except => [ :create ] do
post "create" => "things#create", :as => :create, :path => 'new', :on => :collection
end
You would also need to modify your forms to make them POST to the correct path.
You could hook into rails routing by adding this in an initializer:
https://gist.github.com/903411
Then just put the regular resources in your routes.rb:
resources :users
It should create the routes and behaviour you are looking for.
You can set up the routing manually, if you're that concerned about what URL is going to show. For what you want, you can have a GET to /foos/new render your form, and a POST to the same URL do the creation:
map.with_options :controller => :foos do |foo|
foo.new_foo '/foos/new', :conditions => {:method => :get}, :action => :new
foo.create_foo '/foos/new', :conditions => {:method => :post}, :action => :create
foo.foos '/foos', :conditions => {:method => :get}, :action => :index
end
This should work without requiring any changes to your controller (yay!) - all three actions from your example are taken care of. The few disclaimers:
This is based on my routing for a 2.3.8 app - some syntax (semantics?) changes are probably required to get it into Rails 3 routing style.
My attempts to mix this style of routing with map.resources have failed horribly - unless you're more familiar with this than me, or Rails 3 routing is better (both easily possible), you'll have to do this for every route to the controller.
And finally, don't forget to add /:id, (.:format), etc. to the routes that need them (none in this example, but see #2).
Hope this helps!
Edit: One last thing - you'll need to hard-code the URL in your form_for helper on /foos/new.html.erb. Just add :url => create_foo_path, so Rails doesn't try to post to /foos, which it will by default (there might be a way to change the creation URL in the model, but I don't know of it, if there is one).
You could use Rack::Flash to store the parameters you wanted in the user's session and then redirect to your form url.
def create
#foo = Foo.new(params[:foo])
if #foo.save
redirect_to foos_path, :notice => 'Created a foo.'
else
flash[:foo] = params[:foo]
flash[:errors] = #foo.errors
redirect_to new_foo_path #sorry - can't remember the Rails convention for this route
end
end
def new
# in your view, output the contents of flash[:foo]
#foo = Foo.new(flash[:foo])
end

Rails 3 - Missing Index Paths?

I'm having a pretty weird problem with one of my rails apps. I think I'm probably doing something really silly that I just haven't been able to identify. My problem is that, I seem to be missing about half of my index paths.
For example, if my controller is "foos" for a model foo, I'll have the:
foos POST /foos(.:format) {:action=>"create", :controller=>"foos"}
But no GET option which would usually be as:
foos GET /foos(.:format) {:action=>"index", :controller=>"foos"}
Below I'll show you my actually code, to help me recover my missing index routes.
routes.rb:
resource :announcements, :controller => "announcements" do
resources :comments
member do
post 'vote'
end
end
routes for the announcements part:
announcements POST /announcements(.:format) {:action=>"create", :controller=>"announcements"}
new_announcements GET /announcements/new(.:format) {:action=>"new", :controller=>"announcements"}
edit_announcements GET /announcements/edit(.:format) {:action=>"edit", :controller=>"announcements"}
GET /announcements(.:format) {:action=>"show", :controller=>"announcements"}
PUT /announcements(.:format) {:action=>"update", :controller=>"announcements"}
DELETE /announcements(.:format) {:action=>"destroy", :controller=>"announcements"}
As you can see there is no get / index. In my controller, I have the simply index method defined...
def index
#announcements = Announcement.all
respond_to do |format|
format.html
format.xml { render :xml => #announcements }
end
end
I really don't understand why I don't have this index path. It's happening on several other controllers as well. Any help would be appreciated.
Edit: In the console, app.announcements_path returns a method missing error, in addition to the others that have missing index paths.
This is because you're using the singularized version of resources (resource). There is no index action route generated for these. You should change this to be the pluralized version, and remove :controller from the line too.

Resources