问题
So I have a Post
model, that has these scopes:
scope :unconfirmed, -> { where( status: "unconfirmed") }
scope :corroborated, -> { where( status: "corroborated") }
scope :confirmed, -> { where( status: "confirmed") }
What I want to do is when someone goes to posts/confirmed
it shows all the posts scoped to confirmed.
What is the most elegant way to do this? Also, how do I create a path for each scope in my routes.rb
?
Edit 1
I know one way to do this is to simply create an action in my posts controller for each enumerable scope (i.e. unconfirmed
, etc.).
But I would rather just do all of this in my index action - so I am rendering and using my Index view.
I have tried doing this in my Post#Index
:
if params[:unconfirmed]
@posts = Post.published.unconfirmed
end
But that just gives me a routing error.
This is the error:
Started GET "/unconfirmed" for 127.0.0.1 at 2014-10-27 02:29:51 -0500
Processing by PostsController#show as HTML
Parameters: {"id"=>"unconfirmed"}
Post Load (3.0ms) SELECT "posts".* FROM "posts" WHERE "posts"."publication_status" = 1 AND "posts"."slug" = 'unconfirmed' ORDER BY "posts"."id" ASC LIMIT 1
Post Load (1.7ms) SELECT "posts".* FROM "posts" INNER JOIN "friendly_id_slugs" ON "friendly_id_slugs"."sluggable_id" = "posts"."id" AND "friendly_id_slugs"."sluggable_type" = 'Post' WHERE "posts"."publication_status" = 1 AND ("friendly_id_slugs"."sluggable_type" = 'Post' AND "friendly_id_slugs"."slug" = 'unconfirmed') ORDER BY "posts"."id" ASC LIMIT 1
Completed 404 Not Found in 28ms
ActiveRecord::RecordNotFound - ActiveRecord::RecordNotFound:
friendly_id (5.0.4) lib/friendly_id/finder_methods.rb:23:in `find'
() myapp/controllers/posts_controller.rb:78:in `set_post'
The set_post
method is:
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.published.find(params[:id])
end
I am using friendly_id
for my URLs.
This is my routes.rb
:
get 'posts/:id' => redirect("/%{id}")
get '/:friendly_id', to: 'posts#show'
get 'posts/:friendly_id', to: 'posts#show'
Edit 2
I am still getting this error. I believe it to be the case because whenever I try to do /confirmed
or any of the other statuses, this is what my log looks like:
Started GET "/confirmed" for 127.0.0.1 at 2014-10-31 18:28:55 -0500
ActiveRecord::SchemaMigration Load (2.2ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by PostsController#show as HTML
Parameters: {"friendly_id"=>"confirmed"}
Completed 404 Not Found in 58ms
Which obeys this routing rule:
get '/:friendly_id', to: 'posts#show'
get 'posts/:friendly_id', to: 'posts#show'
It still does this even if I put the recommended routing rules for my resources post
at the end of my routes file, i.e. like this:
resources :posts do
collection do
get 'confirmed' => 'posts#index', status: 'confirmed'
get 'unconfirmed' => 'posts#index', status: 'unconfirmed'
get 'corroborated' => 'posts#index', status: 'corroborated'
end
end
root to: "posts#index"
How do I get the status routes to not be redirected to the posts#index
action?
Edit 3
See my full routes.rb
Rails.application.routes.draw do
%w[privacy terms].each do |page|
get page, controller: 'info', action: page
end
resources :locations
devise_for :users, :path_names => { :sign_up => "register",
:sign_in => "login",
:sign_out => "logout",
:settings => "settings" },
:controllers => { :confirmations => "confirmations" }
devise_scope :user do
get "login", :to => "devise/sessions#new"
get "register", :to => "devise/registrations#new"
get "settings", :to => "devise/registrations#edit"
delete "logout", :to => "devise/sessions#destroy"
end
resources :posts do
collection do
get 'confirmed' => 'posts#status', status: 'confirmed'
get 'unconfirmed' => 'posts#status', status: 'unconfirmed'
get 'corroborated' => 'posts#status', status: 'corroborated'
end
end
get 'posts/:id' => redirect("/%{id}")
get '/:friendly_id', to: 'posts#show'
get 'posts/:friendly_id', to: 'posts#show'
# This supports legacy URLs e.g:
# http://www.example.com/rbt/26766-algaj-pays-tribute-to-the-honourable-roger-clarke.html
get '/rbt/:name', to: redirect {|path_params, _| "/#{path_params[:name].gsub(/^\d+\-/, '')}" }
get ':name', to: 'posts#show'
root to: "posts#index"
end
Edit 4
The last bit of the puzzle is the original premise of the question, how do I create a link to that newly created path.
The tricky bit is that I want dynamically create the link_path based on the status.
For instance, as a result of the new routes rules, I now have confirmed_path, corroborated_path, unconfirmed_path
.
However, I am displaying them in my view like so:
<span class="post-status status label<%=render partial: "shared/color", locals: {post: post.status }%>"><%= post.status.try(:upcase) %></span>
I tried doing this:
<span class="post-status status label<%=render partial: "shared/color", locals: {post: post.status }%>"><%= link_to post.status.try(:upcase), post.status.path %></span>
But that gave me an undefined method path
error.
In a normal instance, I would simply do string interpolation and Ruby would render the current string. Given that Ruby is supposed to parse this path, I doubt that would work.
So how do I get that view to automagically generate confirmed_path, corroborated_path, unconfirmed_path
dynamically based on what post.status
is?
回答1:
As you see you are lost in routes. Here calling posts/confirmed
will go at posts#show
instead of your posts#index
method instead. The reason is action pack give priority of routes from top to bottom, and since your routes are defined like this:
resources 'posts'
get 'posts/:id' => redirect("/%{id}")
get '/:friendly_id', to: 'posts#show'
get 'posts/:friendly_id', to: 'posts#show'
All posts/unconfirmed
, posts/confirmed
, etc will never make to the index method as it will be matched with posts#show
, and then with get 'posts/:friendly_id', to: 'posts#show'
.
Now, even if you move your all get
routes to top, it will never make it to other methods you might wanna define later, as all posts/unconfirmed
, posts/confirmed
, etc will be matched with get 'posts/:id' => redirect("/%{id}")
and will be redirected as /confirmed
, /unconfirmed
, etc.
Recommended way is to have separate methods for individual routes, since in future you also might want to have a different behavior for confirmed posts than corroborated posts. For that your routes will look like this:
resources :posts do
collection do
get 'confirmed'
get 'unconfirmed'
get 'corroborated'
end
end
Running $ rake routes|grep posts
will give:
confirmed_posts GET /posts/confirmed(.:format) posts#confirmed
unconfirmed_posts GET /posts/unconfirmed(.:format) posts#unconfirmed
corroborated_posts GET /posts/corroborated(.:format) posts#corroborated
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
PATCH /posts/:id(.:format) posts#update
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
and then you create separate methods, i.e. confirmed
, unconfirmed
, corroborated
etc.
However, you can always point all these routes to one like this(which is not recommended):
resources :posts do
collection do
get 'confirmed' => 'posts#status', status: 'confirmed'
get 'unconfirmed' => 'posts#status', status: 'unconfirmed'
get 'corroborated' => 'posts#status', status: 'corroborated'
end
end
Then in your PostsController
:
def status
@posts = Post.published.where(status: params[:status])
end
UPDATE: Change your routes.rb to this -
Rails.application.routes.draw do
%w[privacy terms].each do |page|
get page, controller: 'info', action: page
end
resources :locations
devise_for :users, :path_names => { :sign_up => "register",
:sign_in => "login",
:sign_out => "logout",
:settings => "settings" },
:controllers => { :confirmations => "confirmations" }
devise_scope :user do
get "login", :to => "devise/sessions#new"
get "register", :to => "devise/registrations#new"
get "settings", :to => "devise/registrations#edit"
delete "logout", :to => "devise/sessions#destroy"
end
resources :posts
get '/confirmed' => 'posts#status', status: 'confirmed', as: :confirmed
get '/unconfirmed' => 'posts#status', status: 'unconfirmed', as: :unconfirmed
get '/corroborated' => 'posts#status', status: 'corroborated', as: :corroborated
get 'posts/:id' => redirect("/%{id}")
get '/:friendly_id', to: 'posts#show'
get 'posts/:friendly_id', to: 'posts#show'
# This supports legacy URLs e.g:
# http://www.example.com/rbt/26766-algaj-pays-tribute-to-the-honourable-roger-clarke.html
get '/rbt/:name', to: redirect {|path_params, _| "/#{path_params[:name].gsub(/^\d+\-/, '')}" }
get ':name', to: 'posts#show'
root to: "posts#index"
end
and make sure you have status
method in your PostsController
:
def status
# your code for fetching posts goes here!
render text: params[:status] # this is to show an example that `status` is set properly
end
来源:https://stackoverflow.com/questions/26581269/how-do-i-create-a-link-route-directly-to-a-scope