I am very new to the world of rails and I am tying to create a package tracking app. I want to be able to pass the tracking number of the package to the rails method I created to scrape the carrier's site.
I am getting a wrong number of arguments error and don't understand why.
Any input is appreciated!
routes.rb
Rails.application.routes.draw do
resources :packages do
match '/scrape', to: 'packages#scrape', via: :post, on: :collection
end
root 'home#index'
get 'dashboard' => 'packages#index'
get '/logout' => 'auth0#logout'
get 'auth/auth0', as: 'authentication'
get 'auth/auth0/callback' => 'auth0#callback'
get 'auth/failure' => 'auth0#failure'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
scrape method
def scrape(tracking_num)
require 'watir'
url = "https://www.ups.com/track?loc=en_US&tracknum=#{tracking_num}&requester=WT/trackdetails"
b = Watir::Browser.new :chrome, headless: true
b.goto(url)
text = b.p(:class, 'ups-txt_size_double_lg').when_present.text
flash.now[:notice] = text
end
button_to
<%= button_to 'Track', scrape_packages_path(tracking_num: package.tracking), class: "btn btn-sm btn-outline-dark" %>
The POST request and action are not used like you wrote.
I answer your direct question first: the actions in controller are called without argument. def scrape(tracking_num) expects being called with one argument so it raises an error.
Here are the modifications:
Rails.application.routes.draw do
resources :packages do
# you can use 'post' directly
post '/scrape', to: 'packages#scrape', on: :collection
end
end
# remove the parameters of method and use params[:tracking_num] to get it
def scrape
require 'watir'
url = "https://www.ups.com/track?loc=en_US&tracknum=#{params[:tracking_num]}&requester=WT/trackdetails"
b = Watir::Browser.new :chrome, headless: true
b.goto(url)
text = b.p(:class, 'ups-txt_size_double_lg').when_present.text
flash.now[:notice] = text
end
# add { remote: true, method: :post } to the link
<%= link_to 'Track', scrape_packages_path(tracking_num: package.tracking), class: "btn btn-sm btn-outline-dark", method: :post, remote: true %>
Related
I have an Approved column in a database which is false by default and might become true on "Approve" button click.
That's what this button look like at the moment:
<%= link_to('Approve It', #comment_path, method: :update) %>
But it raises an exception:
No route matches [POST] "/books/4/comments/6
# app/controllers/comments_controller.rb
def update
#comment = Comment.find(params[:id])
#comment.approve = true
redirect_to '/dashboard'
end
# config/routes.rb
resources :books do
resources :comments
end
How can I fix it?
link_to has to point to an existing route/action, with a proper method name. There is no :update HTTP method.
FYI: Approve action doesn't seem like it belongs to the #update method/action. You might want to extract it to a separate route like so:
resources :books do
resources :comments do
post :approve, on: :member
end
end
this is more idiomatic/common approach in Ruby because #update is usually preserved for more general object updates.
For this you will need to change :method argument value to :post and update your route/#comment_path.
Rails-ujs event handlers - this link might be useful for understanding how it works behind the scenes.
Controller Namespaces and Routing
Post / Update actions require forms
You're using a link_to. This is good for GET requests, but is no good for POST/PATCH/UPDATE requests. For that you'll have to use a form in HTML. Luckily Rails offers some short cut. You can use something like button_to:
<%= button_to "Approve", { controller: "comments", action: "update" }, remote: false, form: { "id" => #comment.id, "approved" => true } %>
This creates a form for you. Which will come with CSRF protection automatically. You can style the button however you like.
or you could use a link to:
<%= link_to comment_approved_path(#comment), method: :put %>
but then you would need to create a separate "approved" action in your controller, and a separate route to reach it.
(The above code has not been tested).
#html
<%= link_to "Approve It", book_comment_path(#comment), method: 'put' %>
# app/controllers/comments_controller.rb
def update
#comment = Comment.find(params[:id])
#comment.approve = true
#comment.save
redirect_to '/dashboard'
end
I am trying to link to a custom controller route action and I'm doing something wrong. I have a Document model that handles uploading documents to my CRUD app. I want users to be able to 'delete' something, but not actually delete it from the system, but rather update the column 'active' to false. Then if someone with admin privileges can go ahead an complete the deletion. This process is needed because the app gets audited and we do not want to accidentally delete uploaded files.
I can't get the custom update action (remove) to work. When I rake routes I see:
remove_documents PUT /documents/remove(.:format) document#remove
In my routes file (I'll a couple similar routes I'll want to add later so I used collection it up this way):
resources :documents do
collection do
put "remove", to: "document#remove", as: :remove
end
end
In the Documents index view:
<%= link_to remove_documents_url(document), :method => :put do %>
<span class="fa fa-trash text-danger"></span>
<% end %>
My Controller:
def remove
#document = Document.find(params[:id])
#document.active = false
#document.save
html { redirect_to(:back, :notice => 'Document was successfully removed.')}
end
The link works, but then I get the following error:
NameError at /documents/remove.75 uninitialized constant DocumentController
raise unless e.missing_name? qualified_name_for(parent, const_name)
end
end
name_error = NameError.new("uninitialized constant #{qualified_name}", const_name)
name_error.set_backtrace(caller.reject {|l| l.starts_with? __FILE__ })
raise name_error
end
# Remove the constants that have been autoloaded, and those that have been
# marked for unloading. Before each constant is removed a callback is sent
If you want the remove action on a specific Document, change the routes to :
resources :documents do
member do
put "remove", to: "documents#remove", as: :remove
end
end
which gives you : remove_document PUT /documents/:id/remove(.:format)
and use it like :
<%= link_to remove_document_path(document), :method => :put do %>
<span class="fa fa-trash text-danger"></span>
<% end %>
I'm writing tests for a presenter class and am getting the following error when running them, coming from a custom url helper method.
Error:
PersonPresenterTest#test_0011_should get email icon:
ArgumentError: arguments passed to url_for can't be handled. Please require routes or provide your own implementation
app/helpers/people_helper.rb:4:in `link_fa_to'
app/presenters/base_presenter.rb:14:in `method_missing'
app/presenters/person_presenter.rb:54:in `email_icon'
test/presenters/person_presenter_test.rb:59:in `block in <class:PersonPresenterTest>'
person_presenter_test.rb
class PersonPresenterTest < ActionView::TestCase
add_template_helper(PeopleHelper)
def setup
#person = FactoryGirl.create(:person)
#presenter = PersonPresenter.new(#person, view)
end
test 'should get email icon' do
expected = '<a class="icon-link" href="/people/1"><i class="fa fa-envelope fa-1x"></i></a>'
actual = #presenter.email_icon
assert_equal(expected, actual)
end
end
person_presenter.rb
class PersonPresenter < BasePresenter
presents :person
def email_icon
link_fa_to('envelope fa-1x', person) if person.email.present?
end
end
base_presenter.rb
class BasePresenter
def initialize(object, template)
#object = object
#template = template
end
def self.presents(name)
define_method(name) do
#object
end
end
def method_missing(*args, &block)
#template.send(*args, &block)
end
end
people_helper.rb
module PeopleHelper
def link_fa_to(icon_name, link)
link_to content_tag(:i, '', class: "fa fa-#{icon_name}"), link, class: "icon-link"
end
end
routes.rb
Rails.application.routes.draw do
resources :organisations
resources :people
resources :reset_password, only: [:edit, :update, :new, :index, :create]
resources :dashboards, only: [:index]
resources :users
get 'profile' => 'users#show', as: :profile
controller :sessions do
get 'login' => :new
post 'login' => :create
end
get '/auth/:provider/callback', to: 'sessions#create'
get "sessions/create"
get 'sessions' => "sessions#destroy", as: :logout
root 'sessions#new'
end
I can test presenter methods which use link_to successfully, and would expect methods calling link_fa_to to 'fall' through to the template in the same way. This is only affecting tests, the method works perfectly when running the application.
Any help much appreciated.
edit:
If I remove add_template_helper(PeopleHelper) from person_presenter_test.rb, the error is:
Error:
PersonPresenterTest#test_0011_should get email icon:
NoMethodError: undefined method `link_fa_to' for #<#<Class:0x007fcdb6bb19e0>:0x007fcdb6bb15a8>
Did you mean? link_to
app/presenters/base_presenter.rb:14:in `method_missing'
app/presenters/person_presenter.rb:54:in `email_icon'
test/presenters/person_presenter_test.rb:58:in `block in <class:PersonPresenterTest>'
edit 2:
After updating people_helper.rb to the following in response to Shishir's answer, the error message remains the same:
module PeopleHelper
def link_fa_to(icon_name, link)
link_to link, class: "icon-link" do
content_tag(:i, '', class: "fa fa-#{icon_name}")
end
end
end
The method content_tag returns a string (actually it's an ActiveSupport::SafeBuffer, but you can handle it like a string), so that's not your problem.
It seems that you are calling your helper method with an instance of ActiveRecord::Base instead of an url:
link_fa_to('envelope fa-1x', person) if person.email.present?
vs:
def link_fa_to(icon_name, link)
link_to content_tag(:i, '', class: "fa fa-#{icon_name}"), link, class: "icon-link"
end
You could try to update your code to something like this:
link_fa_to('envelope fa-1x', person_path(person)) if person.email.present?
The syntax of link_to you are using doesn't accept content_tag as parameter. It only accepts string.
link_to content_tag(:i, '', class: "fa fa-#{icon_name}"), link, class: "icon-link"
Try
link_to link do
content_tag(:i, '', class: "fa fa-#{icon_name}")
end
I'm trying to implement the datagrid gem in Rails 4 but am not sure how to include a link in the Grid class.
I currently have for the UsersGrid class:
class UsersGrid
include Datagrid
scope do
User.order("users.created_at desc")
end
column(:avatar) do |user|
if user.avatar?
link_to ActionController::Base.helpers.image_tag(user.avatar.url, alt: "Profile"), user_path(user)
else
link_to ActionController::Base.helpers.image_tag("profile.gif", alt: "Profile"), user_path(user)
end
end
end
This generates the following error message referring to the link_to line :
undefined method 'user_path' for #<ActionView::Base:0x007f821d3115b8>
How should I adjust the code to make the link work?
Additional information:
View page:
<%= datagrid_form_for #grid, :method => :get, :url => users_path %>
<%= will_paginate(#grid.assets) %>
<%= datagrid_table(#grid) %>
<%= will_paginate(#grid.assets) %>
Controller method:
def index
#grid = UsersGrid.new(params[:users_grid]) do |scope|
scope.where(admin: false).page(params[:page]).per_page(30)
end
#grid.assets
end
I found the solution: I had to add :html => true to column(:avatar, :html => true). This way html code such as link_to work and I also no longer needed ActionController::Base.helpers to get access to the image_tage method.
It sounds like you don't have a route configured for a user resource in routes.rb.
To verify and see what path helpers are available, go to command line, navigate to the project directory, and type in rake routes. If properly configured you should see something like:
user GET /users/:id/(.:format) users#show
On the far left of the example above is the "Name" which is used to generate the path helper. So in the example above the name is "user" and Rails will automatically generate the helper user_path which accepts an argument that is a user's id. So user_path(1) is a helper for /users/1. If you pass in a User object (like you were in your example) it will just get the id from the User in the background e.g.) user_path(current_user) will find the id of current_user and return /users/1.
Read more about rake routes here.
Anyways, if user is missing from your routes.rb file you could add something like this to routes.rb:
get '/users/:id', :to => 'users#show', :as => :user
Keep in mind you likely already have something for the users resource, so you might be able to make an easier/cleaner change depending on how you have configured the file.
I'm trying to use a named route with {{id}} as one of the params, allowing the rendered content to be consumed by Handlebars. url_for is escaping the param so the resulting url contains %7B%7Bid%7D%7D. I've tried adding :escape => false to the call, but it has no effect.
routes.rb
resources :rants, :except => [:show] do
post '/votes/:vote', :controller => 'votes', :action => 'create', :as => 'vote'
end
index.haml
%script{:id => 'vote_template', :type => 'text/x-handlebars-template'}
.votes
= link_to 'up', rant_vote_path(:rant_id => '{{id}}', :vote => 'up')
%span {{votes}}
= link_to 'down', rant_vote_path(:rant_id => '{{id}}', :vote => 'down')
application.js
var vote_template = Handlebars.compile($('#vote_template').html());
output
<script id="vote_template" type="text/x-handlebars-template">
<div class='votes'>
up
<span>{{votes}}</span>
down
</div>
</script>
I've simplified the example for the sake of readability but question remains the same; is there any way to use a named route with {{ }} as a param? I understand I can just do link_to 'up', '/rants/{{id}}/votes/up' so please don't supply that as an answer.
The trouble is the mustache characters are not valid in URLs and are being escaped. I'd recommend creating a wrapper.
def handlebar_path(helper, arguments={})
send("#{helper}_path", arguments).gsub(/%7B%7B(.+)%7D%7D/) do
"{{#{$1}}}"
end
end
handlebar_path :rant_vote, :rant_id => '{{id}}', :vote => 'up'
I ended up just overriding url_for to handle a custom param:
module TemplateHelper
def url_for(*args)
options = args.extract_options!
return super unless options.present?
handle = options.delete(:handlebars)
url = super(*(args.push(options)))
handle ? url.gsub(/%7B%7B(.+)%7D%7D/){|m| "{{#{$1}}}"} : url
end
end
So now, calling named_url(:id => '{{id}}', :handlebars => true) works as you'd expect.