Passing attributes in test url - Rspec / Addressable gem

548 views Asked by At

From a View file I pass attributes in a url:

%= link_to piece_path(@current_piece, file: file, rank: rank), method: :patch do %>

this gives urls like http://localhost:3030/pieces/%23?file=8&rank=8

I need to extract the value of file and rank from this url, to update the database fields (coordinates of a chess piece after a move).

In Controller I'm trying to use the Addressable gem:

def update
    (some code)
    current_piece.update_attributes(piece_params)
    (more code)
end

private

def piece_params
    uri = Addressable::URI.parse(request.original_url)
    file = uri.query_values.first    ## I don't know if "first" 
    rank = uri.query_values.last     ## and "last" methods will work
    params.require(:piece).permit({:file => file, :rank => rank})
end

When I inspect uri I get: #<Addressable::URI:0x3fa7f21cc01c URI:http://test.host/pieces/2> There is no hash of attributes trailing the url. Thus uri.query_values returns nil. I don't know how to mirror such thing in the test.

The error message:

1) PiecesController pieces#update should update the file and rank of the chess piece when moved
     Failure/Error: file = uri.query_values.first

     NoMethodError:
       undefined method `first' for nil:NilClass

In Controller_spec:

describe "pieces#update" do
    it "should update the file and rank of the chess piece when moved" do
      piece = FactoryGirl.create(:piece)
      sign_in piece.user
      patch :update, params: { id: piece.id, piece: { file: 3, rank: 3}}
      piece.reload
      expect(piece.file).to eq 3
      expect(piece.rank).to eq 3
   end

I can't check if the logic works from the localhost browser (I don't have pieces objects at the moment so I run into errors). Also working on that.

My question regards the test; however if there are suggestions to extract the attributes from the url in different ways I'm all ears!

3

There are 3 answers

0
Claire D On BEST ANSWER

button_to with nested attributes works; in the view file:

<%= button_to piece_path(@current_piece), method: :patch, params: {piece: {file: file, rank: rank}} do %>

And keep it simple in the controller:

def piece_params
  params.require(:piece).permit(:rank, :file)
end
1
NM Pennypacker On

If your URL is http://localhost:3030/pieces/%23?file=8&rank=8 you should be able to do:

def piece_params
    params.require(:piece).permit(:rank, :file)
end

and then access them in your action via params[:rank] and params[:file] I usually use params[:file].present? to make sure that the params are there before I try to assign the value. Something like this should work:

p = {}
if params[:rank].present?
  p[:rank] = params[:rank]
end
if params[:file].present?
  p[:file] = params[:file]
end
current_piece.update_attributes(p)

FWIW, you probably shouldn't use the URL string to pass params to a PATCH/PUT request. You might consider passing them via a form or something.

2
max On

You don't need to manually parse the request URI to get query params in Rails.

Rails is built on top of the Rack CGI interface which parses the request URI and request body and provides the parameters as the params hash.

For example if you have:

resources :things

class ThingsController < ApplicationController
  def index
    puts params.inspect
  end
end

Requesting /things?foo=1&bar=2 would output something like:

{
  foo: 1,
  bar: 2,
  action: "index",
  controller: "things"
}

link_to method: :patch uses JQuery UJS to let you use a <a> element to send requests with other methods than GET. It does this by attaching a javascript handler that creates a form and sends it to the URI in the HREF attribute.

However unlike "normal forms" in rails the params are not nested:

<%= link_to piece_path(@current_piece, file: file, rank: rank), method: :patch do %>

Will give the following params hash:

{
  file: 1,
  rank: 2
}

Not

{
  piece: {
    file: 1,
    rank: 2
  } 
}

If you want nested keys you would have to provide the params as:

<%= link_to piece_path(@current_piece, "piece[file]" => file, "piece[rank]" => rank), method: :patch do %>