I am coding a website, that have no legit option for editing users.
Once created, nothing can be changed.
And here is my question:
Is it possible to change users data (for example by sending patch requests) without using edit form (because I did not create one), by 3rd party (a.k.a. not host)?
As Rails 5 requires, I am using strong params (sending user name and password when creating user by signup form).
#routes
Prefix Verb URI Pattern Controller#Action
root GET / sessions#new
signup GET /signup(.:format) users#new
POST /signup(.:format) users#create
login GET /login(.:format) sessions#new
POST /login(.:format) sessions#create
logout DELETE /logout(.:format) sessions#destroy
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
Should also mention that there is no update method, but I am using
resources :users
For third party access to your db you probably need an API.
Simplified example:
You app's URL is https://myapp.example.com/.
config/routes.rb:
namespace :api do
namespace :v1 do
resources :users, only: [:update]
end
end
rake routes:
Prefix Verb URI Pattern Controller#Action
api_v1_user PATCH /api/v1/users/:id(.:format) api/v1/users#update
PUT /api/v1/users/:id(.:format) api/v1/users#update
app/controllers/api/v1/base_controller.rb:
class Api::V1::BaseController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
protect_from_forgery with: :null_session
end
app/controllers/api/v1/users_controller.rb:
class Api::V1::UsersController < Api::V1::BaseController
def update
# check params, maybe look for some security tokens
user = User.find(params[:id])
if user
user.update_attribute(:name, params[:name])
render plain: "success" and return
else
render plain: "failure" and return
end
end
end
Then request like this
curl -X PATCH https://myapp.example.com/api/v1/users/1?name=new_name
would change users' with id = 1 name by "new_name".
For proper API desing you may check http://jsonapi.org/.
Related
Im trying to learn to dry up my code a bit but have come across a issue,
Im scoping my routes with controller and path options
scope path: '/administrators', controller: :administrators do
get 'unverified' => :unverified
patch 'verify/:id' => :verify
get 'reported' => :reported
get 'ban_user' => :ban_user
patch 'execute_ban/:id' => :execute_ban
end
So this is what ive done so far, all the get links are working correctly...
but this is making the patch 'execute_ban/:id' => :execute_ban become a extension of ban user like this: (also the same with verify)
verified GET /administrators/unverified(.:format) administrators#unverifie
PATCH /administrators/verify/:id(.:format) administrators#verify
reported GET /administrators/reported(.:format) administrators#reported
ban_user GET /administrators/ban_user(.:format) administrators#ban_user
PATCH /administrators/execute_ban/:id(.:format) administrators#execute_ban
now ive changed my link_to route = link_to 'ban', ban_user_path(x.id), method: :patch
but its throwing an routing error saying no path matches.
Is there something im missing, any insight would aooreciated as always.
Thanks
Why not go for a more restful design that centers around the resource being modified?
Rails.application.routes.draw do
resources :users do
get :unverified, on: :collection
get :reported, on: :collection
get :ban
patch :ban, action: 'execute_ban'
end
end
You don't need different paths for the form and acting on a resource - use the HTTP verb instead.
Prefix Verb URI Pattern Controller#Action
unverified_users GET /users/unverified(.:format) users#unverified
reported_users GET /users/reported(.:format) users#reported
user_ban GET /users/:user_id/ban(.:format) users#ban
PATCH /users/:user_id/ban(.:format) users#execute_ban
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
If you are adding a separate controller just for authorization purposes it's an anti-pattern of sorts since you are just adding more complexity for something that should be handled by Pundit/CanCanCan.
Suppose you have UsersController and routes are configured in config/routes.rb as the followings.
root 'users#index' # root should list all users
resources :users
Then rake routes shows
Prefix Verb URI Pattern Controller#Action
root GET / users#index
users GET /users(.:format) users#index
users POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
There are a couple of URIs indicating same users#index action.
Should I prevent this kind of URI duplication or should I let them alone for me to follow Rails' default convention?
There are a couple of URIs indicating same users#index action. There are a couple of URIs indicating same users#index action.
Well, that's what you've specified by creating the users resource and then pointing the root path to users#index. If you don't want this behavior, you can remove the default index action from the resource.
resources :users, except: [:index]
Note that with this place, you can no longer refer to the index path via the traditional URL helpers. Personally I wouldn't be concerned with this and just let the URL duplication exist.
For root it's okay--this is typical and it's just redirecting to the users resource.
I'm working on supporting users in my Rails app. There's no need for a user to even be aware that there are other users out there. I don't want to just use resources :users, because the routes that generates are these:
users GET /users users#index
POST /users users#create
new_user GET /users/new users#new
edit_user GET /users/:id/edit users#edit
user GET /users/:id users#show
PATCH /users/:id users#update
PUT /users/:id users#update
DELETE /users/:id users#destroy
(.:format) removed for improved readability.
You'd have to put the user id number in the URL, and that offers a chance for users to be aware that other users exist. I want these routes:
users GET /users users#index
POST /users users#create
new_user GET /users/new users#new
edit_user GET /users/me/edit users#edit
user GET /users/me users#show
PATCH /users/me users#update
PUT /users/me users#update
DELETE /users/me users#destroy
Yes. /users/me is the path of your user, and that's the only user path you can get at.
But the problem is defining these routes. Here's one idea:
resources :users, constraints: { id: 'me' }
And in the User model:
def to_param
'me'
end
But that seems too kludgy for me. Any better ideas?
With Singular Resources your are good to go ;-)
Define your routes like this:
# config/routes.rb
resources :users, only: [:index, :create, :new]
resource :user, path: '/users/me', only: [:show, :edit, :update, :destroy]
Your routes will be singular (/user) if you leave the path: option. Play around with the options ;-)
And your rake routes result should look like this:
Prefix Verb URI Pattern Controller#Action
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/me/edit(.:format) users#edit
user GET /users/me(.:format) users#show
PATCH /users/me(.:format) users#update
PUT /users/me(.:format) users#update
DELETE /users/me(.:format) users#destroy
You can also test the routes on the Rails console (rails c)
2.1.3 :001 > Rails.application.routes.url_helpers.user_path
=> "/users/me"
2.1.3 :002 > Rails.application.routes.url_helpers.edit_user_path
=> "/users/me/edit"
If you want, you can pluralize :user on the Singular Resources, but don't forget to set the as: option, her an example:
# config/routes.rb
resources :users, only: [:index, :create, :new]
resource :users, path: '/users/me', as: 'user', only: [:show, :edit, :update, :destroy]
Please take a look to the notes and warnings in the Rails guide! Here is an excerpt:
A long-standing bug prevents form_for from working automatically with singular resources. ...
I get this error:
Failures:
1) UsersController DELETE 'destroy' should sign a user out
Failure/Error: delete :destroy
ActionController::UrlGenerationError:
No route matches {:controller=>"users", :action=>"destroy"}
My test is:
it "should sign a user out" do
test_sign_in(Factory(:user))
delete :destroy
expect(controller).to_not be_signed_in
expect(response).to redirect_to(root_path)
end
The test_sign_in function is in the spec helper:
def test_sign_in(user)
controller.sign_in(user)
end
My rake routes:
Prefix Verb URI Pattern Controller#Action
sessions_new GET /sessions/new(.:format) sessions#new
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
sessions POST /sessions(.:format) sessions#create
new_session GET /sessions/new(.:format) sessions#new
session DELETE /sessions/:id(.:format) sessions#destroy
root GET / pages#home
contact GET /contact(.:format) pages#contact
about GET /about(.:format) pages#about
help GET /help(.:format) pages#help
signup GET /signup(.:format) users#new
signin GET /signin(.:format) sessions#new
signout GET /signout(.:format) sessions#destroy
pages_home GET /pages/home(.:format) pages#home
Anyone knows how can I solve this error?
Your route is defined as
DELETE /users/:id(.:format) users#destroy
which means that the route is expecting something like
DELETE /users/4
Looking at your test, you are just requesting DELETE /users, this was derived from this error message:
ActionController::UrlGenerationError: No route matches {:controller=>"users", :action=>"destroy"})
So, you need to modify your test to handle the :id part of the route. This is un-tested, but you're roughly looking for:
user = Factory(:user)
test_sign_in(user)
delete :destroy, id: user.id
ooooh god!! I found the error. I accidentally was writing the code in the users_controller_spec and I should do this in the sessions_controller_spec.
Thanks Andreas for trying to help!
Lets say I have an UsersController that contains an action #new. In my routes file I map with the following:
match 'signup', to: 'users#new'
This action can now be accessed by both /signup and /users/new. How do I restrict it to only the custom route.
I apologize if this has been answered, but am new to this. I've searched, but haven't found the answer. Possibly due to my not knowing how to concisely phrase this.
You can exempt the new route from the users resource, and replace it with your custom route:
resources :users, except: [:new]
get 'signup', to: 'users#new', as: "new_user"
Resulting in:
users GET /users(.:format) users#index
POST /users(.:format) users#create
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
new_user GET /signup(.:format) users#new