I've been trying to wrap my head around this single table inheritance example. I understand the concept behind it, but am not sure from reading other posts around the web & on this site how to make it work for my example. I'm using STI because i've got two types of projects - public and private. The logic behind them is the same and the data they store in the database will be identical whether public or private. I just intend to implement some authorization & roles for users in the future that differ slightly based on whether the project is public or private. So my question is, what steps are still needed to make this work...so far I've:
1) added a type_column to projects via a migration...my project model is
PROJECT.RB
class Project < ActiveRecord::Base
has_many :users
has_many :versions, dependent: :destroy
validates :title, presence: true, length: { maximum: 100 }
validates :background, presence: true
validates :user_id, presence: true
default_scope -> { order('created_at DESC') }
end
2) I created separate classes for PublicProjects & PrivateProjects...those models are:
PUBLIC_PROJECT.RB
class PublicProject < Project
end
PRIVATE_PROJECT.RB
class PrivateProject < Project
end
So now i'm wondering what needs to be re-factored in my controller (i'd like to keep the single Projects controller)...and then also what needs changing in my 'new' form view. Step by step help on the code that would make this work would be much appreciated. The controller/view files are:
PROJECTS_CONTROLLER.RB
class ProjectsController < ApplicationController
before_filter :signed_in_user, only: [:create, :new, :edit, :update]
def new
#project = Project.new
end
def show
#project = Project.find(params[:id])
#user = User.where(:id => #project.user_id).first
end
def index
#projects = Project.paginate(page: params[:page])
end
def create
#project = current_user.projects.build(project_params)
if #project.save
flash[:success] = "Welcome to your new project."
redirect_to #project
else
render 'new'
end
end
def edit
end
def update
#project = Project.find(params[:id])
if #project.update_attributes(params[:project])
flash[:success] = "Project Created"
redirect_to #project
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "Project destroyed"
redirect_to users_path
end
private
def project_params
params.require(:project).permit(:title, :background)
end
end
NEW.HTML.ERB (NEW PROJECTS VIEW W/ FORM)
<% provide(:title, 'New Project') %>
<h1>Create a new project</h1>
<div class="row-fluid">
<div class="col-md-5 no-pad offset3">
<%= bootstrap_form_for #project do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_field :title %>
<%= f.text_area :background %>
<div class="row-fluid">
<div class="no-pad col-md-6">
<h5>Is this project public or private?</h5>
<div class="btn-group">
<button type="button" class="btn btn-default"><%= image_tag "globe.png" %> Public</button>
<button type="button" class="btn btn-default"><%= image_tag "lock.png" %> Private</button>
</div>
<script>
$(".btn-group > .btn.btn-default").click(function(){
$(".btn-group > .btn.btn-default").removeClass("active");
$(this).addClass("active");
});
</script>
</div>
<div class="col-md-6">
Some static graphics
</div>
</div>
<br clear="all"><br/>
<%= f.submit "Create your project", class: "btn btn-lg btn-primary" %>
<% end %>
</div>
</div>
Lastly, i've seen some STI examples that are getting into the routes file (although I've seen that this isn't recommended as often). but just in case:
ROUTES.RB
ProductionApp::Application.routes.draw do
resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :projects do
resources :versions
end
# get "static_pages/home"
# get "static_pages/help"
# get "static_pages/about"
#The original routes above map to...
root 'static_pages#home'
match '/signup', to: 'users#new', via: 'get'
match '/signin', to: 'sessions#new', via: 'get'
match '/signout', to: 'sessions#destroy', via: 'delete'
match '/help', to: 'static_pages#help', via: 'get'
match '/about', to: 'static_pages#about', via: 'get'
match '/contact', to: 'static_pages#contact', via: 'get'
end
Thanks a lot for any help,
I am not sure about the authentication, but instead of using additional classes for public and private, you could just create a type field that tells you whether or not the project is public or private something like is_public.
The other option is to use a enum release in rails 4 - really simple and straight forward. This is my preferred option, but you need to be working on the latest version of rails.
Related
I have created a ToDoList based off Hartl's tutorial, and following a video and worded tutorial to add a tagging system. I have followed till Section 10, where they asked me to modify my new.html.erb file to the code as shown on the source. To improvise for structural differences in code, I would edit some other files, like in this case, my micropost_form partial instead. Occasionally, I alternated between the code in the video and code in the worded tutorial because some of them would produce error messages or would not produce the required functionality. Here are the files that I think are involved in this question.
_micropost_form.html.erb(The filling up form that would be displayed on the user's home page)
<%= simple_form_for #micropost do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_area :content, placeholder: "Add new task..." %>
</div>
<div class="field">
<%= f.label :tag_list, "Tags (separated by commas)" %><br />
<%= f.text_field :tag_list %>
</div>
<%= f.submit "Add Task", class: "btn btn-primary" %>
<% end %>
micropost.html.erb(for showing the individual micro posts)
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, user_path(micropost.user) %></span>
<span class="content"><%= micropost.content %></span>
<p><small>Tags: <%= raw micropost.tags.map(&:name).map { |t| link_to t, tag_path(t) }.join(', ') %></small</p>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
<% if current_user?(micropost.user) %>
<%= link_to "Done", micropost_path(micropost), method: :delete, data: { confirm: "Keep up the good work!" } %>
<% end %>
</span>
</li>
routes.rb
Rails.application.routes.draw do
resources :users
resources :microposts
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
post '/signup', to: 'users#create'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
get '/users/admin', to: 'users#admin'
get 'tags/:tag', to: 'microposts#index', as: :tag
root 'static_pages#home'
end
micropost_controller
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def index
params[:tag] ? #microposts = Micropost.tagged_with(params[:tag]) : #microposts = Micropost.all
end
def show
#micropost = Micropost.find(params[:id])
end
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
def destroy
#micropost.destroy
flash[:success] = "You have deleted a task!"
redirect_to request.referrer || root_url
end
private
def micropost_params
params.require(:micropost).permit(:content, :tag_list, :tag,
{tag_ids: [] }, :tag_ids)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
Micropost model
class Micropost < ApplicationRecord
belongs_to :user
has_many :taggings
has_many :tags, through: :taggings
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :content, presence: true, length: {maximum: 140 }
attr_accessor :tag_list
def self.tagged_with(name)
Tag.find_by!(name: name).microposts
end
def self.tag_counts
Tag.select('tags.*, count(taggings.tag_id) as count')
.joins(:taggings).group('taggings.tag_id')
end
def tag_list
tags.map(&:name).join(', ')
end
def tag_list=(names)
self.tags = names.split(',').map do |n|
Tag.where(name: n.strip).first_or_create!
end
end
end
Tag model
class Tag < ApplicationRecord
attr_accessor :name
has_many :taggings
has_many :microposts, through: :taggings
end
static_pages controller
class StaticPagesController < ApplicationController
def home
if logged_in?
#micropost = current_user.microposts.build
#feed_items = current_user.feed.paginate(page: params[:page])
end
end
def help
end
def about
end
def contact
end
end
feed.html.erb
<% if #feed_items.any? %>
<ol class="microposts">
<%= render #feed_items %>
</ol>
<%= will_paginate #feed_items %>
<% end %>
I got the following error
ActionController::UrlGenerationError in StaticPages#home
No route matches {:action=>"index", :controller=>"microposts", :tag=>nil}, missing required keys: [:tag]
app/views/microposts/_micropost.html.erb:5:in `block in _app_views_microposts__micropost_html_erb___3891111682689684005_70324923859580'
app/views/microposts/_micropost.html.erb:5:in `map'
app/views/microposts/_micropost.html.erb:5:in `_app_views_microposts__micropost_html_erb___3891111682689684005_70324923859580'
app/views/shared/_feed.html.erb:3:in `_app_views_shared__feed_html_erb__3168328449514417483_70324923896060'
app/views/static_pages/home.html.erb:13:in `_app_views_static_pages_home_html_erb__3511776991923566869_70324898321240'
Can anyone suggest what might be wrong here? Please let me know if more information is needed.
Update: I have implemented some of the changes provided by the answer below, but still has not understood why :tag is not detected, and why the code in red is actually highlighted.
ActionController::UrlGenerationError in StaticPages#home
No route matches {:action=>"index", :controller=>"microposts", :tag=>nil}, missing required keys: [:tag]
The problem is you don't have an index route for your microposts.
Rails.application.routes.draw do
root 'static_pages#home'
get '/readme', to: 'static_pages#readme'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
post '/signup', to: 'users#create'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
get '/users/admin', to: 'users#admin'
resources :users
resources :microposts, only: [:create, :destroy] #Here's the problem
get 'tags/:tag', to: 'microposts#index', as: :tag
end
change to:
resources :microposts, only: [:index, :create, :destroy]
EDIT:
Another problem is
if logged_in?
#micropost = current_user.microposts.build #this just returns a new 1
#feed_items = current_user.feed.paginate(page: params[:page])
end
You probably want something like:
if logged_in?
#microposts = current_user.microposts
#feed_items = Micropost.all.paginate(page: params[:page])
end
This will give you all the user's microposts. Then you iterate through them in your views.
I actually found the cause of the problem to be quite simple. After running rails console, it seems like my db:seed wasn't even raked properly, causing my tags to have nil names and causing me to be unable to find the route. Looking further into Rails console is adding nil instead of values to solve my seed adding problem, I realised I have added attr_accessor, forgetting that normal attributes should be added via the command line into the Migration instead of writing into the Model directly. Removing it according to the post updates my database and the code works.
I'm using the mailboxer gem to build a messaging system between users for my rails application. For some reason, I'm getting this error:
(undefined method `receipts_for' for nil:NilClass)
Maybe it's because I should define 'receipts_for' somewhere in my controller or in the mailboxer.rb? I tried a few things.. but unfortunately none of them were successful.
This is my routes.rb:
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
get '/login', to: 'sessions#new'
get 'search/index'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :users do
member do
get :following, :followers
end
end
resources :conversations do
resources :messages
end
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :microposts, only: [:create, :destroy]
resources :relationships, only: [:create, :destroy]
resources :searches
end
This is my conversations controller:
class ConversationsController < ApplicationController
def show
#conversation = current_user.mailbox.conversation.find(params[:id])
end
def new
#recipients = User.find(params[:user_id])
end
def create
recipient = User.find(params[:user_id])
receipt = current_user.send_message(recipient, params[:body])
redirect_to conversation_path(receipt.conversation)
end
end
This is my messages_controller:
class MessagesController < ApplicationController
before_action :set_conversation
def create
receipt = current_user.send_message(#conversation, body)
redirect_to receipt.conversation
end
private
def set_conversation
#conversation = current_user.mailbox.conversations.find(params[:conversation_id])
end
end
The messaging system I'm building precisely, is a messaging system where the 'current_user' can go to the profile page of any user on the application and message him/her via the profile page. So this means that I'm rendering show.html.erb of the conversation model inside the show.html.erb of my users model. Here is the code of both views:
show.html.erb - Users Model:
<% provide(:title, #user.name) %>
<div class="row">
<aside class="col-md-4">
<section>
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
<h6>
<%= #user.gender %>
</h6>
<%= render "conversations/conversation" %>
</aside>
<div class="col-md-8">
<%= render 'follow_form' if logged_in? %>
<% if #user.microposts.any? %>
<h3>Microposts (<%= #user.microposts.count %>)</h3>
<ol class="microposts">
<%= render #microposts %>
</ol>
<%= will_paginate #microposts %>
<% end %>
</div>
</div>
_conversation.html.erb - show view of the conversations model.
<% #conversation.receipts_for(current_user).each do |receipt| %>
<div>
<%= receipt.message.body %>
</div>
<% end %>
<%= form_tag conversation_messages_path(#conversation), method: post do %>
<div>
<%= text_area_tag :body %>
</div>
<%= submit_tag %>
<% end %>
So how exactly can I define the 'receipts_for' method for my mailboxer? Or is something else wrong with my code?
Any help would be much appreciated!
Thanks!
I’ll piggy-back on what #mu is too short said. It’s important to pay attention to the whole error message. “undefined method receipts_for for nil:NilClass” is telling you that the receiver of the receipts_for method is nil. #conversation.receipts_for(current_user) looks to be the only place where receipts_for is used, so I would start debugging by making sure #conversation is assigned the value of an existing ActiveRecord-backed conversation object.
There seems to be too a lot going on, so I don't know how to offer you a quick fix. The value assignment in your ConversationsController#show method, #conversation= current_user.mailbox.conversation.find(params[:id]) looks cumbersome. That suggests to me that you're looking for a conversation based on a mailbox belonging to the current_user, which might be what you want (in which case, you need to have the appropriate associations defined in your models).
However, since the chain ends with conversation.find(params[:id]), I'm guessing that current_user.mailbox is not needed. Alternatively, if your params don't actually have a conversation id, then maybe that's what you need to focus on.
The good news is you can probably figure out how to define #conversation if you stick byebug (or binding.pry, depending on what you have installed) at the top of your show method and in your view partial:
# In the controller:
def show
binding.pry
#conversation = current_user.mailbox.conversation.find(params[:id])
end
# in the view
<% binding.pry %>
I'm making my first Rails application and mistakes abound. I've searched for "no route matches" questions, but I couldn't find one that fit my problem.
I have a User model. It has_many :tasks and it has_one :pet. Both Task and Pet models belong_to :user. Task and Pet have no pages of their own, they appear on User's page. With Task, this works fine. Here's the show action in users_controller.rb
def show
#user = User.find(params[:id])
#tasks = #user.tasks.paginate(page: params[:page])
#pet = #user.pet
end
I have a feed of #tasks on the #user show.html.erb page, as well as a form for creating tasks. I also have a form for creating a #pet. However, when I click the submit button on the #pet creation form, it goes to an error page.
Routing Error. No route matches [PATCH] "/pet/1"
Strangely, looking at the routes, I see this:
tasks POST /tasks(.:format) tasks#create
task DELETE /tasks/:id(.:format) tasks#destroy
pet_index POST /pet(.:format) pet#create
pet DELETE /pet/:id(.:format) pet#destroy
root GET / static_pages#home
pet#create has the address pet_index, even though I certainly did not do that on purpose. A user only has one pet, a pet index would make no sense. I created the Pets model in the same way I did the Tasks model, but somewhere, something went wrong. The differences between the two have to do with the fact that there are several #tasks that are created and destroyed, while the pet is just one.
Here is pets_controller.rb
class PetsController < ApplicationController
before_action :signed_in_user
def create
#pet = current_user.build_pet(pet_params)
if #pet.save
flash[:success] = "Your pet is ready!"
redirect_to root_url
else
render 'static_pages/home'
end
end
def destroy
end
private
def pet_params
params.require(:pet).permit(:nome)
end
end
I also have a shared/_pet.html.erb partial which contains code that can't access the user.
<%= #user.pet.nil? ? "Pet name" : #user.pet.name %>
If I put
<%= render 'shared/pet' %>
in home.html.erb, it gives me this error:
NoMethodError in StaticPages#home. undefined method `pet' for
nil:NilClass
It's a mess. I appreciate any help.
EDIT: Here's the pet creation form, in shared/pet_form.html.erb
<%= form_for(#pet) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :name, placeholder: "Give your pet a name." %>
</div>
<%= f.submit "Meet pet", class: "btn btn-small btn-default" %>
<% end %>
EDIT 2: Here's config/routes.rb
Petrefa::Application.routes.draw do
resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :tasks, only: [:create, :destroy]
resources :pet, only: [:create, :destroy]
root 'static_pages#home'
match '/register', to: 'users#new', via: 'get'
match '/help', to: 'static_pages#help', via: 'get'
match '/developers', to: 'static_pages#about', via: 'get'
match '/login', to: 'sessions#new', via: 'get'
match '/logout', to: 'sessions#destroy', via: 'delete'
end
EDIT 3: After having changed form_for(#pet) do into form_for(#pet, method: :create, action: '/pet') do this is the resulting HTML
<form accept-charset="UTF-8" action="/pet/1" class="edit_pet" id="edit_pet_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓"><input name="_method" type="hidden" value="create"><input name="authenticity_token" type="hidden" value="fC/PjvsGPG1sKSGvzFSFfzSKAhUZv99w6fkmiRkLRU0="></div>
<div class="field">
<textarea id="pet_nome" name="pet[nome]" placeholder="Dê um nome para o seu petRefa.">fulano</textarea>
</div>
<input class="btn btn-small btn-default" name="commit" type="submit" value="Conhecer pet">
</form>
First, if you want to create a pet, you should use POST and not PATCH request. A POST request will be redirected to the create method, whereas a PATCH request will be redirected to the update method, which doesn't exist and has no route for it in your PetsController. You can specify which action (like POST or PATCH) to use in the HTML form.
Secondly, are you sure that you set #user in StaticPages#home? That could be the reason for the undefined method `pet' for nil:NilClass`.
Also, I think that to render a partial, you should use <%= render partial: 'shared/pet' %>
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!
I have a controller "find_numbers", which I'm using to submit a form to the Twilio API. Before it submits though, I'd like to validate against two form fields, which aren't in the data model for this controller. The fields are :name, and :original_number
So, in my find_numbers model, I added attr_accessor :name, attr_accessor :originial number to run a validates command under it.
After doing that and submitting the form as invalid, I get the error :
Routing Error
No route matches {:controller=>"phone", :action=>"new"}
Try running rake routes for more information on available routes.
I'm not sure why it says there's no roots, but I'm not sure why it's accessing that anyways. I want it to POST to find_numbers
The find_numbers/new template
<%= form_tag("/find_numbers", :method => "post", :id => "new_user" ) do %>
<%= render 'shared/error_messages' %>
<%= label_tag(:name, "What Are You Tracking?") %>
<%= text_field_tag(:name) %>
<%= label_tag(:original_number, "Your Orginal Number") %>
<%= text_field_tag(:original_number) %>
<%= label_tag(:in_postal_code, "Near US postal code (e.g. 94117):") %>
<%= text_field_tag(:in_postal_code) %>
<%= label_tag(:near_number, "Near this other number (e.g. +4156562345)") %>
<%= text_field_tag(:near_number) %>
<%= label_tag(:contains, "Matching this pattern (e.g. 415***EPIC):") %>
<%= text_field_tag(:contains) %>
<%= submit_tag("Search", :class => "btn btn-large btn-primary") %>
<% end %>
here's my find_number model
class FindNumber < ActiveRecord::Base
attr_accessor :name
attr_accessor :original_number
validates :name, presence: true
validates :original_number, presence: true
end
Here's my Find_number controller
class FindNumbersController < ApplicationController
def new
#user = current_user
end
def create
#user = current_user
client = Twilio::REST::Client.new(#user.twilio_account_sid, #user.twilio_auth_token)
search_params = {}
%w[in_postal_code near_number contains].each do |p|
search_params[p] = params[p] unless params[p].nil? || params[p].empty?
end
local_numbers = client.account.available_phone_numbers.get('US').local
#numbers = local_numbers.list(search_params)
unless #numbers.empty?
render 'find_numbers/show'
else
flash.now[:error] = "Sorry, We Couldn't Find Any Numbers That Matched Your Search! Maybe Something Simpler?"
render 'find_numbers/new'
end
end
def show
end
end
Any thoughts on accomplishing this would be greatly appreciated!
Edit
Routes.rb file
Dct::Application.routes.draw do
resources :users
resources :sessions, only: [:new, :create, :destroy]
resources :phones, only: [:new, :create, :destroy]
resources :find_numbers, only: [:new, :create, :destroy]
match '/find_numbers', to: 'find_numbers#new'
match '/signup', to: 'users#new'
match '/login', to: 'sessions#new'
match '/signout', to: 'sessions#destroy', via: :delete
root to: 'static_pages#home'
match '/product_demo', to: 'static_pages#product_demo'
match '/pricing', to: 'plans#index'
match '/contact', to: 'static_pages#contact'
Edit
Here is the server log, of what happened when I hit submit
http://stepanp.com/railserror.jpg
Also, here's the find_numbers/show view
From what you've posted, the only other thing that looks suspicious to me is that you presumably have a PhonesController (plural) since you've declared resources :phones, but the routing error seems to occur because it is looking for a PhoneController (singular).