2

I have a routes question concerning Rails 5. I have this in my config/routes.rb file

  resources :votes

The "show" method in my VotesController can take an ID of either a numeric or string form.

  def show
    id = params[:id]
    if id.present?
      # If they entered a name
      if id.to_i == 0
        name = id.gsub "_", " "
        @person = Person.find_by_name(name)
      else
        @person = Person.find_by_id(id)
      end

I used to be able to construct this link_to to link to the method to generate a link with a numeric ID ...

<%= link_to person.name, vote_path(person), :class => 'unvotedPersonLink' %>

However, I would like to generate a link with a string value for the ID, as defined by a method in my model called "person.seo_name". So I tried this

<%= link_to person.name, vote_path(:id => person.seo_name), :class => 'unvotedPersonLink' %>

but got the error

No route matches {:action=>"show", :controller=>"votes", :id=>nil} missing required keys: [:id]

How can I build my link_to tag so that it passes in my desired "string" parameter instead of a numeric one?

Marvin Fischer
  • 2,552
  • 3
  • 23
  • 34
  • make sure seo_name mehod is not returning nil – PGill Feb 21 '18 at 19:53
  • `vote_path(:id => person.seo_name)` should work as long as `seo_name` has a value. Try commenting out all other routes except `resources :votes` to see if another route definition is interfering. – Arctodus Feb 22 '18 at 12:19
  • You should just be able to do `vote_path(person.seo_name)` ... you don't need to (and shouldn't) specify `:id =>...` as that means the first parameter(the linked object) is `nil`. – SteveTurczyn Feb 28 '18 at 23:37

5 Answers5

1

You can pass in whatever you want. If the error is telling you that the ID you're passing is nil, then your seo_name attribute for the Person you're using is nil.

As long as vote_path is expecting an ID, which you can check via rake routes | grep vote, then this will work fine for you as far as generating a link goes:

<%= link_to person.name, vote_path(person.seo_name) %>

But keep in mind that it'll require you to make the necessary adjustments in your controller to search your persons table based on the seo_name column, instead of id.


Also, I highly recommend that you utilize a gem called friendly_id for what you're trying to accomplish. It'll make things a lot cleaner and DRYer, as you can use it easily in any model. And you can easily use either the ID or a URL-friendly slug to query your table, simply by adding friendly to the query:

Person.friendly.find(1)

# OR #

Person.friendly.find('this-is-a-url-friendly-string')
jeffdill2
  • 3,968
  • 2
  • 30
  • 47
  • I tried changing my "vote_path(person)" block to what you have -- "vote_path(person.seo_name)", but I still get the error, "No route matches {:action=>"show", :controller=>"votes", :id=>nil} missing required keys: [:id]". –  Feb 12 '18 at 20:51
  • I have verified through debugging that "person.seo_name" is indeed not nil. –  Feb 12 '18 at 21:42
0

In your view

<%= link_to person.name, vote_path(person), :class => 'unvotedPersonLink' %>

or

<%= link_to person.name, vote_path(id: person.seo_name), :class => 'unvotedPersonLink' %>

in your controller

@person = Person.find_by(id: params[:id]) || Person.find_by(name: params[:id])

make sure seo_name method is not returning nil

PGill
  • 3,373
  • 18
  • 21
  • Am I supposed to be doing something with my config/routes.rb file? When I put in "<%= link_to person.name, vote_path(id: person.seo_name), :class => 'unvotedPersonLink' %>", I get the error, "No route matches {:action=>"show", :controller=>"votes", :id=>nil} missing required keys: [:id]" upon loading the page. –  Feb 21 '18 at 20:12
  • is your controller name spaced? – PGill Feb 21 '18 at 20:31
  • The votes controller? THe file path is "app/controllers/votes_controller.rb" and the first line in the file reads "class VotesController < ApplicationController". –  Feb 21 '18 at 20:56
  • No @Natalia you do not need to do anything with your route. Just make sure in your database person do have seo_name. If it doesn't then this error will come. – Manishh Feb 22 '18 at 06:00
0

The exception is coming from vote_path, so we can focus on that and not worry about link_to or the controller.

When I'm having route problems like this, the first thing I do is run rake routes (or nowadays rails routes) to make sure I've got the right name. Second I play around in the console with the app variable. For example:

> app.vote_path(1)
 => "/votes/1"
> app.vote_path(id: 1)
 => "/votes/1"
> app.vote_path("foo")
 => "/votes/foo"
> app.vote_path(id: "foo")
 => "/votes/foo"
> app.vote_path(nil)
ActionController::UrlGenerationError: No route matches {:action=>"show", :controller=>"votes", :id=>nil}, possible unmatched constraints: [:id]
> app.vote_path(id: nil)
ActionController::UrlGenerationError: No route matches {:action=>"show", :controller=>"votes", :id=>nil}, possible unmatched constraints: [:id]

You say elsewhere you've checked already that person.seo_name is not returning nil, but I would check again, but it really looks like that's the problem. Try this:

p = Person.find 5   # or whatever is the numeric id
app.vote_path(p.seo_name)
Paul A Jungwirth
  • 23,504
  • 14
  • 74
  • 93
0

The params can contain any information, but you have to process them in the controller based on your need. By default, the routes requires to have an params called :id. If you want other name to be used in your route you can override the default resource identifier :id

Check the rails routing

Make sure the person.seo_name is not returning nil. So, you can use before_save callback in Person model. There, you can set the seo_name field with specific data.

class Subscription < ActiveRecord::Base
   before_save :populate_seo_name, on: :create

   private
     def populate_seo_name
       self.seo_name = ...
     end
end
mmsilviu
  • 1,211
  • 15
  • 25
-1

This has been answered in the past https://stackoverflow.com/a/26600064/4652075.

You are passing a string to a field that is expecting an integer and it is being rendered null. You should be passing a string value to a field that is expecting a string. If your "votes" model is generated by ActiveRecord then it is likely an integer.

In your case:

class Person < ApplicationRecord
  def to_param
    identifier
  end

  # or
  alias_method :to_param, :identifier
end

class VotesController < ApplicationController
  before_action :set_voter

  def show end;

  private

  def set_voter
    if !params[:id]?
      @person = Person.find_by(identifier: params[:seo_name])
    else
      @person = Person.find(params[:id])
    end
  end
end

# in the view
<%= link_to person.name, votes_path(identifier: person.seo_name), class: "whatever" %>

Should work.