I'm trying to build a small expense tracking app using Rails 4.1. Using devise for authorization. Expense and it's nested attribute, comments belong to a user. The associations are set up in the model and expenses are getting associated with the user. Here's the Expense controller:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
#item = #expense.items.build
##comment = #expense.comments.build
end
def index
#expenses = Expense.all
##items = Item.where(:expense_id => #expense.id)
end
def show
#expense = Expense.find(params[:id])
#items = Item.where(:expense_id => #expense.id)
end
def create
#expense = current_user.expenses.new(expense_params)
respond_to do |format|
if #expense.save
ExpenseMailer.expense_submission(#expense).deliver
format.html { redirect_to #expense, notice: 'Expense Report Submitted.' }
format.json { render :show, status: :created, location: #expense }
else
format.html { render :new }
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def edit
#expense = Expense.find(params[:id])
end
def update
#expense = Expense.find(params[:id])
##comment = #expense.comments.build
if #expense.update(expense_params)
#if #comment.save
#ExpenseMailer.comments_added(#expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
#else
# flash[:notice] = "Expense Report Updated"
#redirect_to expenses_path
##end
else
render 'edit'
end
end
The form from where the comment attributes are built looks like:
<%= nested_form_for (#expense) do |f| %>
<div class="form-group">
<%= f.label :state %><br />
<%= f.select :state, Expense.states, :include_blank => false, class: "form-control" %>
</div>
<%= f.fields_for :comments, #expense.comments.build do |comment| %>
<div class="form-group">
<%= comment.label :comment%>
<%= comment.text_area :comment, class: "form-control" %>
</div>
<%= comment.hidden_field :commenter %>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
The #comment.commenter = current_user isn't adding the current user id to the database. Should I include it in the expense controller somewhere?
You have to add:
#comment.commenter = current_user
below that if statement. Like this:
def create
#article = Expense.find(params[:expense_id])
if #comment = #expense.comments.create(comment_params)
#comment.commenter = current_user
#comment.save
ExpenseMailer.comments_added(#expense).deliver
redirect_to expenses_path
end
end
And then save the comment again. In your current code you're overwriting the #comment object with the newly created object by doing:
#comment = #expense.comments.create(comment_params)
but you haven't set the commenter on that new object anywhere yet.
Model
I just tried to create better code for your strong params, but I couldn't work out how to include the param in your nested attributes
I would therefore recommend using the inverse_of: method in your Comment model to get it sorted properly:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
belongs_to :user
has_many :comments, inverse_of: :expense
accepts_nested_attributes_for :comments
end
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :expense, inverse_of: :comments
before_create :populate_expense, on: :create
private
def populate_expense
self.commenter_id = self.expense.user_id
end
end
This should work if you're populating the comments from the accepts_nested_attributes_for directive
Comments
I don't understand why you've created two create actions for both your expenses and comments controllers - the controller action is meant to be independent of the Model
What I'm trying to say is that if you think the comments#create controller action will be invoked by your nested attribute creation, you'd be mistaken - it is only invoked when you send a request to it through the Rails router :)
If you're creating Comments and Expenses separately, you'll be able to use these two different actions; but they won't be invoked by each other. Only Model methods can be invoked by the controller (you shouldn't be calling other controller methods)
If you wanted to create a Comment from the expenses#show page, here's how you'd set it up:
#config/routes.rb
resources :expenses do
resources :comments #-> domain.com/expenses/:expense_id/comments/new
end
#app/controllers/expenses_controller.rb
Class CommentsController < ApplicationController
def new
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new
end
def create
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new(comment_params)
#comment.save
end
private
def comment_params
params.require(:comment).permit(:comment, :params).merge(commenter_id: current_user.id)
end
end
This will work if you wanted to create a comment from the expenses#show page. If you do this, you need to ensure you are calling the comments#new / comments#create actions, rather than those of the expenses controller
Related
Not sure how to title my question...
My intent is that users will have a list of items (and only the current_user can edit this list)
class Item < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :items
end
I have a Users controller (note: I'm also using devise gem, but created a separate Users Controller)
class UsersController < ApplicationController
load_and_authorize_resource
def show
#user = User.find(params[:id])
end
def list
#user.items.build
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(
items_attributes: [:user_id, :item, :name, :_destroy]).merge(user_id: current_user.id)
end
end
Right now I have set up routing
get 'users/:id/list', to: 'users#list', as: :list
Which gives me this: localhost:3000/users/1/list
Theoretically, this should be the show view... where it'll populate the list of items for user id: 1.
I want to be able to do localhost:3000/list/edit So the user can update this list.
My form in views/users/list.html.erb (I'm using cocoon gem for help with nested forms).
<%= simple_form_for #user do |f| %>
<%= f.simple_fields_for :items do |i| %>
<%= f.text_field :item %>
<%= f.text_field :name %>
<% end%>
<%= link_to_add_association 'add item', f, :items %>
<%= f.button :submit %>
<% end %>
I know my form is in my list.html.erb where it'll populate all the items, but I'm just doing this for testing purposes.
How do I update the form to save into items database and its associated with the current user?
EDIT:
def update
#user = current_user.items.find_by_user_id(current_user.id)
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'Congrats on your successful update!' }
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
There is nothing wrong with having a form in a "show" view. If you only want the current_user to view the form, then wrap your form in an if statement:
<% if #user == current_user %>
(form goes here...)
<% end %>
As long as you have an update route/action for users, that form should submit (disclaimer: I've never used the cocoon gem).
Edit: Your form should be fine as is. Add this route to your routes.rb file:
patch 'users/:id', to: 'users#update'
For current user authentication you should use cancan gem
https://github.com/ryanb/cancan
For nested fields related to user nested_form gem is the best option
https://github.com/ryanb/nested_form
I am trying to understand Rails' field_for, specifically what should go into the controller for nested resources. My issue is that when I create a comic with comic pages through the Comic form, the page's image are not saved.
I have Users, Comics, and ComicPages. Here are the models:
class User < ActiveRecord::Base
has_many :comics
has_many :comic_pages, through: :comics
end
class Comic < ActiveRecord::Base
belongs_to :user
has_many :comic_pages, :dependent => :destroy
accepts_nested_attributes_for :comic_pages
end
class ComicPage < ActiveRecord::Base
belongs_to :comic
end
Here is the form for Comic, where I also want to add comic_pages:
<%= form_for ([#user, #comic]) do |f| %>
<%= f.text_field :title %>
<%= f.fields_for :comic_pages do |comic_page| %>
<%= comic_page.file_field :comic_page_image %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I am confused about the comics_controller (new and create actions). How can I pass comic_page params to this controller???
def new
#user = current_user
#comic = #user.comics.new
#comic.comic_pages.build
end
def create
#user = current_user
#comic = #user.comics.new(comic_params)
#comic.comic_pages.build
respond_to do |format|
if #comic.save
format.html { redirect_to #user, notice: 'Comic was successfully created.' }
format.json { render action: 'show', status: :created, location: #user }
else
format.html { render action: 'new' }
format.json { render json: #comic.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comic
#comic = Comic.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comic_params
params.require(:comic).permit(:title, :synopsis)
end
def comic_page_params
params.require(:comic_page).permit(:comic_page_image, :comic_image_file_name)
end
Many thanks!
--- EDIT ---
After the answer for the params, I used it to create the following create action:
def create
#user = current_user
#comic = #user.comics.new(comic_params)
i = 0
until i = 1
#comic_page = #comic.comic_pages.new(comic_params[:comic_pages_attributes]["#{i}"])
#comic_page.save
i += 1
end
respond_to do |format|
if #comic.save
...
end
end
end
You need to permit those fields from comic_pages that you want to save through in the comic_params section of your controller
params.require(:comic).permit(:title, :synopsis, comic_pages_attributes: [:comic_page_image])
I have three models: Client, Car, and ParkingRate. Client has many cars, and car has many parking_rates. I have a form on the client page that creates a car associated with that client. What I don't know how to do is to add a field for parking_rate to that form, so that when a car is created for that client, a parking rate is also created for that car.
My code looks like:
client.rb
class Client < ActiveRecord::Base
has_many :cars, dependent: destroy
end
car.rb
class Car < ActiveRecord::Base
belongs_to :client
has_many :parking_rates
end
parking_rate.rb
class ParkingRate < ActiveRecord::Base
belongs_to :car
end
On the client page (client/:id), I have a form to create a car associated with that client, like this:
views/clients/show.html.erb:
<h1>Client information</h1>
... client info ...
<%= render 'cars/form' %>
views/cars/_form.html.erb:
<%= form_for([#client, #client.cars.build]) do |f| %>
<p>
<%= f.label :vehicle_id_number %><br>
<%= f.text_field :vehicle_id_number %>
</p>
<p>
<%= f.label :enter_date %><br>
<%= f.text_field :enter_date %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
The Clients and Cars controllers look like this:
clients_controller.rb:
class ClientsController < ApplicationController
def new
#client = Client.new
end
def create
#client = Client.new(client_params)
if #client.save
redirect_to #client
else
render 'new'
end
end
def show
#client = Client.find(params[:id])
end
def index
#clients = Client.all
end
def edit
#client = Client.find(params[:id])
end
def update
#client = Client.find(params[:id])
if #client.update(client_params)
redirect_to #client
else
render 'edit'
end
end
def destroy
#client = Client.find(params[:id])
#client.destroy
redirect_to clients_path
end
private
def client_params
params.require(:client).permit(:first_name, :last_name)
end
end
cars_controller.rb:
class CarsController < ApplicationController
def create
#client = Client.find(params[:client_id])
#car = #client.cars.create(car_params)
#parking_rate = #car.parking_rates.create(rate_params)
redirect_to client_path(#client)
end
def show
#client = Client.find(params[:client_id])
#car = Car.find(params[:id])
end
def edit
#client = Client.find(params[:client_id])
#car = Car.find(params[:id])
end
def update
#client = Client.find(params[:client_id])
#car = Car.find(params[:id])
#car.update(car_params)
redirect_to client_path(#client)
end
def destroy
#client = Client.find(params[:client_id])
#car = #client.cars.find(params[:id])
#car.destroy
redirect_to client_path(#client)
end
private
def car_params
params.require(:car).permit(:vehicle_id_number, :enter_date, :rate)
end
def rate_params
params.require(:parking_rate).permit(:rate)
end
end
With this I am able to add cars to a given client, but I would also like to add a parking_rate to a car on the same form. So right when I create a car using this form, I want to create an associated parking rate. The form_for helper uses a [#client, #client.comments.build] as the model object, so I am not sure how to reference the parking_rate model in the same form. I think the solution is to use a fields_for helper, what would be the model reference for that, and what would I need to add to the cars and client controllers?
In client.rb, add the line
accepts_nested_attributes_for :cars
I'm working on an app that allows users to comment on a single "work" (think blog post). The associations in the models are as follows:
class User < ActiveRecord::Base
has_many :works
has_many :comments
class Work < ActiveRecord::Base
belongs_to :user
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
belongs_to :work
There's a form on the Works show page that allows users to post a comment:
<%= form_for(#comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post a comment!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
The Works controller is as follows. Note that I'm adding the build comment functionality here so that the form on the Works page functions:
class WorksController < ApplicationController
#before_filter :current_user, only: [:edit, :update]
def index
#works = Work.all
#comment = #work.comments.build(params[:comment])
#comment.user = current_user
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #works }
end
end
def create
#work = current_user.works.create(params[:work])
redirect_to current_user
end
def edit
#work = current_user.works.find(params[:id])
end
def new
#work = current_user.works.new
end
def destroy
#work = current_user.works.find(params[:id]).destroy
flash[:success] = "Work deleted"
redirect_to current_user
end
def update
#work = current_user.works.find(params[:id])
if #work.update_attributes(params[:work])
flash[:success] = "Profile updated"
redirect_to #work
else
render 'edit'
end
end
def show
#work = Work.find(params[:id])
#comment = #work.comments.build
#comment.user = current_user
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Work", trackable_id: #work).all
#comments = #work.comments.order("created_at DESC").where(work_id: #work ).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #work }
end
end
end
And lastly, here is the Comments controller:
class CommentsController < ApplicationController
before_filter :authenticate_user!
def index
#comments = Comment.all
end
def show
#comment = Comment.find(params[:id])
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Comment", trackable_id: #comment).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #comment }
end
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(params[:comment])
flash[:success] = "Comment updated"
redirect_to #comment
end
end
def create
#work = Work.find(params[:id])
#comment = #work.comments.build(params[:comment])
#comment.user = current_user
if #comment.save
#flash[:success] = "Post created!"
redirect_to #work
else
render 'home#index'
end
end
end
end
When I attempt to submit a comment using the comment form on the works show view page, I get the following error:
Activerecord::RecordNotFound in CommentsController#create
Couldn't find Work without an ID
Why can't the application find the Work so that it can associate the comment to it?
EDIT 1:
Thanks to the answers below I edited the comment form:
<%= form_for(#work, #comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post feedback or contribute content
to this work!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
I'm still getting the same error after making the change to the form and adding the nested route.
I edited the routes file to include a nest for work comments:
authenticated :user do
root :to => 'activities#index'
end
root :to => "home#index"
devise_for :users
resources :users do
member do
get :following, :followers, :posts, :comments
end
end
resources :works do
resources :comments
end
resources :relationships, only: [:create, :destroy]
resources :posts
resources :activities
resources :comments
Rake routes shows the following for Comments#create:
POST /comments(.:format)
The POST URL (where the error shows up) is appURL/works/1/comments
Doesn't seem right. What do I need to change? Thank you so much for the help so far!!
Your form needs to be form_for([#work, #comment]) so that Rails knows to build a URL like /works/123/comments. Right now it would just be posting to /comments.
Check your rake routes to see the route for your CommentsController#create action. You might also need to tweak the controller to read params[:work_id] instead of params[:id].
The view helper form_for(#comment) will post to '/comments' by default. You can specify a url (see the guides) that includes the :id of the work record. The typical approach is to use form_for([#work, #comment]) and Rails will do this for you so long as you've set up your routes with comments as a nested resource of work.
I'm working on an app that allows users to comment on a single "work" (think blog post). The associations in the models are as follows:
class User < ActiveRecord::Base
has_many :works
has_many :comments
class Work < ActiveRecord::Base
belongs_to :user
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
belongs_to :work
In the comments table, a record has the following fields:
id
content
user_id
created_at
updated_at
work_id
In my Comments controller, I have the following Create action:
def create
#work = Work.find(params[:id])
#comment = #work.comments.create(params[:comment])
#comment.user = current_user
if #comment.save
#flash[:success] = "Post created!"
redirect_to root_url
else
render 'activities'
end
end
I'm trying to associate both the user AND the work to the comment but I get the following error message when I try to create a comment:
Unknown action
The action 'update' could not be found for CommentsController
I'm trying to use the following StackOverflow answer as a guide but the solution is not working for me:
Multiple Foreign Keys for a Single Record in Rails 3?
EDIT:
I have a add comment form on the works#show action:
def show
#work = Work.find(params[:id])
#comment = current_user.comments.create(params[:comment])
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Work", trackable_id: #work).all
#comments = #work.comments.order("created_at DESC").where(work_id: #work ).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #work }
end
end
The Comment form itself:
<%= form_for(#comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post a comment!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
I also have an update method on the Comments controller:
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(params[:comment])
flash[:success] = "Comment updated"
redirect_to #comment
end
end
The error message says:
The action 'update' could not be found for CommentsController
So, the issue is that your form is trying to call an update action on the CommentsController. This is unrelated to adding both the User and the Work instance as foreign keys. Your code for that seems right.
For sure if you persist the comment to the database during the show action:
#comment = current_user.comments.create(params[:comment])
Then the form helper will build an update form rather than a create form (because the model already exists):
<%= form_for(#comment) do |f| %>
If the desired action is to POST a comment create from the show page then try building the comment in the show action:
#comment = current_user.comments.build(params[:comment])