Rails 4 - Couldn't find User without an ID - ruby-on-rails

I'm new to rails, so any explanation & advise would much appreciated.
i have a webpage in which i would like any user to view that page not just the current_user, but i am unsure how to correctly define the instance variable #user in my controller
in my static_pages_controller.rb i have the below action recruiterpg
static_pages_controller.rb
def recruiterpg
#user = User.find(params[:user_id])
#adverts = #user.adverts
#applications = #user.forms
end
in my controller, i have defined user as #user = User.find(params[:user_id]) but this breaks my code in the views; views/static_pages/recruiterpg.html.erb
when i define user as #user = current_user my code in the views works perfectly fine
what am trying to do is: for my views, the recruiterpg.html.erb, i would like
any user to be able to view the page not only the current_user to
view the page. Could one kindly advise me and explain to me how to
define #user correctly in my status_pages_controller.rb. i also
tried #user = User.find(params[:id]) but my code still breaks in the
views - i get the error message
Couldn't find User without an ID

You need to make sure you are passing a user_id to the recruiterpg action. For example, try this url in your browser (set user_id to a known id in the users table):
http://localhost:3000/dashboard?user_id=1
A suggested modification to your action:
def recruiterpg
#user = User.find params.require(:user_id)
#adverts = #user.adverts
#applications = #user.forms
end

If params[:user_id] isn't defined, you want to find a way to make visible what is being defined.
If you throw the following statements into your controller...
def recruiterpg
...
puts params
...
end
...you should see something like the following get spit out in your console when you load the page...
{"controller"=>"static_pages", "action"=>"recruiterpg", "id"=>"49"}
Take a look at the Rails guide for parameters. They can get defined in one of three ways.
One: As a query string similar to Sean's answer above.
Two: Routing parameters. See section 4.3 in the Rails guide. In your case, that would mean you should have something like the following in routes.rb:
get '/dashboard/:user_id' => 'staticpages#recruiterpg'
Note that there's nothing magic about :user_id in that string.
Three: From a form which it doesn't seem like applies here, since a user isn't submitting data.

Since you're new, here is some information for you:
User Story
Firstly, the best way to resolve errors is to identify your user story.
A "user story" is a software principle in which you put the "user's perspective" first -- explaining how the software should work in conditions defined from how the user engages with it.
One of the main issues you have with your question is your user story is very weak; it's hard to decifer what you're trying to achieve.
Next time you ask a question, you should try your hardest to describe how the user should see your app, before providing code snippets :)
Controller
Your main issue is an antipattern.
An antipattern is basically a "hack" which will likely break another part of your app in future. Kind of like duct tape for software):
#app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def recruiterpg
#user = User.find(params[:user_id])
#adverts = #user.adverts
#applications = #user.forms
end
end
So you're showing a static page but yet you want to populate it with data?
Hmm...
What you should be doing is something like the following:
#config/routes.rb
resources :users do
resources :recruiters, only: :index #-> url.com/users/:user_id/recruiters
end
#app/controllers/recruiters_controller.rb
class RecruitersController < ApplicationController
def index
#user = User.find params[:user_id]
#adverts = #user.adverts
#applications = #user.forms
end
end
This will allow you to populate the following view:
#app/views/recruiters/index.html.erb
<%= #adverts %>
--
It's important to note the structure of the controller / routes here.
The issue you have is that you're calling a "static page" and expecting to have params available to find a User. This can only happen if you have params available...
Params
Rails is not magic, and as such if you want to look up a user, you have to provide the parameters to do so.
This is why you're able to look up current_user -- the params are already set for this user.
As such, you'll need to use something called nested routes in order to attain a user ID other than that of current_user:
#config/routes.rb
resources :users do
resources :recruiters #-> url.com/users/:user_id/recruiters
end

Related

Rails routes url take out user_id

I currently have the standard
get 'profile/:id/view', to: 'profile#view', as: 'view'
which produces http://localhost:3000/profile/233/view
the controller is
def view
#user = User.find(params[:id])
end
How can I make a path that the url is http://localhost:3000/profile/view - leaving out the URL?
I was trying to do something like
def view(user_id)
#user = User.find(user_id)
end
But I'm not sure how to write that in the routes, or what the link_to path would look like for that?
I have been looking for a straight forward answer, but have not been able to find one.
Thanks in advance!
You can change the route to
get 'profile/view', to: 'profile#view'
And then in the controller, you can user current_user method which devise provides.
def view
#user = current_user
end

Action show to find value from hash instead of id

I am still learning rails and have done a lot of readings, but I am not very clear about how params, 'show' actions work yet.
For example we have UsersController, 'index' action is showing all the users with the code #user = User.all, and 'show' action is looking into each users, by using the code #user = User.find(params[:id])
I understand that they are all from the database, where User is a model.
However in my scenario, what if the data I am showing in views, doesn't go through database, instead in the 'index' action it is something like this -
#user = [{name => "alex"}, {name => "peter"}, {name => "john"}]
and in my 'show' action, how can I write the code so that it finds the users by name?
In your Rails app, the data that you show in your views, do not necessarily have to come from/through the database. You can always show any data you want in your views.
For example, in your index action, if you have this:
#users = [{name => "alex"}, {name => "peter"}, {name => "john"}]
Then, in your index view, you can show only those users by looping through the #users instance variable.
Same for show page as well.
If you want to show the users by name in your show page, you have to set the users by name in an instance variable e.g. #users_by_name:
#users_by_name = User.find_by(name: user_name)
# or you can hard code the values if you want like index action
and then this #users_by_name instance variable will be available in your show view so that you can loop through that and show the user names.
Originally, the show page is designed for showing a particular user related information, but you can show whatever information you want going against the conventions.
To be able to have a route like this: localhost:3000/users/alex that will show the user alex's information, you can add a route in your routes.rb file:
get 'users/:name', to: "users#show"
And, in your controller's show action, something like this:
def show
#user = User.find(params[:name])
end
Then, show the #user information in your view page.
P.S. This is not a good idea to find user by name as there might be more than one user with same name in the database and it will create conflict/ or give wrong data in such situations.
In show action , we search the user specific record not all.
So , we have to provide some unique identifiers as parameters to find the specific record.
For eg. Your view should be similar to the params we are passing as below:
<% #user.each do |user| %><br>
<%= link_to user.name, user_show_path+"?name="+user.name %><br>
<% end %><br>
In show action , write the code
def show
#user = User.find_by(:name => params[:name])
end
Also in routes.rb , write the below code:
get 'users/:name', to: "users#show"
For the above solution, make sure that name field will be unique.
My original question is that if it is possible for 'show' action not to go through database
Sure.
Your show action can be the following if you wanted it to:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = "me"
end
end
You really don't have to do anything specific in your application, Rails is just a framework and has certain conventions if you want it to work efficiently.
What you're asking is if you can populate your #user object from a third party set of data...
... Yes you can ...
The way to do it would be in the model, not the controller:
#app/models/user.rb
class User < ActiveRecord::Base
# populates from Hash
end
You'd then be able to populate the data in the controller from the model again:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = User.__________ #-> pull from your hash
end
end
finds the users by name
That's simple - just pass the name through the url: url.com/users/marine_lorphelin
This will set the :id parameter to marine_lorphelin, with which you'll be able to look up the name through your model:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = User._______
end
end
If you were using a database with your user model, you'd be able to use the following:
def show
#user = User.find_by name: params[:id]
end
Since you're not, you'll have to attach your XML hash to your model somehow. This, I don't know without specifics such as where you're getting your data from, how you're accessing it, and which routes you're going to send to invoke it.

Redirect all non-model-named urls to named ones in Rails

I've got an app where I'm overriding Rails' def to_param method to include the model name like so: www.myapp.com/1-some-model-name, however when you go to www.myapp.com/1 you get the same page with the same content, but these are two different URLs. What's the best way to do a redirect so that everytime I hit www.myapp.com/1 I go to www.myapp.com/1-some-model-name?
Thanks and any advice is much appreciated.
I've tried solutions found here and elsewhere, but can't get my app to work properly. (I got the error Error 310 (net::ERR_TOO_MANY_REDIRECTS))
I've make simple solution, but this solution works well enough.
If you are using to_param like this :
def to_param
[id, name.parameterize].join("-")
end
You just add some condition to action on controller looks like :
def show
#user = User.find(params[:id])
formal = #user.to_param
if formal != params[:id]
# on routes : match "/user/:id" => "users#show", :as => :show_user
redirect_to show_user_path([#user.id, #user.firstname.parameterize].join("-"))
end
end

Ruby on Rails controller design

When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.

Rails: Forum has_many topics, saving the association?

I might be going about this the wrong way in the first place, so I will give a bit of background first.
As you can tell from the title, I am building a forum from scratch. I thought it was working correctly; however, I am a bit unsure as to how to update/save the forum object from within the topics "create" method in it's controller.
What I tried to do:
In the "New" method, I sent the Forum's id via the routing. So on the new-topic page has a address that looks like this: "localhost:3000/new-topic/1". The one being the Forum's id. In the method itself, I try to attach it to the new topic object.
#topic = Topic.new
#topic.forum = Forum.find(params[:id])
My create method then tries to use that forum.
#topic = Topic.new(params[:topic])
#topic.forum.topics << #topic #Simplified down.
if #topic.save
#topic.forum.save
...
I get the feeling that I am going about this the wrong way. I was looking at someone's tutorial and they got the forum by calling params[:forum_id] but they didn't show they routing they did to achieve that.
How do I do this correctly and/or what is the correct way to route all of this? For the record, I do plan on using this same method for the Topic => Post association. Thanks for any help.
You should use nested REST routes:
# routes.rb
resources :forums do
resources :topics
end
this will result in the following routes:
GET /forums/:forum_id/topics/new # displays the form
POST /forums/:forum_id/topics # creates the topic
and in controller you should use builders, they have several advantages like security, scope preserving etc.:
def new
#forum = Forum.find(params[:forum_id])
#topic = #forum.topics.build
def create
#forum = Forum.find(params[:forum_id])
#topic = #forum.topics.build(params[:topic])
if #topic.save
...
http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources

Resources