I'm trying to get some basic authentication/authorization with devise/cancan with Rails. Rather than using roles like Ryan B's screencast and other examples around I'm trying to do something basic:
1 - A user can log in
2 - A user can only edit/destroy their own articles (no roles, you're either logged in and can create new articles and edit/destroy your own or you're logged out and you can only see articles and login)
I'm using devise for the first part and that's working well but I can't get the second part working with CanCan. The the edit and destroy links for the articles don't appear when you're logged in and the direct URL (e.g. /articles/3/edit) still allows even if the article is for another user.
My ability.rb is
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.nil?
can :read, :all
else
# can :manage, :all #test - with this, all the edit/destroy links appear
can :manage, Article, :user_id == user
end
end
end
articles_controller.rb:
class ArticlesController < ApplicationController
before_filter :authenticate_user!, :except => [:index, :show] # for Devise
load_and_authorize_resource
# GET /articles
# GET /articles.xml
def index
#articles = Article.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #articles }
end
end
# GET /articles/1
# GET /articles/1.xml
def show
#article = Article.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #article }
end
end
# GET /articles/new
# GET /articles/new.xml
def new
#article = Article.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #article }
end
end
# GET /articles/1/edit
def edit
#article = Article.find(params[:id])
end
# POST /articles
# POST /articles.xml
def create
#article = Article.new(params[:article])
#article.user = current_user
respond_to do |format|
if #article.save
format.html { redirect_to(articles_path, :notice => 'Article was successfully created.') }
format.xml { render :xml => articles_path, :status => :created, :location => articles_path }
else
format.html { render :action => "new" }
format.xml { render :xml => #article.errors, :status => :unprocessable_entity }
end
end
end
# PUT /articles/1
# PUT /articles/1.xml
def update
#article = Article.find(params[:id])
respond_to do |format|
if #article.update_attributes(params[:article])
format.html { redirect_to(#article, :notice => 'Article was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #article.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /articles/1
# DELETE /articles/1.xml
def destroy
#article = Article.find(params[:id])
#article.destroy
respond_to do |format|
format.html { redirect_to(articles_url) }
format.xml { head :ok }
end
end
end
and the view partial that lists articles _article_list.html.erb:
<table>
<tr>
<th>Title</th>
<th>Description</th>
<th>User</th>
<th></th>
<th></th>
<th></th>
</tr>
<% #articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.description %></td>
<td><%= article.user_id %></td>
<td><%= link_to 'Show', article %></td>
<% if can? :update, #article %>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<% end %>
<% if can? :destroy, #article %>
<td><%= link_to 'Destroy', article, :confirm => 'Are you sure?', :method => :delete %></td>
<% end%>
</tr>
<% end %>
</table>
With this setup, the edit/destroy links in the view don't show up unless there's a blanket can :manage, :all, even can :manage, Article doesn't work. As I mentioned above, it also isn't restricting the actual actions as you're able to deep link straight to editing an article and it permits it.
I'm not sure what I'm doing wrong here. It would be great to get some help.
Thanks in advance
Jason
I managed to resolve my problem. I reset my environment (rvm - resintalled the gems and gemsets - ruby 1.9.2 and rails 3.0.0) and changed some of the code and all the issues I was having went away (redirect loop, view elements not changing based on being logged in, unauthorized controller actions still permissable). I've pasted ability.rb, articles_controller.rb, and _article_list.html.erb.
ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
if user
can :create, Article
can :read, :all
can :update, Article, :user_id => user.id
can :delete, Article, :user_id => user.id
else
can :read, :all
end
end
end
I guess it makes sense now but because only update and delete were supposed to be for the current user's articles, I split out the CRUD elements to be specific.
articles_controller.rb
class ArticlesController < ApplicationController
before_filter :authenticate_user!, :except => [:index, :show]
# load_and_authorize_resource # RESTful automated CanCam authorization - excludes non RESTful
# GET /articles
# GET /articles.xml
def index
#articles = Article.all
authorize! :read, #articles
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #articles }
end
end
# GET /articles/1
# GET /articles/1.xml
def show
#article = Article.find(params[:id])
authorize! :read, #article
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #article }
end
end
# GET /articles/new
# GET /articles/new.xml
def new
#article = Article.new
authorize! :create, #article
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #article }
end
end
# GET /articles/1/edit
def edit
#article = Article.find(params[:id])
authorize! :update, #article
end
# POST /articles
# POST /articles.xml
def create
#article = Article.new(params[:article])
#article.user = current_user
authorize! :create, #article
respond_to do |format|
if #article.save
format.html { redirect_to(articles_path, :notice => 'Article was successfully created.') }
format.xml { render :xml => articles_path, :status => :created, :location => articles_path }
else
format.html { render :action => "new" }
format.xml { render :xml => #article.errors, :status => :unprocessable_entity }
end
end
end
# PUT /articles/1
# PUT /articles/1.xml
def update
#article = Article.find(params[:id])
authorize! :update, #article
respond_to do |format|
if #article.update_attributes(params[:article])
format.html { redirect_to(#article, :notice => 'Article was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #article.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /articles/1
# DELETE /articles/1.xml
def destroy
#article = Article.find(params[:id])
#article.destroy
authorize! :delete, #article
respond_to do |format|
format.html { redirect_to(articles_url) }
format.xml { head :ok }
end
end
def by
#user = User.find(params[:id])
#articles = #user.articles
authorize! :read, #articles
end
end
load_and_authorize_resource works but I've put specific authorize! lines in each controller action as I have an extra action at the bottom. Both now work.
I updated the reference to #article to article to reference the current article in the list in _article_list.html.rb:
<table>
<tr>
<th>Title</th>
<th>Description</th>
<th>User</th>
<th></th>
<th></th>
<th></th>
</tr>
<% #articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.description %></td>
<td><%= article.user_id %></td>
<td><%= link_to 'Show', article %></td>
<% if can? :update, article %>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<% end %>
<% if can? :delete, article %>
<td><%= link_to 'Destroy', article, :confirm => 'Are you sure?', :method => :delete %></td>
<% end %>
</tr>
<% end %>
</table>
All working now. Thanks for the help here and hopefully this will help someone else out if they run into this problem.
Your condition for matching a user id isn't quite right. It should be:
can :manage, Article, :user_id => user.id
The attribute you want to check is mapped to the value you want to check against.
Also, you are checking for user.nil? when it can't be nil because you've just initialised it. (Probably a symptom of having tried lots of things!)
Does your catch work? If you uncomment the can :manage, :all line will a user be able to edit his/ her post ( along with everyone else's of course )?
Have you tried changing, can :manage, Article, :user_id == user to
can :manage, Article do |article|
article.try(:user) == user
I have never been able to get load an authorize to work- although I suspect that I was doing something wrong. To prevent someone from accessing the url directly, in your article's edit action, try adding this
unauthorized! if cannot? :edit, #article
Related
I've been trying to build a facebook/blog type app where comments show directly under new posts, and you can also POST a new comment by rendering a form directly under the corresponding Post. As you'll learn in the rest of my post, I'm fairly new to Rails so any resources that directly help me understand the issue I'm having (even if it's just pointing me to the right parts of the Rails docs) would be super helpful.
As of right now, my homepage renders the post#index action, as well as a a post form so that you can create new posts directly on the index page. All of this works fine until I try to render my comment form. As a note, I'm only focusing on creating a new comment before implementing editing and deleting (not that that should affect anything I wouldn't think).
Unfortunately I keep getting this error -
Moreover, when I split each of these pages into a more traditional, link_to XXXX_path style where every form and action is on its own url page, everything works fine. I think there might be something fundamental that I'm just not understanding. So, before showing my code I'll just give a quick explanation of my understanding of how I expect things to work in my app
On the index.html.erb view:
Render the index page
Show each individual post and any corresponding info I want to display with my each method.
pass the specific instance of Post using <%= render 'comment_form', :post => post %>. This should also give me access to all of the params of said instance of post in my _comment_form.html.erb partial.
On the _comment_form.html.erb partial:
add model: [post, #comment] (as my comment controller is nested in post) so that Rails knows to build a new comment with the associated post instance that was pushed through my index view.
At the end of the day, if I had to guess, my issue is with my controllers - I feel like I've put the correct code in the methods/actions, but my hunch is that there's something conflicting between the #post instance variable and the post instance being iterated over in my each method. I'm really not sure what the problem is and any help would be much appreciated. I hope I didn't over (or under) explain my problem. Thanks for the help in advance!
views/posts/index.html.erb
<p id="notice"><%= notice %></p>
<nav>
</nav>
<h1>Posts</h1>
<table>
<thead>
<tr>
<th>User</th>
<th>Body</th>
<th colspan="3"></th>
</tr>
</thead>
<%= render 'form' %>
<tbody>
<% #posts.each do |post| %>
<tr>
<td><%= link_to post.user.email, user_path(post.user_id) %></td>
<td><%= post.body %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<td>
</tr>
<tr>
<%= render 'comment_form', :post => post %>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Post', new_post_path %>
views/posts/_comment_form.html.erb
<% if user_signed_in? %>
<%= form_with(model: [post, #comment], url: post_comments_path, method: "post", local: true) do |form| %>
<div class="field">
<%= form.label :body %>
<%= form.text_area :body %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
<% else %>
Please sign in to comment on the post!
<% end %>
posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
#before_action :authenticate_user!
# GET /posts
# GET /posts.json
def index
#posts = Post.all.order("created_at DESC")
#post = post_exists?
end
# GET /posts/1
# GET /posts/1.json
def show
end
# GET /posts/new
def new
#post = current_user.posts.build
end
# GET /posts/1/edit
def edit
end
# POST /posts
# POST /posts.json
def create
#post = current_user.posts.build(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to index, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1
# PATCH/PUT /posts/1.json
def update
respond_to do |format|
if #post.update(post_params)
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { render :show, status: :ok, location: #post }
else
format.html { render :edit }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.find(params[:id])
end
# Only allow a list of trusted parameters through.
def post_params
params.require(:post).permit(:body)
end
def post_exists?
current_user.posts.build if current_user != nil
end
end
comments_controller.rb
class CommentsController < ApplicationController
def new
#post = Post.find(params[:post_id])
#comment = #post.comments.build
end
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(comments_params)
#comment.user_id = current_user.id
respond_to do |format|
if #comment.save
format.html { redirect_to posts_path, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: #comment }
else
format.html { render :new }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
private
def comments_params
params.require(:comment).permit(:body)
end
end
routes.rb
devise_for :users, controllers: {
sessions: 'users/sessions'
}
devise_scope :user do
get '/users/sign_out' => 'devise/sessions#destroy'
end
resources :posts do
resources :comments, only: [:new, :create, :destroy]
end
resources :users
root to: 'posts#index'
Models
>user.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
>post.rb
belongs_to :user
has_many :comments, dependent: :destroy
>comment.rb
belongs_to :user
belongs_to :post
The error is saying that it is missing the post_id on line 2 in your comment_form.
You got:
<%= form_with(model: [post, #comment], url: post_comments_path, method: "post", local: true) do |form| %>
The url is set to post_comments_path but it doesn't knows which post so you need to pass the post to it as an argument like so:
<%= form_with(model: [post, #comment], url: post_comments_path(post), method: "post", local: true) do |form| %>
Although this will probably solve the current error, you will hit another error since you also got #comment in the comment_form and in your posts#index you don't declare #comment. You also can't declare #comment because it is depending on the post. You could try to solve that with:
<%= form_with(model: [post, post.comments.new], url: post_comments_path, method: "post", local: true) do |form| %>
I'm new to RoR. I've been following a tutorial where I had to generate scaffold user first_name last_name and then migrate to the DB. For some reason, when I tried to push to a new branch in Git, some of the changes were lost. Then I couldn't load the local server, getting an error related to the DB. After long hours trying to figure out what was wrong I gave up and decided to destroy and re-migrate the DB.
I've tried to generate scaffold user first_name last_name, but console gives me an error:
The name 'User' is either already used in your application or reserved by Ruby on Rails. Please choose an alternative and run this generator again.
Whilst the index page looks ok and I can create users and log in/log out normally, when I try to access http://localhost:3000/users/, I get this error:
NoMethodError in Users#index
Showing /Users/Jen/nameofapp/app/views/users/index.html.erb where line #16 raised:
undefined method `first_name' for # User:0x007febdf5938a0
These are my code snippets:
views/users/index.html.erb
<p id="notice"><%= notice %></p>
<h1>Listing Users</h1>
<table>
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<tr>
<td><%= user.first_name %></td>
<td><%= user.last_name %></td>
<td><%= link_to 'Show', user , class:"btn btn-default btn-xs" %></td>
<td><%= link_to ('<span class="glyphicon glyphicon-pencil"></span>').html_safe, edit_user_path(user) %></td>
<td><%= link_to ('<span class="glyphicon glyphicon-remove"></span>').html_safe, user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<div class="col-sm-6 col-md-4">
<%= link_to 'New User', new_user_path, class:"btn btn-default btn-xs" %>
</div>
models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :orders
end
controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
before_action :set_user, only: [:show, :edit, :update, :destroy]
load_and_authorize_resource
# GET /users
# GET /users.json
def index
#users = User.all
end
# GET /users/1
# GET /users/1.json
def show
end
# GET /users/new
def new
#user = User.new
end
# GET /users/1/edit
def edit
end
# POST /users
# POST /users.json
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /users/1
# PATCH/PUT /users/1.json
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:first_name, :last_name)
end
end
You can use scaffold if you have already used Devise for User class. However, you can create another migration file to add these columns to users table
rails g migration AddFieldsToUser
Then inside the migration
def change
add_column :users, :first_name, :string
add_column :users, :last_name, :string
end
As you are a newbie, im writing these code snippets.
REFORMULATED FOR MORE INFO
I'll be rather short. As a newbie, that's the error I am getting while developing my rails app:
param is missing or the value is empty: task
The error highlights:
def task_params
params.require(:task).permit(:name, :description, :deadline, :status, :pdf, :done)
end
It happens when I click the button 'Mark as done' I'm creating.
Here follows the code:
app/views/tasks/index.html.erb:
<p id="notice"><%= notice %></p>
<h1>Tasks</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Time</th>
<th>Ready?</th>
<th colspan="10"></th>
</tr>
</thead>
<tbody>
<% #tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
.
.
.
<td><%= (link_to 'Mark done', task_path(task, done: true), method: :PUT) %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
app/controllers/tasks_controller.rb:
class TasksController < ApplicationController
before_action :set_task, only: [:show, :edit, :update, :destroy]
# GET /tasks
# GET /tasks.json
def index
#tasks = Task.all
end
# GET /tasks/1
# GET /tasks/1.json
def show
end
# GET /tasks/new
def new
#task = Task.new
end
# GET /tasks/1/edit
def edit
end
# POST /tasks
# POST /tasks.json
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render :show, status: :created, location: #task }
else
format.html { render :new }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /tasks/1
# PATCH/PUT /tasks/1.json
def update
respond_to do |format|
if #task.update(task_params)
format.html { redirect_to #task, notice: 'Task was successfully updated.' }
format.json { render :show, status: :ok, location: #task }
else
format.html { render :edit }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
# DELETE /tasks/1
# DELETE /tasks/1.json
def destroy
#task.destroy
respond_to do |format|
format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_task
#task = Task.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def task_params
params.require(:task).permit(:name, :description, :deadline, :status, :pdf, :done)
end
end
Thanks for the help!
since you've done this in controller as you said in command
before_action :set_task
def set_task
#task = Task.find(params[:id)
end
you should simply be able to do :
<td><%= 'Mark as done', tasks_setdone_path(task), method: :post %></td>
and not <td><%= 'Mark as done', tasks_setdone_path(task), method: :post %></td>
'#' represent instance variable accessible from controller AND template.
Another thing is that your action controller will require a respond (html or json).
Now that you answer is given, here is the proper way to do it.
task_path(#task, done: true), method: :PUT
POST is use for creation where PUT is use for updating an object.
Apologies if this has already been answered but I can't find anything that can help me. I am a newbie with Rails so please be gentle :D
I have been pulling my hair out trying to get nested forms working, I am sure I got nested forms working using Rails 3 and the railscasts demo last year, but Rails 4 is beating me.
Looking at the log, the query is being run to pull the data for the associated table, but nothing is rendered in the form.
I have read many web sites, but none have helped so far and I don't know where to start. The latest article I have followed is this http://www.createdbypete.com/articles/working-with-nested-forms-and-a-many-to-many-association-in-rails-4/
Still nothing being rendered in the view.
Where do I start debugging this, maybe my Rails install is broken?? But I am probably missing something crucial.
Thanks,
Royce
Edit - I have added some of the controllers and the view in question
surveys_controller.rb
class SurveysController < ApplicationController
before_action :set_survey, only: [:show, :edit, :update, :destroy, :answers]
# GET /surveys
# GET /surveys.json
def index
#surveys = Survey.all
end
# GET /surveys/1
# GET /surveys/1.json
def show
end
# GET /surveys/new
def new
#survey = Survey.new
end
# GET /surveys/1/edit
def edit
end
# POST /surveys
# POST /surveys.json
def create
#survey = Survey.new(survey_params)
respond_to do |format|
if #survey.save
format.html { redirect_to #survey, notice: 'Survey was successfully created.' }
format.json { render :show, status: :created, location: #survey }
else
format.html { render :new }
format.json { render json: #survey.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /surveys/1
# PATCH/PUT /surveys/1.json
def update
respond_to do |format|
if #survey.update(survey_params)
format.html { redirect_to #survey, notice: 'Survey was successfully updated.' }
format.json { render :show, status: :ok, location: #survey }
else
format.html { render :edit }
format.json { render json: #survey.errors, status: :unprocessable_entity }
end
end
end
# DELETE /surveys/1
# DELETE /surveys/1.json
def destroy
#survey.destroy
respond_to do |format|
format.html { redirect_to surveys_url, notice: 'Survey was successfully destroyed.' }
format.json { head :no_content }
end
end
def answers
#participants = Participant.all
#questions = #survey.questions
end
private
# Use callbacks to share common setup or constraints between actions.
def set_survey
#survey = Survey.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def survey_params
params.require(:survey).permit(:name,
:questions_attributes => [:id, :content,
:answers_attributes => [:id, :content, :participant_id]
])
end
end
participents_controller.rb
class ParticipantsController < ApplicationController
before_action :set_participant, only: [:show, :edit, :update, :destroy]
# GET /participants
# GET /participants.json
def index
#participants = Participant.all
end
# GET /participants/1
# GET /participants/1.json
def show
end
# GET /participants/new
def new
#participant = Participant.new
end
# GET /participants/1/edit
def edit
end
# POST /participants
# POST /participants.json
def create
#participant = Participant.new(participant_params)
respond_to do |format|
if #participant.save
format.html { redirect_to #participant, notice: 'Participant was successfully created.' }
format.json { render :show, status: :created, location: #participant }
else
format.html { render :new }
format.json { render json: #participant.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /participants/1
# PATCH/PUT /participants/1.json
def update
respond_to do |format|
if #participant.update(participant_params)
format.html { redirect_to #participant, notice: 'Participant was successfully updated.' }
format.json { render :show, status: :ok, location: #participant }
else
format.html { render :edit }
format.json { render json: #participant.errors, status: :unprocessable_entity }
end
end
end
# DELETE /participants/1
# DELETE /participants/1.json
def destroy
#participant.destroy
respond_to do |format|
format.html { redirect_to participants_url, notice: 'Participant was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_participant
#participant = Participant.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def participant_params
params.require(:participant).permit(:name)
end
end
application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
answers.html.erb
<h1><%= #survey.name %> Answers</h1>
<%= form_for(#survey) do |f| %>
<% #participants.each do |participant| -%>
<h3><%= participant.name %></h3>
<table>
<thead>
<tr>
<td>Questions</td>
<td>Answer</td>
</tr>
</thead>
<tbody>
<% #questions.each do |question| -%>
<tr>
<td><%= question.content %></td>
<td>
<%= f.fields_for :questions, question do |q| -%>
<%= q.fields_for :answers, question.answers.find_or_initialize_by(participant: participant) do |a| -%>
<%= a.text_area :content %>
<%= a.hidden_field :participant_id, participant.id %>
<% end -%>
<% end -%>
</td>
</tr>
<% end -%>
</tbody>
</table>
<% end -%>
<div class="actions">
<%= f.submit %>
</div>
<% end -%>
As you're new with Rails, let me explain how nested forms work for you!
--
Nested
Nested forms are not actually nested at all - they are associative forms.
You must remember that Rails (by virtue of being built on Ruby) is an object orientated framework. OOP (object orientated programming) is not just a buzzword - it's a fundamental core construction for your application & how it hands input / execution.
The problem many people have is they don't realize the true nature of Rails, and consequently become confused about how its many features work. If you appreciate that everything you do in Rails should be constructed around objects, life gets much simpler!
--
Form
With this in mind, you can begin to appreciate the role of objects throughout Rails, to the degree that you need to build / invoke objects for every element of your Rails application, including your form:
#app/models/survey.rb
Class Survey < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions
end
#app/controllers/surveys_controller.rb
Class SurveysController < ApplicationController
def new
#survey = Survey.new
#survey.questions.build #-> very important
end
end
#app/views/surveys/new.html.erb
<%= form_for #survey do |f| %>
...
<%= f.fields_for :questions do |q| %>
<%= q.text_field :title %>
<% end %>
<%= f.submit %>
<% end %>
This should create a form which allows you to pass associative data through to your child model. There are several important elements to consider:
You need to include accepts_nested_attributes_for in your "parent" model
You need to build your associative objects
You need to populate your form with the relative objects
By following this simple pattern, you'll be able to populate the nested form that you wish to show in the view
Try to use the following code:
<%= f.fields_for :questions do |q| -%>
<%= q.fields_for :answers, q.object.answers.find_or_initialize_by(participant: f.object.participant) do |a| -%>
<%= a.text_area :content %>
<%= a.hidden_field :participant_id, participant.id %>
<% end -%>
<% end -%>
and make sure that you render to answers.html.erb, you have accepts_nested_attributes_for :questions in survey.rb file, and accepts_nested_attributes_for :answers in question.rb file
Have you got accepts_nested_attributes_for :question in your survey model? And the same for the answer model?
I'm following this railscast and got stuck immediately: http://asciicasts.com/episodes/244-gravatar
Whenever I try to edit the index.html file I get this response from the server:
undefined local variable or method `user'.
By the looks of it, it shouldn't seem too difficult. I just need to swap a few lines here and there, but I am having a tough time.
This is what I have in index.html.erb:
<h1>Listing posts</h1>
<% #posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.title %></td>
<td><%= post.content %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
I would like to change it to this:
<% for user in #users %>
<tr>
<td><%= image_tag avatar_url(user) %></td>
<td><%= user.email %></td>
<td><%= link_to "Show", user %></td>
<td><%= link_to "Edit", edit_user_path(user) %></td>
<td><%= link_to "Destroy", user, :confirm => 'Are you ↵
sure?', :method => :delete %></td>
</tr>
<% end %>
My application helper code:
module ApplicationHelper
def avatar_url(user)
gravatar_id = Digest::MD5::hexdigest(user.email).downcase
"http://gravatar.com/avatar/#{gravatar_id}.png"
end
end
My post controller code:
class PostsController < ApplicationController
# GET /posts
# GET /posts.xml
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #posts }
end
end
# GET /posts/1
# GET /posts/1.xml
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
# GET /posts/new
# GET /posts/new.xml
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.xml
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.xml
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to(#post, :notice => 'Post was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.xml
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to(posts_url) }
format.xml { head :ok }
end
end
end
$ rails g scaffold user email:string
run it in console in the root folder of your application, then make needed changes in app/views/users/index.html.erb
In my case using Devise, I followed the same thing and got stuck as it would give me errors. So I replaced;
module ApplicationHelper
def avatar_url(user)
gravatar_id = Digest::MD5::hexdigest(user.email).downcase
"http://gravatar.com/avatar/#{gravatar_id}.png"
end
end
with;
module ApplicationHelper
def avatar_url(user)
gravatar_id = Digest::MD5::hexdigest(current_user.email).downcase
"http://gravatar.com/avatar/#{gravatar_id}.png"
end
end
I've also noticed that using;
wont work unless you capitalize the "User" looking like this;
I'm very fresh and new to this but after doing that everything works in my app. Hope this helps.
class Question < ApplicationRecord
def gravatar
"http://www.gravatar.com/avatar/#{Digest.MD5.hexdigest(email)}"
end
end
Learnt while going through pluralsight