create edit URL route that doesn't show :id - ruby-on-rails

I'm in the midst of trying to clean up my routing. I have a company model that can log in and create applications. They can create several.
Currently this is my setup:
Routes
get 'applications/edit/:id', to: 'applications#edit'
Applications_controller
def edit
#application = current_company.applications.find(params[:id])
end
def update
#application = Application.find(params[:id])
if #application.update(application_params)
redirect_to dashboard_path
else
render 'edit'
end
end
Each company have their own dashboard. Here's my code from
/dashboard
Your active applications
<% #applications.all.each do |f| %>
<%= link_to "Application", show_path + "/#{f.id}" %> | <%= link_to "Edit", edit_application_path("#{f.id}") %>
<br>
<% end %>
Now this all works, if I go to edit_application/11 f.ex I see it.
The thing I'd like changed is to remove the :id from the URL.
Thus make it more secure and to give a nicer feel. Now it took me 5 minutes before I realised I could just change the :id url and edit everything. Thus I added the current_company.applications to stop that. Yet I don't feel like this is very secure.

If you want to remove the :id, you'll still need a way to find the data you want.
As long as you have the url /edit/12 and as long as you use the id 12 in the GET url to find your content, it will show in the browser bar. The only way to "hide" it (but it's not more secure at all, because it's easily found out), is to use a POST request with a form containing a hidden field with the id (can be made in JavaScript).
You are asking the application to get the id from the link in the #applications.all.each but the only way it can do that is to include it somewhere in the request (be it GET, POST, COOKIES/SESSION, ...).
For another (possibly better) solution, read on.
A very common practice is to use slugs: you create a unique key for each content, for example, if your title is "My great app", the slug will be my-great-app. Thus there is no id in your URL (and it cannot be found out if you always use slugs as references). The advantage is that you'll still find a quick match for what you're searching for (creating an unique index on the slugs).
Some further reading about slugs:
http://rubysnippets.com/2013/02/04/rails-seo-pretty-urls-in-rails/
What is the etymology of 'slug'?

Related

Is there a real problem with using i.e. get method instead of put in an action through clicking on a button

For example I have button for thumbs up or I want to resend a specific mail or I change a specific enum state through clicking on a button (like published/unpublished)
Now regarding the rails implementation, I can make it work using either put, get or patch, since I only have to send a specific id.
In my opinion it seems to be best practice using patch, post or put wheter one or several attributes will change on my objects. So this seems mainly to be a convention here.
On the server side I will have to add some policies to allow only specific users to do so, but beyond adding policies are there any possible issues with not using the conventional http-method here?
One very real problem with using GET is that is supposed to be an idempotent method. Lets say the new guy creates the form:
get '/things/create', to: "things#create"
<%= form_with(model: #post, url: '/posts/create', method: :get) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= f.submit %>
<% end %>
class PostsController < ApplicationController
def index
#posts = Post.all
end
def create
#post = Post.new(title: params[:post][:title])
#post.save
redirect_to action: :index
end
end
He then tries it out in the browser in and is perfectly happy with it. Mission accomplished.
His boss then attempts to test it. He creates a Post titled "This new guy might not be so bad anyways". And then he hits the back button to try creating another Post. Weirdly it just loops back to the index page. He tries it again - the only thing that happens is that the page starts to fill up with "This new guy might not be so bad anyways" and he is becomes less and less convinced that its actually true.
If you used POST, PATCH, PUT or DELETE the browser would have warned him that he is about to resend a form. Thats why GET should never change anything on the server (besides maybe your pageview stats).
It also opens up for any malicous actor to get users to create, delete or modify resources simply by fooling them into clicking a link. The malicous actor doesn't even have to got though the effort of creating a phishing site and circumventing the anti-CSRF protection that Rails provides.
There is absolutely no difference between how POST, PATCH, PUT or DELETE are treated by the client or server beyond the conventions of Rails.
But since Rails is a highly convention driven framework which adheres to a specific flavor of REST it really befits you to follow those conventions if you want to be productive and not be that new guy.
When it comes to actions beyond the classical CRUD verbs its really down to your best judgement and intent is really what matters. What does the action do? Is it updating something (PATCH)? Is it actually a separate resource? (POST /mails/1/mailouts). As you may see there is no easy answer. Just be clear and document what you're doing if you're unsure.

Create link in Ruby on Rails to update a specific database entry

I am new to Ruby on Rails and I want to create a very simple application. I used scaffolding to create a database called users. Two of the fields in users are limit and containers, which is the max number of containers a user can have and the remaining total containers that they can have. They both start off at a given number (e.g. 15), but when a user takes one containers will go down to 14 and will continue to decrease every time they take one, until it reaches 0. When a user returns a container, the number for container goes up by one until the max number, 15 in my example.
Since I created this using Ruby scaffolding, I can just go to the edit page for each user right now and manually change the containers value, but that can allow me to change it to any number. On the users/:id page I want to have a link like the edit link that is there right now
<%= link_to 'Edit', edit_user_path(#user) %>
and have something similar, but along the lines of
<%= link_to 'Take container', #run method to decrease and return here# %>
<%= link_to 'Return container', #run method to increase and return here# %>
For now, all I care about is just changing the number and elsewhere I will be rendering the user information.
AJAX would be a good fit here, but that would complicate the question, so I will stick to doing it with Rails.
You will first need to edit your routes.rb file to include something like:
get '/remove_container/:user_id', to: 'users#remove_container', :as => :remove_container
get '/add_container/:user_id', to: 'users#add_container', :as => :add_container
Then add methods in your Users controller:
def remove_container
user_id = params[:user_id]
#user = User.find user_id
# code to change container number
redirect_to edit_user_path(#user)
end
def add_container
user_id = params[:user_id]
#user = User.find user_id
# code to change container number
redirect_to edit_user_path(#user)
end
There are a lot of things to make this better, but this should get you going in the right direction anyway.
Links to these could be made manually or done with paths:
Remove Container
or
link_to "Remove Container", remove_container_path(#user)

Rails 4 Link_ to Create New Record - Request Type

I am on Rails 4.
Just created a Favorite model & controller in which users can favorite articles. I have it set up so when a user clicks on the link, it routes to the favorite action inside of the favorites_controller. Right now, it is a get request, and inside of the action it is simply creating a new favorite.
My question is... is it ok that this is a get request? I feel like maybe a post request would be better (as forms use post to create things), but since there is no form in this case I decided a get request would be fine. Is this ok? or frowned upon?
here is the link_to in my view:
<%= link_to favorite_path(title: #article.title, id: #article.id) do %>
<span class="glyphicon glyphicon-star-empty"></span>
<% end %>
and the action it routes to in the controller:
def favorite
current_user.favorites.create(article_id: params[:id],
article_title: params[:title])
flash[:success] = "You added this page to your favorites."
redirect_to request.referrer || root_url
end
and my routes.rb:
get 'favorite' => 'favorites#favorite'
Everything works fine, just want some clarification as to the get request and if this is the best way to achieve what I am doing. If there is already documentation on this, please send it my way.
No, you shouldn't use a get request to create things or change states.
The reason is simple: Searchengines will follow links and trigger that action. Or some browsers may preload links to speed things up. In both case you will create records although the user never chose to click the link.
Instead just tell Rails to use some javascript to fake a post request (note the method: :post part):
<%= link_to(favorite_path(title: #article.title, id: #article.id),
method: :post) do %>
<span class="glyphicon glyphicon-star-empty"></span>
<% end %>
Read the doc about link_to for details.

Rails Forms for custom actions

I'm trying to link the input of a form to a specific action in my rails app.
Currently if I go to www.myapp.com/check/:idNumber, I'll be able to trigger the action just fine (which means routes is setup properly?). This action is basically a function call to a ruby/rails script with the parameter "idNumber" being passed to it. If the function is successful, it would return a newly created "Person" object and nil otherwise. This is different than the standard new operation as it determines the the attributes based on some information that it obtained from a database somewhere else.
Rake routes does give me the following:
check /check/:idNumber(.:format) person#check {:id=>/\d+/}
What I'm having trouble implementing is the form itself.
<%= form_tag("/check", :method => "get") do %>
<%= text_field_tag(:idNumber) %>
<% end %>
Controller action:
def check
regCheck = RegCheck.new
#person = regCheck.check_id(params[:idNumber])
if #person.name == nil
redirect_to root_path
end
end
submitting the form above would bring me to myapp.com/check?utf8=✓&idNumber=1234 instead. Can someone tell me what am I doing wrong?
I believe that using the check_path helper that is generated from the routes file is your best bet.
The form should look like this then.
<%= form_tag(check_path) do %>
<%= text_field_tag(:idNumber) %>
<% end %>
Rails forms can be finicky, especially when trying to build really customized forms.
This line
= form_for [#object]
Determines where the form goes, as well as the object that is being implemented. If you want to route the form to a different place, you can user the :url option. This options determines the path of the form, however you must keep in mind that the method is determined by the #object. If it is a new object, the method will be POST, an existing object will use a PUT method.
Let's suppose you want to update an existing object, but you want to send in data for a new object belonging to the existing object. That would look like
= form_for [#object], :as => #child_object, :url => my_optional_custom_path do |f|
# etc...
This generates a form sending a PUT request to the custom url (or the update path for #object if no custom url is supplied. The PUT request is sent with the parameter params[:child_object].
Hopefully this helps!
Best,
-Brian
I don't think it's possible the way you're trying.. The URL for the form is created before the user inputs any data.. So you need to remove the :idNumber from your routing..
If you do you get the following route:
check /check(.:format) person#check
Because the regex is removed now, you need to do this in you're controller:
def check
# Make sure ID is digits only
idNumber = params[:idNumber].gsub(/[^\d]/, '')
regCheck = RegCheck.new
#person = regCheck.check_id(idNumber)
if #person.name == nil
redirect_to root_path
end
end
You're form is allright, but you may want to use check_path like TheBinaryhood suggests..
If you really want it to be check/:idNumber you may also be able to submit the form to another action and redirect it to the right path from there..

Newbie rails database relation question

I have two models Station and Broadcast. Broadcast belongs_to Station and has a station_id column.
I don't know how to make new method in BroadcastController to expect the station_id value and how to create a new Broadcast with right station_id in it.
I'd recommend against using a link_to here. Using a 'get' action to change/add data on the server is generally frowned upon. Also, I wouldn't use the 'new' action, as it's used by convention in a Rails Restful route to present a form, not actually persist data. All that said, the simple answer to your question is to pass a value through the link_to helper like so:
link_to 'Add broadcast', new_broadcast_path(:station_id => station.id)
...and your 'new' method on BroadcastsController would do:
def new
#broadcast = BroadCast.new(:station_id => params[:station_id])
if #broadcast.save
flash[:notice] = "New broadcast created!"
redirect :back # or whatever
else
# etc.
end
end
But, again, this is not the right way to do this. What you probably want to do is stay within the Rails (and web) conventions and use a form to create the new broadcast record by way of the 'create' action on the controller. You might place this form next to your stations on the index view which presents a button that points to the correct 'create' action of BroadcastsController, and uses a hidden_field to set the station_id. Something like (EDIT: better use of hidden_field):
<% form_for :broadcast do |f| %>
<%= f.hidden_field :station_id, :value=> station.id %>
<%= submit_tag 'Add broadcast' %>
<% end %>
Assuming you've set a restful route in routes.rb for broadcast like:
map.resources :broadcasts
...then your form will automatically point to a 'create' action on BroadcastsController, which you should write to look something like:
def create
#broadcast = BroadCast.new(params[:broadcast])
if #broadcast.save
# etc., etc.
end
The Rails Guides are a good place to get more comfortable with the Rails controller actions. I'd also spend some time looking over the RESTful actions and how they're used in Rails as well.
You actually do specify a station_id for your Broadcast model, such as
script/generate scaffold broadcast name:string station_id:integer ...
so when you add a broadcast record, it will ask you for a station_id.

Resources