Passing attributes in test url - Rspec / Addressable gem - ruby-on-rails

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!

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 %>

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.

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

Related

How can I dynamically set a search form with a different route in Rails views?

I currently have a very simple form for search written in HAML:
%form.search{ method: 'get', action: '/users/search' }
...
What would be the correct rails conventions for rendering a different search route based on the model that the controller sets in an instance variable when rendering this view?
I found this blog post, but this code <%= form_tag(recipes_path, :method => "get" is not generic enough for me. I would like to set this value, recipes_path, based on the model that the controller is collaborating with when it renders this view. The search form could be used across multiple controllers with their own search action. My app can search on different pages for different models.
I can definitely come up with a way to do it, but I would like to know the 'right' way or I suppose the 'rails' way of dynamically setting the form action to a different controller action based on the data that the form will be searching against.
I don't know what the 'right' or 'rails' way of doing this is. (But, it sure isn't hand-crafting a form with %form.)
In my apps, I tend to only have one form partial that looks something like this:
app/views/widgets/form
- #presenter = local_assigns[:presenter] if local_assigns[:presenter]
= form_tag #presenter.form_path, remote: true, id: #presenter.form_id, class: #presenter.form_classes, data: #presenter.form_data, method: #presenter.form_method do
= #presenter.form_inner
In my presenter_base.rb file, I have something like this:
class PresenterBase
def render_partial
render(partial: "#{file_name}", locals: {presenter: self})
end
def render_form
render_partial 'widgets/form'
end
end
So, to render the form in a FooPresenter, I might do something like:
class FooPresenter < PresenterBase
def present
render_form
end
def form_path
some_form_path(and: :maybe, some: :query_params)
end
def form_id
'my-cool-form'
end
def form_classes
'some cool classes'
end
def form_data
{some: :form, data: :here}
end
def form_method
:post
end
def form_inner
...
end
end
Naturally, there's more to it than just that (like, how I get a plain old ruby object to render). But, that should give you a sense of one way of doing it.
A simple way if there are no complications and you follow the conventions, can be something like this
%form.search{ method: 'get', action: "/#{controller_name}/search" }
so if you are in users_controller, it will print "users", if you are in static_pages_controller, it will show "static_pages" and so on.

Construct the path that links to the customized method in controllers

I have a non-standard method listByName in the OrdersController, as shown below.
class OrdersController < ApplicationController
def index
#orders=Order.all
end
def listByName(name)
#orders=Order.find_by(:name=>name)
render index
end
end
I want to create some links that can be routed to the OrdersController#listByName method.
<%= link_to 'listByName', listByName_path %>
My problem is how to construct the path in the html.erb pageļ¼Œ and how to write the router.rb?
Thanks
See the routing guide. Your question is answered in 1.1 and 1.2.
1.1 Connecting URLs to Code When your Rails application receives an incoming request for:
GET /patients/17
it asks the router to match it to a controller
action. If the first matching route is:
get '/patients/:id', to: 'patients#show'
the request is dispatched to
the patients controller's show action with { id: '17' } in params.
1.2 Generating Paths and URLs from Code You can also generate paths and URLs. If the route above is modified to be:
get '/patients/:id', to: 'patients#show', as: 'patient'
and your application contains this code in the controller:
#patient = Patient.find(17)
and this in the corresponding view:
<%= link_to 'Patient Record', patient_path(#patient) %> then the
router will generate the path /patients/17. This reduces the
brittleness of your view and makes your code easier to understand.
Note that the id does not need to be specified in the route helper.

Forward parameters in the specific order (using link_to helper)

I want to forward parameters from one view to the another one, and this is what I managed to do. But I want to pass those parameters in the specific order. On first request, after I send the form, I get the params in the order that I want. For example:
http://www.example.com/someurl?project_id=1&start_date=2016-01-10&end_date=2016-01-20
Then, after I have those params in the view, I generate a link with link_to helper, in this kind of a way:
= link_to "link text", some_specific_path(some_id, {"project_id"=>"1", "start_date"=>"2016-01-10", "end_date"=>"2016-01-20"})
But then, the link will be generated as:
http://www.example.com/someurl?end_date=2016-01-20project_id=1&start_date=2016-01-10
So, the problem is - when I send a form, parameters get added to the url in the order of how they appear in the form. But, when you generate a link with link_to helper and path helper, then parameters are always added in the alphabetical order, no matter how they actually appear.
It is Rails bug https://github.com/rails/rails/issues/1146
also, consider send params between request in sessions.
Ok, my solution for you, but it's realy urly:
hash = {"project_id"=>"1", "start_date"=>"2016-01-10", "end_date"=>"2016-01-20"}
query_string = hash.map{|k,v| "#{k}=#{v}"}.join("&")
link_to "link text", some_specific_path(some_id) + "?" + query_string
It's good idea to define a helper here:
module ApplicationHelper
def link_to_with_sorted_params(text, path, _params)
prepared_params = _params.map { |k,v| "#{k}=#{v}" }.join("&")
prepared_link = "#{path}?#{prepared_params}"
link_to text, prepared_link
end
end
Call it
=link_to_with_sorted_params("hi", users_path, {"user" => 1, "res" => 2})
#=> hi

rails I18n wrong parameter order in route (id as locale and id is nil)

I want to internationalize an external code (see github) with i18n for rails. I have read the guide for Rails Internationalization (I18n) API. Translating the text is not a problem, but the underlying code seems not to work properly anymore in all situations. Unfortunately, I'm not a rails/ruby expert.
From the log:
FATAL -- : ActionView::Template::Error (No route matches
{:action=>"edit", :controller=>"plans", :format=>nil, :id=>nil,
:locale=>115} missing required keys: [:id]):
So the problem is, that the parameter for the id (=115) is passed as locale instead as id.
To get i18n working I added the following code into app/controllers/application_controller.rb:
...
before_action :set_locale
protected
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
def default_url_options(options = {})
{ locale: I18n.locale }.merge options
end
...
Moreover, I wrapped the original routes in config/routes.rb:
Dmptool2::Application.routes.draw do
scope "(:locale)", locale: /en|de/ do
a lot of original routes
end
end
Therefore, the question is, is there a missing route or is there a problem inside the code or is it just my fault. Besides translating text and buttons, I haven't changed the original code. The original routes.rb can be found on github (sry, I can't post the link because I don't have enough reputation).
Any suggestions / help would be perfect.
Edit
I think I'm a little bit closer. Maybe now it's more clear, why it isn't working.
First of all the "full" stacktrace:
F, [2015-05-04T16:43:58.600384 #19289] FATAL -- :
ActionView::Template::Error (No route matches {:action=>"edit", :controller=>"plans", :format=>nil, :id=>nil, :locale=>#<Plan id: 158, name: "asdfe", requirements_template_id: 59, solicitation_identifier: "",
submission_deadline: nil, visibility: :public, created_at: "2015-05-04 14:41:33", updated_at: "2015-05-04 14:43:48", current_plan_state_id: 300>} missing required keys: [:id]):
2:
3: The plan "<%= #vars[:plan].name %>" has been completed.
4:
5: If you have questions pertaining to this action, please visit the DMP Overview page at <%= edit_plan_url(#vars[:plan]) %>
6:
7: <%= render :partial => 'users_mailer/notification_boilerplate.text.erb' %>
app/views/users_mailer/dmp_owners_and_co_committed.text.erb:5:in `_app_views_users_mailer_dmp_owners_and_co_committed_text_erb__754483862330985648_69917281861000'
app/mailers/users_mailer.rb:32:in `notification'
app/models/concerns/plan_email.rb:50:in `block in email_dmp_saved'
app/models/concerns/plan_email.rb:49:in `email_dmp_saved'
app/models/plan_state.rb:31:in `update_current_plan_state'
app/controllers/plan_states_controller.rb:97:in `create_plan_state'
app/controllers/plan_states_controller.rb:73:in `committed'
If I hit the "done" Button on the webpage, the function committed is called, wich calls create_plan_state(:committed). Within the definition of create_plan_state, there is the statement plan_state = PlanState.create( plan_id: #plan.id, state: state, user_id: current_user.id). This triggers a callback for after_create: update_current_plan_state:
def update_current_plan_state
#update the current plan pointer in the plan model
p = self.plan
p.current_plan_state_id = self.id
p.save!
end
Now, this triggers after_save: email_dmp_saved:
def email_dmp_saved
...
if current_state.state == :committed
users = self.users
users.delete_if {|u| !u[:prefs][:dmp_owners_and_co][:committed]}
users.each do |user|
UsersMailer.notification(
user.email,
"PLAN COMPLETED: #{self.name}",
"dmp_owners_and_co_committed",
{:user => user, :plan => self } ).deliver
end
I think the definition of the notification is not important. But the 3rd last line calls "dmp_owners_and_co_committed", which is defined as:
Hello <%= #vars[:user].full_name %>,
The plan "<%= #vars[:plan].name %>" has been completed.
If you have questions pertaining to this action, please visit the DMP Overview page at <%= edit_plan_url(#vars[:plan]) %>
<%= render :partial => 'users_mailer/notification_boilerplate.text.erb' %>
And in _notification_boilerplate.text.erb there is:
You may change your notification preferences at <%= edit_user_url(#vars[:user].id) %>#tab_tab2 .
I think the problem is edit_plan_url and edit_user_url. Because if I add some random text as parameter it works...:
edit_plan_url("de",#vars[:plan])
and in _notification:
edit_user_url("de",#vars[:user].id)
The question is, why is it working? Is there a way to print the created route? Because in the stacktrace the route doesn't match because format and id is nil. Now I want to see the new route in order to know where my random string "de" is placed.
Looks like your routes are expecting two params, and ordering it as it comes. There's a way to avoid it by using a named hash into the params passed:
edit_plan_url(id: #vars[:plan].id)
The Rails Automagic will use the symbol to identify the param and avoid the problem.

Rails url_for when passing a nested hash of parameters

I'm looking to do something like the following:
# /app/helpers/application_helper.rb
def custom_filter_url(additional_params={})
new_params = params.clone
new_params[:filter] ||= {}
new_params[:filter] = new_params[:filter].merge(additional_params)
url_for(new_params)
end
In a view (e.g., http://example.com/things?filter%5Bfoo%5D=bar) I would like the following:
<%= link_to "Bar", custom_filter_url(:foo => 'different') %>
To render:
http://example.com/things?filter%5Bfoo%5D=different
However, I'm getting this instead:
http://example.com/things?filter[foo]=different
Apparently, the url_for method doesn't fully encode the nested parameters hash/array. How do I get it to do so, or is there a better way of accomplishing this?

Resources