I have a new question.
What I want to do?
I've created the profile for each player, now I want to allow users to edit their profile.
Part of this profile takes the devise table, with a second table called profiles.
Devise: Captures username, password and email
Profiles: Captures Personal Information
Schema of Profiles
create_table "profiles", force: :cascade do |t|
t.text "about"
t.string "fb"
t.string "skype"
t.string "birthday"
t.string "twitter"
t.string "steam"
t.string "gender"
t.string "occupation"
t.string "location"
t.string "interest"
end
My profile_controller.rb (Updated)
class ProfileController < ApplicationController
def index
#profile = Player.all
end
def show
#profile = Player.find_by(nick: params[:nick])
end
def edit
# use this to populate a form in your view
#profile = Player.find_by(nick: params[:nick])
end
def update
#profile = Player.find_by(nick: params[:nick])
if #profile.update_attributes(profiles_params)
redirect_to(:action => "show", :profile => #profile.nick)
else
render("index")
end
end
private
def profiles_params
params.require(:profiles).permit(:about, :skype)
end
end
Rake routes
profiles GET /profiles(.:format) profiles#index
GET /profiles/:nick/edit(.:format) profiles#edit
GET /profiles/:nick(.:format) profiles#show
PATCH /profiles/:nick(.:format) profiles#update
PUT /profiles/:nick(.:format) profiles#update
Routes.rb
devise_for :players
resources :profile, param: :nick
resources :profiles, only: [:index, :show, :edit, :update], param: :nick
Notes:
I have a profile controller
I have these pages:
show.html.erb
index.html.erb
Now my question is:
*How should I make the edit page for each users profile,
considering that each user edits their own profile?
Once a user edits a field, I want this to be reflected on the page show.html.erb. How can I achieve this?*
** show.html.erb **
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="info">
<p><div class="form-group">
<label for="textArea" class="col-lg-2 control-label">About <%= #profile.nick %></label>
<div class="col-lg-10">
<pre style="padding-bottom: 200px;"><%= #profiles.about %>(THIS IS THE ERROR)</pre>
<span class="help-block">In Progress</span>
</div>
</div></p>
</div>
** edit.html.erb (Not _edit, because missing template)**
<strong>Testing About</strong>
<%= form_for :profiles do |f| %>
<%= f.label :about %>
<%= f.text_field :about %>
<div class="btn btn-primary">
<a href="http://localhost:3000/profile/<%= #profile.nick %>">
<%= f.submit "Update Profile", :class => 'btn btn-primary' %></a>
</div>
<% end %>
<% end %>
<p>I am editing for</p>
<%= #profile.nick %>
Thanks in advance
Show should be used to display a users profile, not edit it - this is the rails way.
I suggest you jump over and have a read on rails resourceful routing: http://guides.rubyonrails.org/routing.html - this covers routes and CRUD.
Consider you have your resource, :profiles - in your routes this can be declared as:
resources :profiles
This route will automatically create the desired routes for you - in this case we only seem to want index, show, edit and update therefore this can be declared:
resources :profiles, only: [:index, :show, :edit, :update]
We now have the following routes, i've commented what their purpose should be:
GET /profiles #show all profiles
GET /profiles/:id #show specific user profile
GET /profiles/:id/edit #edit specific user profile
PATCH /profiles/:id #update a specific profile
Now that we have our routes lets look at this from a controller perspective - i'm jsut going to focus on edit/update as this is your question:
class ProfileController < ApplicationController
def index
#do something
end
def show
# do something
end
def edit
# use this to populate a form in your view
#profile = get_profile
end
def update
#profile = get_profile
if #profile.update_attributes(profile_params)
#do something on success
else
# do something on fail
end
end
private
def profile_params
params.require(:profile).permit(:about, :skype, etc)
end
def get_profile
nick = params[:nick]
if Player.where(username: nick).present?
if Player.where(username: nick).count == 1
Player.includes(:profile).find_by(username: nick).profile
else
raise "Error: Multiple profiles found"
end
else
raise "Error: Profile cannot be found"
end
end
end
You'll notice the 'profile_params' method, this is apart of rails strong paramaters and whitelists the attributes we can use for mass assignment. Read more: http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html
Because our routes are set up as expected for our profile, our forms are super simple as rails can infer everything.
edit.html.erb
<%= form_for #profile do |f| %>
<%= f.text_field :about %>
... etc
<% f.submit %> # this will submit to profile#update
<% end %>
Further reading on form_for: http://apidock.com/rails/ActionView/Helpers/FormHelper/form_for
Ofcourse you should also be adding authorisation to the controller methods but hopefully i've covered the basic premise for you.
EDIT1: In regards to your comment I've added a private method for locating your user profile by either username or id (tweak to suit). But like I said, go through the linked readings so you can get an understanding of routing.
EDIT2: SO taking into consideration your further comments and the routes you added to the question i've updated your get_profile method.
Related
Working with nested routes and associations. I have a partial which creates a tenant, but after the creation it stays with the form rendered and the url changes to /tenants. Desired behavior is that it needs to redirect_to the show page. Routes are as follows:
Rails.application.routes.draw do
devise_for :landlords
authenticated :landlord do
root "properties#index", as: "authenticated_root"
end
resources :tenants
resources :properties do
resources :units
end
root 'static#home'
end
So far the properties and units work (and the landlord) Issue is with Tenants. Originally I had Tenants nested under units, but had issues there as well. Partial looks like this:
<%= form_for #tenant do |f| %>
<%= f.label "Tenant Name:" %>
<%= f.text_field :name %>
<%= f.label "Move-in Date:" %>
<%= f.date_field :move_in_date %>
<%= f.label "Back Rent Amount:" %>
$<%= f.text_field :back_rent %>
<%= f.button :Submit %>
<% end %>
<%= link_to "Cancel", root_path %>
Tenants Controller looks like this:
before_action :authenticate_landlord!
#before_action :set_unit, only: [:new, :create]
before_action :set_tenant, except: [:new, :create]
def new
#tenant = Tenant.new
end
def create
#tenant = Tenant.new(tenant_params)
if #tenant.save
redirect_to(#tenant)
else
render 'new'
end
end
def show
end
def edit
end
def update
if #tenant.update(tenant_params)
redirect_to unit_tenant_path(#tenant)
else
render 'edit'
end
end
def destroy
end
private
def set_property
#property = Property.find(params[:property_id])
end
def set_unit
#unit = Unit.find(params[:unit_id])
end
def set_tenant
#tenant = Tenant.find(params[:id])
end
def tenant_params
params.require(:tenant).permit(:name, :move_in_date, :is_late, :back_rent, :unit_id)
end
end
Models have associations:
class Tenant < ApplicationRecord
belongs_to :unit, inverse_of: :tenants
end
class Unit < ApplicationRecord
belongs_to :property, inverse_of: :units
has_many :tenants, inverse_of: :unit
end
Lastly the show#tenants in rake routes is:
tenant GET /tenants/:id(.:format) tenants#show
I have extensively searched for this topic, but haven't had any success. Any help is appreciated. Rails 5.1
The route you are showing near the end of your question:
tenant GET /tenants/:id(.:format) tenants#show
is not the tenants index; it is the individual tenants/show route. You can tell this because it includes :id, which means it will show you a specific tenant having that id.
Try running rake routes again. The index route should look like this:
tenants GET /tenants(.:format) tenants#index
If you want to return to the tenants index after creating or updating a Tenant record, then you need to specify that path in your TenantsController. In both the #create and #update actions, your redirect line (after if #tenant.save and if #tenant.update, respectively) should read:
redirect_to tenants_path
That will take you to the TenantsController, #index action.
In the alternative, if you want to return to the individual tenant show page, then instead change both of those redirects in the TenantsController in both the #create and #update actions to:
redirect_to tenant_path(#tenant)
That will take you to the TenantsController, #show action for the current #tenant.
Ruby 2.2.4
Rails 4.2.6
PostgreSQL 9.5
I am trying to save a simple model, but when I submit the form, my browser url shows this "http://localhost:8080/notes/new?utf8=%E2%9C%93&authenticity_token=z0cyVNfUKYWDSDASDWFFZ96zj29UTtDYe8dLlKrI6Mbznb2SrTWNm%2BQ91D2s2AASD2345Fl3fTOneCC2dNg%3D%3D¬e%5Btitulo%5D=ddddddd¬e%5Bconteudo%5D=dddddddddddddddddd&commit=Create"
I am curious about this because other project, it has the same methods, same routes, the only difference is the model that only have one column, but it works fine.
def change
create_table :notes do |t|
t.text :titulo
t.text :conteudo
t.timestamps null: false
end
My controller: notes_controller.rb
def new
#note = Note.new
end
def create
#note = Note.new(note_params)
if #note.save
redirect_to '/'
else
render 'new'
end
end
private
def note_params
params.require(:note).permit(:titulo,:conteudo)
end
my form
<%= form_for(#note) do |f| %>
<div class="field">
<%= f.label :titulo %><br>
<%= f.text_area :titulo %>
<%= f.label :conteudo %><br>
<%= f.text_area :conteudo %>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
my routes
Rails.application.routes.draw do
root 'notes#index'
get 'notes/new' => 'notes#new'
post 'notes' => 'notes#create'
I saw this post Rails form issuing GET request instead of POST request
but does not work for me.
Edit:
I fix it thanks to Anthony E, his answer made me look back to code and realize that I have a form inside a form. The outer form was in application.html.erb.
Thanks to all.
Rails can't infer the appropriate form route from your model. Try explicitly setting the form URL and submit method in your form_for:
form_for #note, url: "/notes", as: :note, html: { method: :post } do |f|
end
Alternatively, it may be simpler to use resourceful routing:
In routes.rb:
resources :notes, only: [:new, :create, :index]
This will create the following routes:
GET /notes/new # Maps to NotesController#new
POST /notes # Maps to NotesController#create
GET /notes # Maps to NotesController#index
I am fairly new to Rails and am using Devise for user authentication in a new demo app. I have got Devise to work, along with the confirmable module and email verification.
I am now trying to build a phone verification on top of this. I updated the User model to have three additional fields: phone_number, phone_verified (boolean) and phone_verification_code. I also updated the Registrations Controller to allow the additional field of phone_number by the User model.
Now, in order to set up a phone verification system, I have created a page /user/:id/verify by adding a verify method in UsersController and updating the routes.rb. When I type in the URL, http://localhost:3000/users/10/verify, I see the view for that page.
However, I am trying to get to the view by creating a button on the /app/views/users/show.html.erb and my code below is getting an error. I am trying to get the correct path helper for the button. Can someone please help.
Here is the error:
Showing /home/ubuntu/work/depot/app/views/users/show.html.erb where line #10 raised:
undefined method `verify_user_path' for #<#:0x007fc3740fe3a0>
app/views/users/show.html.erb
<p id="notice"><%= notice %></p>
<div class="row">
<div class="col-md-offset-2 col-md-8">
<div class="panel panel-default">
<div class="panel-heading"><%= #user.email %></div>
<div class="panel-body">
<strong>Phone number: </strong><%= #user.phone_number %><br/>
<% if #user.phone_verified == nil %>
<%= button_to [:verify, #user] do %>
Verify Phone <strong><%= #user.phone_number %></strong>
<% end %>
<% else %>
<strong>Phone Verified: </strong><%= #user.phone_verified %><br/>
<% end%>
</div>
</div>
</div>
</div>
</div>
UsersController
class UsersController < ApplicationController
def index
#users = User.all
end
def show
begin
#user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
logger.error "Attempt to access an invalid user: #{params[:id]}"
redirect_to store_url, notice: "Attempt to access an invalid user: #{params[:id]}"
else
respond_to do |format|
format.html # show html.erb
format.json { render json: #user }
end
end
end
def verify
end
def generate_pin
#user.phone_verification_code = rand(0000..9999).to_s.rjust(4, "0")
save
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
#user = nil
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params[:user]
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { registrations: "registrations" }
resources :users, only: [:index, :show, :edit, :update]
resources :orders
resources :line_items
resources :carts
match "users/:id/verify" => "users#verify", via: [:get]
get 'store/index'
resources :products
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
# root 'welcome#index'
root 'store#index', as: 'store'
You need to use the as option in your route:
match "users/:id/verify" => "users#verify", via: [:get], as: 'verify_user'
Then restart your server. This will tell Rails to create the proper URL helper methods: verify_user_path and verify_user_url
match "users/:id/verify" => "users#verify", as: 'verify_user', via: [:get]
Or better way:
resources :users, only: [:index, :show, :edit, :update] do
get :verify
end
How can I best implement this feature: As an admin, I can assign a Resident Manager to a Hall.
I have a User model with a namespace routing for the admin -I intend on having another namespace routing that would hold the functions of the RM-
I have a Hall model.
Since its a many-many relationship between the above to models, I have a Management join model which contains only user_id and hall_id columns.
I know implementing the above feature, entails creating a new record in the management table but I don't know how to do it. I didn't think using a form (management#new) would solve this because the admin should not know the user_ids/hall_ids...
BELOW IS WHAT I HAVE TRIED TO DO BUT I CAN'T GET IT RIGHT
When the admin gets to the user index page, s/he should see a link for Hall Assignment for each user. This link leads to the management show page for that particular user, which would show the list of halls assigned to that user and also the show all the other remaining halls that isn't assigned to the user. So, either clicking an ADD button or on the hall's name should add it to that user's assigned halls list which is on the same page.
Management#show page
<h2><%= #user.email %>'s assigned halls</h2>
<% #user.managements.each do |management| %>
<%= management.hall.name %>
<% end %>
<p> HALL LISTS </p>
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add" %> </li>
<% end %>
</ul>
Here's is my Management controller
class Admin::ManagementsController < ApplicationController
def index
#managements = Management.all
end
def show
#user = User.find(params[:id])
#halls = Hall.all
end
def create
#management = Management.create(managements_params)
redirect_to admin_management_path
end
private
def managements_params
params.
require(:management).
permit(user_id: params[:user_id], hall_id: params[:hall_id])
end
end
And here's a piece of what my routes file looks like:
namespace :admin do
resources :users, only: [:index, :update]
resources :halls, only: [:index, :new, :create]
resources :managements, only: [:index, :new, :create, :show] do
resources :halls, only: [:index]
end
end
Your "add" button is just a mini form (with mostly hidden fields). You can instead just make it an actual form (with the submit-button having the text "Add") and the id-values filled in from the item on the page... it just points to the same routes that you'd normally point the form that you'd find in the new template.
if you want more detail, then show us the code that you have written (rather than a verbal description of it).
Edit:
Ok, so you'd put a button on the page like this
<ul>
<% #halls.each do |hall| %>
<li><%= hall.name %> <%= button_to "Add", managements_path(management: {user_id: #user.id, hall_id: hall.id}, method: :put ) %> </li>
<% end %>
</ul>
Notice the managements_path - you might need to check that that routing is correct (check it against what is in rake routes). Note that you're passing in the user id and the hall id, and that you must set the method to "put" on the button.
First things first -
How can I implement a 'create' action without 'new' action
It's relatively simple to do - you will need a create action somewhere, but a new action is just a way to build the respective ActiveRecord object for your controller.
If you do this in another action, you just have to make sure you point the form to the correct create action (and that create action to redirect back to
--
New / Create
Here's how you could handle the new / create actions in different controllers, as an example for you:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def index
#hall = Hall.new
end
end
#app/controllers/halls_controller.rb
Class HallsController < ApplicationController
def create
#hall = Hall.new hall_params
redirect_to users_path if #hall.save
end
private
def hall_params
params.require(:hall).(:hall, :attributes, :user_id)
end
end
This will allow you to show the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= form_for #hall, url: hall_path do |f| %>
<%= f.hidden_field :user_id, value: user.id %>
<%= f.text_field :x %>
<%= f.submit %>
<% end %>
<% end %>
--
Fix
ADD button or on the hall's name should add it to that user's assigned halls list
For this, I don't think you'd need a create action in the "traditional" sense - it will be more about adding new halls to a user's current halls. This is much different than creating a new hall itself:
#config/routes.rb
namespace :admin do
resources :users do
post "hall/:id", to: :add_all #=> domain.com/admin/users/:user_id/hall/:id
end
end
#app/controllers/admin/users_controller.rb
Class UsersController < ApplicationController
def add_hall
#user = User.find params[:user_id]
#hall = Hall.find params[:id]
#user.halls << #hall
end
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :user_halls
has_many :halls, through: :user_halls
end
#app/models/hall.rb
Class Hall < ActiveRecord::Base
has_many :user_halls
has_many :users, through: :user_halls
end
#app/models/user_hall.rb
Class UserHall < ActiveRecord::Base
belongs_to :user
belongs_to :hall
end
This uses the ActiveRecord collection methods to make this work, to which you'll be able to provide the following:
#app/views/users/index.html.erb
<% #users.each do |user| %>
<%= link_to user.name, user %>
<%= button_to "Add Hall Test", user_add_hall_path(user, 1) %>
<% end %>
References and External Links
Ruby on Rails - Settting up Reviews functionality
NoMethodError in Discussions#new
http://ruby.about.com/od/rubyonrails/ss/blogpart4_4.htm
Background
I'm implementing a feature in my application that allow users to rate and review pictures.
I am using a Posts/Comments relationship model for a Pictures/Reviews relationship.
Models
class Review < ActiveRecord::Base
belongs_to :picture
end
class Picture < ActiveRecord::Base
has_many :reviews
end
Above, I established a one-to-many relationship between pictures and reviews.
Reviews Migration
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.string :username
t.text :body
t.references :picture, index: true
t.timestamps
end
end
end
Matched Routes
match '/pictures/:id/reviews', to: 'reviews#show', via: 'get', :as => 'picture_reviews'
match '/pictures/:id/reviews/edit', to: 'reviews#edit', via: 'get'
match '/pictures/:id/reviews/new', to: 'reviews#new', via: 'get', :as => 'new_reviews'
I will name the route for reviews#edit after I fix this issue with reviews#new.
Error Message
NoMethodError in Reviews#new
Undefined method 'reviews_path' for #<#<Class:0x45c1b00>:0x39ae810>
Extracted source (Around line #8):
5 <div class = 'edit-form'>
6 <div class = 'center'>
7
8 <% form_for #review do |f| %>
9
10 <p>
11 <%= f.label :username %><br />
I checked to see if any files contained 'review-path', but all routes were properly named.
Routes
favorite_picture_path PUT /pictures/:id/favorite(.:format) pictures#favorite
pictures_path GET /pictures(.:format) pictures#index
POST /pictures(.:format) pictures#create
new_picture_path GET /pictures/new(.:format) pictures#new
edit_picture_path GET /pictures/:id/edit(.:format) pictures#edit
picture_path GET /pictures/:id(.:format) pictures#show
PATCH /pictures/:id(.:format) pictures#update
PUT /pictures/:id(.:format) pictures#update
DELETE /pictures/:id(.:format) pictures#destroy
users_path GET /users(.:format) users#index
POST /users(.:format) users#create
new_user_path GET /users/new(.:format) users#new
edit_user_path GET /users/:id/edit(.:format) users#edit
user_path 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_path POST /sessions(.:format) sessions#create
new_session_path GET /sessions/new(.:format) sessions#new
session_path DELETE /sessions/:id(.:format) sessions#destroy
contacts_path POST /contacts(.:format) contacts#create
new_contact_path GET /contacts/new(.:format) contacts#new
root_path GET / pictures#welcome
users_new_path GET /users/new(.:format) users#new
about_path GET /about(.:format) pictures#about
GET /contacts(.:format) contacts#new
GET /users/:id/favorites(.:format) users#favorites
signup_path GET /signup(.:format) users#new
signin_path GET /signin(.:format) sessions#new
signout_path DELETE /signout(.:format) sessions#destroy
picture_reviews_path GET /pictures/:id/reviews(.:format) reviews#index
GET /pictures/:id/reviews/edit(.:format) reviews#edit
new_reviews_path GET /pictures/:id/reviews/new(.:format) reviews#new
updated_path GET /updated(.:format) pictures#new_updates
GET /top-rated(.:format) pictures#high_ratings
ReviewsController (Part 1)
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
def index
#picture = Picture.find(params[:id])
#review = Review.all
end
def show
#picture = Picture.find(params[:id])
#review = Review.find(params[:id])
end
def new
#review = Review.new
end
def edit
#picture = Picture.find(params[:picture_id])
#review = Review.find(params[:id])
end
def create
#picture = Picture.find(params[:picture_id])
#review = #picture.reviews.build(params[:review])
if #review.save
flash[:notice] = 'Review was successfully created.'
redirect_to #picture
else
flash[:notice] = "Error creating review: #{#review.errors}"
redirect_to #picture
end
end
Reviews Controller(Part 2)
def update
#picture = Picture.find(params[:picture_id])
#review = Review.find(params[:id])
if #review.update_attributes(params[:review])
flash[:notice] = "Review updated"
redirect_to #picture
else
flash[:error] = "There was an error updating your review"
redirect_to #picture
end
end
def destroy
#picture = Picture.find(params[:picture_id])
#review = Review.find(params[:id])
#review.destroy
redirect_to(#review.post)
end
private
def set_review
#review = Review.find(params[:id])
end
def review_params
params.require(:review).permit(:username, :body, :picture_id)
end
end
Reviews#Index Page
<h3>Reviews for <%= "#{#picture.title}" %></h3>
<table>
<thead>
</thead>
<tbody>
</tbody>
</table>
<div class = 'center'>
<p><%= link_to 'New Review', new_reviews_path(#review), :class => "btn btn-info" %></p>
<p><%= link_to 'Back', picture_path, :class => "btn btn-info" %></p>
</div>
Link to the Reviews#new page
<p><%= link_to 'New Review', new_reviews_path(#review), :class => "btn btn-info" %></p>
Reviews#New Page
<% #title = "New Review" %>
<h3>New Review</h3>
<div class = 'edit-form'>
<div class = 'center'>
<% form_for #review do |f| %>
<p>
<%= f.label :username %><br />
<%= f.text_field :username %>
</p>
<p>
<%= f.label :body %><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.submit "Submit Review" %>
</p>
<% end %>
</div>
</div>
<div class = 'center'>
<%= link_to 'Back', picture_reviews_path(#picture) %>
</div>
Pictures#Show Page
<% #title = "#{#picture.title}" %>
<h4 class = 'indent'>Picture Statistics</h4>
<ul id = 'view'>
<li><strong>Title:</strong> <%= #picture.title %></li>
<li><strong>Category:</strong> <%= #picture.category %></li>
<li><strong>Rating:</strong> <%= pluralize(#picture.rating, 'Star') %></li>
<li><strong>Favorited:</strong> By <%= pluralize(#picture.users.count, 'User') %></li></br>
</ul>
<% if #picture.rating > 4 %>
<button class = 'top-picture'>Top Rated</button>
<% end %>
<%= form_for #picture do |f| %>
<div class = 'indent'>
<p>
<%= f.label :stars, 'Rating' %>
<div class= "rating">
1 ☆<%= f.radio_button :stars, '1' %>
2 ☆<%= f.radio_button :stars, '2' %>
3 ☆<%= f.radio_button :stars, '3' %>
4 ☆<%= f.radio_button :stars, '4' %>
5 ☆<%= f.radio_button :stars, '5' %>
</div>
</p>
<p><input class="btn btn-info" type="submit" value="Rate"></p>
<p><%= link_to 'Reviews', picture_reviews_path(#picture), :class => "btn btn-info" %></p>
<% end %>
<p><%= link_to 'Index', pictures_path, :class => "btn btn-info" %></p>
</div>
I've tried using nested resources like so
resources :pictures do
put :favorite, on: :member
resources :reviews
end
resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :contacts, only: [:new, :create]
That didn't work because It routed my pictures using :picture_id instead of the standard :id field. Since it routed to :picture_id it couldn't find any pictures.
picture_reviews_path GET /pictures/:picture_id/reviews(.:format) reviews#index
GET /pictures/:picture_id/reviews/edit/:id(.:format) reviews#edit
new_reviews_path GET /pictures/:picture_id/reviews/new(.:format) reviews#new
Picture Columns
Picture.column_names
=> ['id', 'title', 'category', 'stars', 'created_at', 'updated_at',
'ratings_count', 'ratings_total']
The problem with nesting routes, is that it calls a path using a column_name not found in the table. That is why I decided to go back to matching routes.
I believe the problem lies in my ReviewsController for which there may be duplicated code.
before_action :set_review, only: [:show, :edit, :update, :destroy]
#review = Review.find(params[:id])
def set_review
#review = Review.find(params[:id])
end
I think I could remove the #review = Review.find line from every method, but my main concern is that the set_review method was defined as a private method so that might not be possible.
Help is greatly appreciated and thanks in advanced.
Update
I think the problem lies in my new action in my ReviewsController.
This is just an extended version of #japed answer.
1. You have no route to the create or update action
Both actions works on POST request, hence url_helpers alone won't tell rails what to do with POST request when it gets it. What you need is to change your routes back to nested resources (it was good the way it was, your issue was caused by another bit of code). So, you need:
resources :pictures do
...
resources :reviews
end
Also remove all other routes for this controller as they may affect your final routes. Remeber to restart your server after changing your routes.
2. The controller:
Firstly, note that there are a lot of repetitions there - you are setting #picture in all the actions. Currently your problem is that it is using params[:id] in some actions and params[:picture_id] in others. It should always be picture_id, id should be reserved to be review's id, as you are inside reviews_controller.
The best way to do this is to create another before_filter which will set up the #picture variable:
class ReviewsContorller < ApplicationController
before_filter :set_picture
# This is perfectly fine, but needs to be executed after :set_picture
before_filter :set_review, only: [:show, :edit, :update, :destroy]
...
private
...
def set_picture
#picture = Picture.find(params[:picture_id])
end
def set_review
#review = picture.reviews.find(params[:id])
end
end
Note that the #review is pulled from #picture association - this is important security check, if you used Review.find instead, all the users are automatically able to view, edit and create new reviews for all the photos, without knowing which photo they are really commenting for. It should not be a great issue in your case, but it is good to get this into the habit.
3. The form:
<% form_for #review do |f| %>
This would seems all right, however imagine you are your application - how would you know what is the correct post url for this form? Rails is quite intelligent framework and it is trying to guess it by the resource supplied. In this case, you pass an instance of Review class, hence it will try to send the from to review_path(#review.id). The problem is, that this path does not exists in your routes, so you will get undefined_method 'review_path' here.
Also note, that the proper route you want is /picture/:picture_id/reviews for new reviews or /picture/:picture_id/review/:idfor existing reviews. Hence rails will need the parent picture object to be passed as well to figure out the rightpicture_id`. You can do this by passing an array of resources, with the one which the form is really for being the last so:
<% form_for [#picture, #review] do |f| %>
This will tell rails to look for picture_reviews_path(#picture.id) for new review or picture_review_path(#picture.id, #review.id) for existing reviews. If you have nested resources in your routes, both of those should exists.
4. Other links
Your current routes defines a named path new_reviews which will not longer exist after you use nested resources - it will be renamed to new_picture_review, so you need to change all the occurrences of new_reviews_path to new_picture_review(#picture)
As you're doing nested routes, you need to find by :picture_id as you've just found
class ReviewsController < ApplicationController
before_action { #picture = Picture.find(params[:picture_id] }
end
As your error says the issue is because reviews_path doesn't exist because you've nested it
So this
<% form_for #review do |f| %>
Wants to change to
<% form_for [#picture, #review] do |f| %>
So that it goes to the picture_reviews_path
Also this
<p><%= link_to 'New Review', new_reviews_path(#review), :class => "btn btn-info" %></p>
Wants to become
<p><%= link_to 'New Review', new_picture_reviews_path(#picture, #review), :class => "btn btn-info" %></p>
Can you use Shallow Nesting Routes? That is, you'll have a nested resource where needed, but when unambiguous you get a shorter path, with just one parameter for the review. You can still find your way back to the picture, using the picture_id in the review.
resources :pictures, shallow: true do
put :favorite, on: :member
resources :reviews, shallow: true
end
resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :contacts, only: [:new, :create]
Then, improve the models to help the associations to bind well, with inverse_of:
class Review < ActiveRecord::Base
belongs_to :picture, inverse_of: :reviews
end
class Picture < ActiveRecord::Base
has_many :reviews, inverse_of: :picture
end
This should mean there's only one copy of a picture in memory. And then in the ReviewsController:
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
def index
#picture = Picture.find(params[:id])
# reference #picture.reviews to get all reviews in the view
end
def show
#picture = Picture.find(params[:id])
# use #picture.reviews to get all reviews in the view
end
def new
# where will you get the picture this belongs to?
# Need to collect the picture_id param. and build the associated review
#picture = Picture.find(param[:picture_id])
#review = #picture.reviews.build()
end
def edit
#picture = Picture.find(params[:picture_id])
# use #picture.reviews in the view controller to get the associated reviews
end
def create
#picture = Picture.find(params[:picture_id])
#review = #picture.reviews.build(params[:review])
if #review.save
flash[:notice] = 'Review was successfully created.'
redirect_to #picture
else
flash[:notice] = "Error creating review: #{#review.errors}"
redirect_to #picture
end
end
I think there's one other significant issue. You keep using a piece of code like this:
#review = Picture.find(id)
But that returns zero or more elements. It will help you understand the code better if you reflect that this is, normally, an array:
#reviews = Picture.find(id)
But even better, don't do that. You have the associations. Use them in the view.
#picture.reviews
This will return an array. If zero length, there are no reviews. If non-zero, that's how many review elements there are.
Then you won't make the mistake of picking up an array variable called #review, which appears to be singular (meaning that link_to #review appears to make sense, but will fail), and instead use an array:
<%- #picture.reviews.each do |review| %>
<% link_to review ...%>
Hope that helps!