Rails url_for when passing a nested hash of parameters - ruby-on-rails

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?

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.

Passing attributes in test url - Rspec / Addressable gem

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

parse form_for/form_tag to get the url or controller/action

I want to parse form_for and form_tag statements in erb files to determine which urls they post to or which controller/action will be called.
For example, given a ".html.erb" file, I want to get all "<%= form_for %>" tags out, somehow parse it, and get to know which exact controller/action pair will be called after I click to submit this form. For instance, the following file, https://github.com/jcs/lobsters/blob/master/app/views/stories/new.html.erb
line 7, "<%= form_for #story do |f| %>", can I determine which controller/action pair it will be mapped to by running some line of code? just like "routes.recognize_path 'form_for #story do |f|'" such kind of thing?
form_tag
form_tag(url_for_options = {}, options = {}, &block)
form_for
form_for(record, options = {}, &block)
Looks like I need to somehow get the url_for_options and options object out and get the url element out. Is there any easy way to do it or is there any existing tools that can achieve such functions? Does rails have any built-in functions for such thing?
i don't know if i understand your question correctly, especially the part about parsing your views etc, but you can generate urls by calling url_for.
for example in your rails console you can do the following:
include Rails.application.routes.url_helpers
default_url_options[:host] = "localhost"
url_for #user # => "http://localhost/users/1"

Undefined method? Controller params in Rails 4

I am getting the weirdest error I have ever seen, consider the following create method:
def create
post = Post.find_by(id: params[:post_id])
#comment = Comment.new(comment_create_params)
#comment.post_id = post.id #I know this line is useless I have yet to refactor.
controller_save(#comment)
end
From here we have comment_create params which is a private method but is defined as such:
def comment_create_params
params.require(:comment).permit(:author, :comment, :parent_id)
end
Now consider the following params that were passed in:
params => {"author"=>"157685iyutrewe1wq",
"comment"=>"14253647turyerwe",
"action"=>"create",
"controller"=>"api/v1/comments",
"post_id"=>"126"}
Based on this everything looks correct. Running through this function everything should save. Till I get the following error:
NoMethodError: undefined method `permit' for "14253647turyerwe":String
I have no idea what this means - I think its trying to treat: "14253647turyerwe" as a method which is a string? not sure....
Params
params.require(:comment).permit(:author, :comment, :parent_id)
This will basically look for a hash which inherits from a comment key, like this:
{"comment" =>
{
"id" => "5",
"name" => "test"
}
}
So when you use the require method, you're basically saying "we need this top-level hash key", to which Rails will then go into the nested hash & use the permit method to locate the other attributes, as shown above.
The problem you have is this:
params => {"author"=>"157685iyutrewe1wq",
"comment"=>"14253647turyerwe",
"action"=>"create",
"controller"=>"api/v1/comments",
"post_id"=>"126"}
The problem here is you're calling require on the comment key; which is just a string. To fix this, you'll need to do something like this:
def comment_params
params.permit(:author, :comment, :action)
end
--
Save
Something else you need to consider is the controller_save method. I've never seen this before, and is against convention. Not a problem with this, but means if you get team members on your app, or want to upgrade Rails, it will be a pain to adapt it.
I would definitely use the standard .save method, like this:
#app/controllers/comments_controller.rb
def create
...
#comment.save
end
The Actual issue was that the attribute in the Comment model was also comment, upon changing it (and the migration scripts as well as tests) to comment_text everything worked again :D
Your 'params' are formed definitely not by a rails' form_for. Because it would be looked as params: { comment: { "author"=>"157685iyutrewe1wq", "post_id" => "some_id" } }
And so your params.require(:comment) returns 'String' object with value = "14253647turyerwe" and it really has no any 'permit' method.
So I suggest you to read about forms in rails, what html parameters they are construct while sending form data to a server
UPDATE:
If for some reason you have no mood for using a form_for #model helper, your whatever method of generating of form should produce for each field of that model something like this html:
<input id="comment_author" name="comment[author]" type="text" value="14253647turyerwe"/>
for them moment I suspect you got something like:
<input id="author" name="author" type="text" value="14253647turyerwe"/>

Passing (and using) parameters to the "new" action of a controller

My app has Lessons which have many Drills.
When I am looking at a lesson I want to be able to create new Drills and automatically assign their lesson_id.
So far what I have is a link in the lesson's show.html.erb that passes in the lesson's id, like so:
<%= link_to t('.new', :default => t("helpers.links.new")),
new_drill_path(lesson_id: #lesson.id), :class => 'btn' %>
This adds the lesson_id to the params hash. I figured out how to pass the param into the drill by doing this in my controller:
def new
#drill = Drill.new
params.each do |key, value|
method = key.to_s + '='
method.to_sym
if #drill.respond_to? method
#drill.send method, value
end
end
end
However, this code seems smelly to me. It seems like there ought to be a more rails-y way to do it, but I can't figure out what it is.
Use nested resources. Something like this:
# in config/routes.rb
resources :lessons do
resources :drills
end
That'll give you nested URLs/routes, and route helpers like lesson_drills_path that take a Lesson-record as its argument to produce a path like /lessons/:lesson_id/drills.
Your DrillsController will therefore have access to params[:lesson_id] because it'll be given in the URL. That means that you can do things like
# POST /lesson/:lesson_id/drills/
def create
#lesson = Lesson.find(params[:lesson_id])
#drill = #lesson.drills.new(params[:drill])
if #drill.save
..
else
..
end
end
See more in this answer over on CodeReview, and check the links in there too
I'm not exactly sure what you're trying to do, but read up on Nested Resources. Your new action should look something like:
def new
#lesson = Book.find(params[:lesson_id])
#drill = #lesson.drills.build
end

Resources