0

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?

marcamillion
  • 32,933
  • 55
  • 189
  • 380

1 Answers1

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
Surya
  • 15,703
  • 3
  • 51
  • 74
  • So assume I don't want to do the `status` version, I just want to redirect to the `Post#Index` and sort it there....all I would do is do a case to check the params in that `index` action? – marcamillion Oct 27 '14 at 08:01
  • Sure, you can change `posts#status` to `posts#index` and put them above `resources :posts` to give them priority. But, again as I said, in future you or other developers will be confused with having routes like that. – Surya Oct 27 '14 at 08:04
  • So what would you suggest is a more Rails-way to approach this problem? Assume I want future maintenance to be simple. One way that occurred to me was using a tagging gem, but given that these attributes are literally enum values on my model that seems like overkill. – marcamillion Oct 27 '14 at 08:23
  • Also by any chance, you familiar with Turbolinks? I have a bounty on an open question that I would love to give away - http://stackoverflow.com/questions/26459182/how-do-i-make-this-js-turbolinks-friendly – marcamillion Oct 27 '14 at 08:24
  • 1
    @marcamillion: I already have put Rails-way in the recommended section of the answer. Future maintenance will be easy too, because all you'll have to do is to add/remove new route/method if your requirement changes, also is I stated, by any chance if your logic for showing confirmed and corroborated or any other status changes, it won't be too much of work to get that done since each code is isolated. **Side note**: This is DRY(sometimes we get confused with DRY and the simplicity of code), however, if you want then you can create a scope to accept `params[:status]` and show posts accordingly. – Surya Oct 27 '14 at 08:57
  • None of these seem to work. I am still getting this error: `Couldn't find Post without an ID`. I tried the declaration of `resources posts` both before and after my other routes. Also, I tried both with `status` as an action in the controller and I tried it with actions for each (i.e. `confirmed`, `unconfirmed`, `corroborated`). Still no dice. – marcamillion Oct 31 '14 at 23:21
  • ok that seems to have worked. One last request, how do I create a dynamic link to the appropriate path without using a bunch of if statements. I am updating the question with more info. – marcamillion Nov 02 '14 at 05:58