I have the following model classes:
class Upload < ActiveRecord::Base
...
has_many :reviews, :order => "created_at DESC"
...
end
class Review < ActiveRecord::Base
...
belongs_to :upload
belongs_to :user
validates_presence_of :description
...
end
My upload/show view has a form for to capture a review for the specific upload:
<% form_for(#review) do |f| %>
<%= f.error_messages %>
...
<p>
<%= f.text_area :description, :rows => 5, :cols => 80 %>
</p>
...
<p>
<%= f.submit 'Submit Review' %>
</p>
<% end %>
When the review validation fails how do I display the error messages in the review form that is part of the upload/show view?
My ReviewController does this:
def create
#review = current_user.reviews.new(params[:review])
if #review.save
flash[:notice] = 'Review was successfully created.'
redirect_to( #review.upload )
else
render :action => :new
end
end
Obviously render :action => :new does not work because I need to display the show action of the UploadsController and not the new action of the ReviewsController.
I'm pretty sure there is a simple way to do this, I just can't figure it out!
Your review controller action should be receiving params['upload_id'] to associate the review with its upload, either through the URL (if reviews are a nested route like POST /uploads/1/reviews), or from a hidden field.
You can use render :template to do your redirection:
def create
#review = current_user.reviews.new(params[:review])
#upload = Upload.find(params['upload_id'])
#review.upload = #upload
if #review.save
flash[:notice] = 'Review was successfully created.'
redirect_to( #upload )
else
flash[:error] = 'Review could not be created.'
render :template => 'uploads/show'
end
end
It's also acceptable to just render the form for the review by itself (i.e. the default 'reviews/new') until the form entry is correct instead of showing the whole page for the upload.
Related
I am building a book repository in rails and I need to be able to add an author inside a book create form that passes the author post into the list of authors once the book has been added. In the same book creation resource I have already created the has_many: authors in the book.rb file and in the author.rb file I have created the belongs_to: author and that works fine I can select the books that author might have created with the following setup:
book.rb
class Book < ActiveRecord::Base
has_attached_file :jacket_cover, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :jacket_cover, :content_type => /\Aimage\/.*\Z/
validates :jacket_cover, :title, :synopsis, :body, presence: true
belongs_to :author
scope :available, ->{ where(available: true) }
scope :unavailable, ->{ where(available: [nil, false]) }
end
author.rb
class Author < ActiveRecord::Base
has_many :books
end
books_controller.rb
class BooksController < ApplicationController
before_action :set_book, only: [:show, :edit, :update, :destroy]
def index
#books = Book.all
end
def show
end
# GET /books/new
def new
#book = Book.new
end
# GET /books/1/edit
def edit
end
def create
#book = Book.new(book_params)
respond_to do |format|
if #book.save
format.html { redirect_to #book, notice: 'Book was successfully created.' }
format.json { render action: 'show', status: :created, location: #book }
else
format.html { render action: 'new' }
format.json { render json: #book.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #book.update(book_params)
format.html { redirect_to #book, notice: 'Book was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #book.errors, status: :unprocessable_entity }
end
end
end
def destroy
#book.destroy
respond_to do |format|
format.html { redirect_to books_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_book
#book = Book.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def book_params
params.require(:book).permit(:title, :synopsis, :body, :jacket_cover)
end
end
authors_controller.rb
class AuthorsController < ApplicationController
before_action :set_author, only: [:show, :edit, :update, :destroy]
def index
#authors = Author.all
end
def show
end
def new
#author = Author.new
end
# GET /authors/1/edit
def edit
end
def create
#author = Author.new(author_params)
respond_to do |format|
if #author.save
format.html { redirect_to #author, notice: 'Author was successfully created.' }
format.json { render action: 'show', status: :created, location: #author }
else
format.html { render action: 'new' }
format.json { render json: #author.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #author.update(author_params)
format.html { redirect_to #author, notice: 'Author was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #author.errors, status: :unprocessable_entity }
end
end
end
def destroy
#author.destroy
respond_to do |format|
format.html { redirect_to authors_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_author
#author = Author.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def author_params
params.require(:author).permit(:name, :biography, :books_ids => [] )
end
end
This allows me to create the books and the authors which is fine but I am now looking to nest the author create into my book create too.
form for book create
<%= simple_form_for(#book, :html => { :multipart => true }) do |f| %>
<%= f.error_notification %>
<div class="inputs">
<%= f.file_field :jacket_cover %>
<%= f.input :title %>
<%= f.input :synopsis %>
<%= f.input :body %>
</div>
<div class="actions">
<%= f.button :submit %>
</div>
<% end %>
form for author create
<%= simple_form_for(#author) do |f| %>
<%= f.error_notification %>
<div class="inputs">
<%= f.input :name %>
<%= f.association :books,
as: :check_boxes,
value_method: :id,
label: 'Books' %>
<%= f.input :biography %>
</div>
<div class="actions">
<%= f.button :submit %>
</div>
<% end %>
Is it possible to nest another resource into an already created resource so adding an author create inside a book create page?
Yes,you need accepts_nested_attributes_for and simple_fields_for helper provided for the simple_form_for(as you are using simple_form_for)
Step #1
In your Book model,you should add accepts_nested_attributes_for :author
Step #2
Modifying your new method of books_controller.rb
As you have belongs_to :author in your Book model,your new method of your BooksController would be
def new
#book = Book.new
#book.build_author #This is very important
end
Step #3
Your book_params method should be modified to
def book_params
params.require(:book).permit(:title, :synopsis, :body, :jacket_cover,author_attributes: [:name,:biography,..,..])
end
Step #4
Finally,your form for book create would be something like this
<%= simple_form_for(#book, :html => { :multipart => true }) do |f| %>
<%= f.error_notification %>
<div class="inputs">
<%= f.file_field :jacket_cover %>
<%= f.input :title %>
<%= f.input :synopsis %>
<%= f.input :body %>
</div>
<%= f.simple_fields_for :author do |a| %>
... author fields...
....................
<% end %>
<div class="actions">
<%= f.button :submit %>
</div>
<% end %>
Is it possible to nest another resource into an already created resource so adding an author create inside a book create page?
Yes.
You probably want a longer answer than that though.
You need to get nested attributes sorted out.
In Book model:
has_one :author, dependent: :destroy
accepts_nested_attributes_for :authors
In BooksController :new add line
#book = Author.new
#book.build_author
And in form for #book:
<%= form_for(#book, :html => { :multipart => true }) do |f| %>
<%= f.error_notification %>
<div class="inputs">
#book fields
</div>
<%= f.fields_for :author do |author| %>
#author fields
<%= f.button :submit %>
</div>
<% end %>
And don't forget to modify book_params method as shown in answer by Rich Peck.
Your question is somewhat verbose, but I'll detail what you need to know
--
Nested Objects
Is it possible to nest another resource into an already created resource so adding an author create inside a book create page?
Rails is built on top of Ruby (an object orientated language). This means Rails is object orientated too, and so if you wanted to create an author at the same time as creating a book (this is only applicable for create), you'll want to use the accepts_nested_attributes_for directive for your model:
#app/models/book.rb
Class Book < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author
end
#app/controllers/books_controller.rb
Class BooksController < ApplicationController
def new
#book = Book.new
#book.build_author #-> this will be authors.build if multiple
end
def create
#book = Book.new(book_params)
#book.save
end
private
def book_params
params.require(:book).permit(:title, :synopsis, :body, author_attributes: [:biography])
end
end
This will allow you to create the following (using form_for for simplicity sake):
#app/views/books/new.html.erb
<%= form_for #book do |f| %>
<%= f.text_field :title %>
<%= f.text_field :synopsis %>
<%= f.text_field :body %>
<%= f.fields_for :author do |a| %>
<%= a.text_field :biography %>
<% end %>
<%= f.submit %>
<% end %>
This will create the book record & associated author record, too
I'm trying to get the text from a text_area field in a form to save to a database in a different Model with the current Model's ID.
Currently, this works but only will save integers. If I put text into the 'Notes' field, then its saves it as a '0'. I suspect this is working correctly but I'm missing a piece to my puzzle. This is because I only want the 'Ticket' to save the note_id because I will have multiple 'Notes' per 'Ticket.
How can I get the Note to save in the Note Model, with an ID, and associate that note_id with this specific ticket?
Form - /app/views/tickets/_form.html.erb
<%= form_for(#ticket) do |f| %>
<% if #ticket.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#ticket.errors.count, "error") %> prohibited this ticket from being saved:</h2>
<ul>
<% #ticket.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.fields_for :notes do |u|%>
<%= u.label :note %>
<%= u.text_area :note, :size => "101x4", :placeholder => "Leave notes here." %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Tickets_controller.rb
class TicketsController < ApplicationController
# GET /tickets
# GET /tickets.json
def index
#tickets = Ticket.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #tickets }
end
end
# GET /tickets/1
# GET /tickets/1.json
def show
#ticket = Ticket.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #ticket }
end
end
# GET /tickets/new
# GET /tickets/new.json
def new
#ticket = Ticket.new
#ticket.notes.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #ticket }
end
end
# GET /tickets/1/edit
def edit
#ticket = Ticket.find(params[:id])
end
# POST /tickets
# POST /tickets.json
def create
#ticket = Ticket.new(params[:ticket])
respond_to do |format|
if #ticket.save
format.html { redirect_to #ticket, notice: 'Ticket was successfully created.' }
format.json { render json: #ticket, status: :created, location: #ticket }
else
format.html { render action: "new" }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
# PUT /tickets/1
# PUT /tickets/1.json
def update
#ticket = Ticket.find(params[:id])
respond_to do |format|
if #ticket.update_attributes(params[:ticket])
format.html { redirect_to #ticket, notice: 'Ticket was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
# DELETE /tickets/1
# DELETE /tickets/1.json
def destroy
#ticket = Ticket.find(params[:id])
#ticket.destroy
respond_to do |format|
format.html { redirect_to tickets_url }
format.json { head :no_content }
end
end
end
Note.rb
class Note < ActiveRecord::Base
belongs_to :ticket
attr_accessible :note, :ticket_id
end
Ticket.rb
class Ticket < ActiveRecord::Base
attr_accessible :notes_attributes
accepts_nested_attributes_for :notes
end
It is because note_id is an integer type.
Use nested models:
Refer this for Nested Models
Model:
class Ticket < ActiveRecord::Base
has_many :notes
attr_accessible :note_id, :notes_attributes
accepts_nested_attributes_for :notes
end
View:
<%= form_for(#ticket) do |f| %>
<%= f.fields_for :notes do |u|%>
<%= u.label :note %>
<%= u.text_area :note %>
<% end %>
<%= f.submit 'Submit' %>
<% end %>
What you have is a nested association, with Ticket as the "parent". The association is governed by the link from note_id in the Note model to the id (primary key) of the Ticket. What you're presently doing right now is manually manipulating that numeric association. Rails, knowing that the note_id column is supposed to be an integer, is taking the text you're trying to insert and turning it in to a number (zero in this case). You've probably got a bunch of orphaned rows right now because of this.
Ultimately, in order to accomplish what you're trying to do, your form will need to provide fields for that associated model. One way you can handle this is by using the accepts_nested_attributes_for in your Ticket model. Like so:
class Ticket < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
And in your form, you can easily create a nested form like so:
<%= form_for(#ticket) do |f| %>
<div class="field">
<%= f.fields_for :notes do |f_notes|%>
<%= f_notes.label :note %><br />
<%= f_notes.text_area :note, :size => "101x4", :placeholder => "Please leave notes here."%>
<% end %>
</div>
<% end %>
Edit Almost forgot: Check out this Railscast from Ryan Bates dealing with Nested Attributes
Edit 2 As codeit pointed out, you don't need the attr_accessible :note_id in Ticket. Since you've indicated that a Ticket has many Notes, and that Note belongs to Ticket, the foreign key column will appear in the Note model as ticket_id, which you already have. Having note_id in the ticket model is useless, and also nonsensical since has_many describes a plural relationship (which can't be expressed with a single column).
I'm working on my first rails project and am trying to use a file_field to upload photos that belongs_to :album. The form appears on my albums/show.html.erb page, since I wanted to show the album and have the user be able to upload pics from one place. However, when I press submit on the form, it doesn't seem to upload.
here is my photos/_form.html.erb
<%= form_for(#photo, :html => { :multipart => true }, :url => { :action => 'create'} ) do |f| %>
<%= f.label :avatar %>
<%= f.file_field :avatar %>
<%= f.submit %>
<% end %>
this is my albums/show.html.erb page. I added the if/else statement just to test if the #album instance was receiving the picture I uploaded, but it always comes back with "no"
<% if #album.photos.any? %>
yes
<% else %>
no
<% end %>
<div>
<%= render 'photos/form' %>
</div>
photos controller (i'm really confused as to what to set the instance variables in this)
class PhotosController < ApplicationController
def create
#album = Album.find(params[:user_id][:album_id])
#photo = #album.build(params[:photo])
respond_to do |format|
if #album.save
format.html { redirect_to #album, notice: 'Album was successfully created.' }
format.json { render json: #album, status: :created, location: #album}
else
format.html { render action: "new" }
format.json { render json: #album.errors, status: :unprocessable_entity }
end
end
end
album model
class Album < ActiveRecord::Base
attr_accessible :avatar, :name, :description
has_many :user_albums
has_many :users, :through => :user_albums
has_many :photos
end
photo model
class Photo < ActiveRecord::Base
belongs_to :album
end
Let me know if you need any other files
You will need,
#album.photos.build(params[:photo])
Also, I assume that your uploading mechanism is correct. :)
Good luck
I have a has_many and belongs_to association set up between two models: Project and Task.
I'd like to be able to create a form which enables me to create a new Task and assign an existing Project as a parent. For example, this form might have a pulldown for selecting from a list of existing projects.
There are only a finite set of projects available in this application, so I've created Project records via a seeds.rb file. I do not need to make a form for creating new Projects.
I believe I've achieved a solution by using a collection_select form helper tag in the new Task form. I'm pretty happy with how this works now, but just curious if there are other approaches to this problem.
#models/project.rb
class Project < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
end
#models/task.rb
class Task < ActiveRecord::Base
belongs_to :project
end
#controllers/tasks_controller.rb
class TasksController < ApplicationController
def new
#task = Task.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #task }
end
end
def create
#task = Task.new(params[:task])
respond_to do |format|
if #task.save
format.html { redirect_to(#task, :notice => 'Task was successfully created.') }
format.xml { render :xml => #task, :status => :created, :location => #task }
else
format.html { render :action => "new" }
format.xml { render :xml => #task.errors, :status => :unprocessable_entity }
end
end
end
end
#views/new.html.erb
<h1>New task</h1>
<%= form_for(#task) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="select">
<%= collection_select(:task, :project_id, Project.all, :id, :name) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= link_to 'Back', tasks_path %>
I just reviewed your code and this looks fantastic to me. One small tweak:
<%= f.collection_select(:project_id, Project.all, :id, :name) %>
This is just slightly cleaner in that you're still using the |f| block variable
Since you mentioned other approaches, I would definitely mention and actually recommend, you use formtastic. The associations are handled automatically and keeps your code clean and also gives you some great customization options.
In my Review model, I have the following:
class Review < ActiveRecord::Base
belongs_to :vendor
belongs_to :user
has_many :votes
validates_presence_of :summary
end
I submit a new entry as follows in the URL:
vendors/9/reviews/new
The new.html.erb contains a form as follows:
<%= error_messages_for 'review' %>
<h1>New review for <%= link_to #vendor.name, #vendor%></h1>
<% form_for(#review, :url =>vendor_reviews_path(#vendor.id)) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :summary %><br />
<%= f.text_area :summary, :rows=>'3', :class=>'input_summary' %>
<%= f.hidden_field :vendor_id, :value => #vendor.id %>
</p>
<p>
<%= f.submit 'Submit Review' %>
</p>
<% end %>
When I leave the field for :summary blank, I get an error, not a validation message:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.name
Extracted source (around line #3):
1: <%= error_messages_for 'review' %>
2:
3: <h1>New review for <%= link_to #vendor.name, #vendor%></h1>
I don't understand what is happening, it works if :summary is populated
def new
#review = Review.new
#vendor = Vendor.find(params[:vendor_id])
#review = #vendor.reviews.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #review }
end
end
def create
#review = Review.new(params[:review])
##vendor = Vendor.find(params[:vendor_id]) #instantiate the vendor from the URL id -- NOT WOKRING
##review = #vendor.reviews.build #build a review with vendor_id -- NOT working
#review = #current_user.reviews.build params[:review]#build a review with the current_user id
respond_to do |format|
if #review.save
flash[:notice] = 'Review was successfully created.'
format.html { redirect_to review_path(#review) }
format.xml { render :xml => #review, :status => :created, :location => #review }
else
format.html { redirect_to new_review_path(#review) }
format.xml { render :xml => #review.errors, :status => :unprocessable_entity }
end
end
end
My guess is that when it fails it is going to redirect_to new_review_path(#review) and so doesn't know the vendor it. How can I redirect to vendor/:vendor_id/reviews/new instead?
You probably don't have #vendor member variable set - but to fix this, it would be more correct to use not #vendor directly, but through your #review variable instance.
If you are creating new review, you already have #review member variable created, and you simply are populating fields in it - so, you need to set the vendor for #review (unless it's optional)... it would be more correct to use #review.vendor.name instead.
(If vendor is optional, then you obviously must catch all vendor.nil? cases.)
What code do you have in the new and create actions in your ReviewsController?
I suspect that your new Review is failing validation because the summary field is blank and then when the form is redisplayed on validation failure, the #vendor instance variable is nil.
You need to make sure that #vendor is assigned a value for both code paths.
I think you need to render :action => 'new' instead of your redirect_to new_review_path(#review). This will keep your error_messages on the #review object. By redirecting you are losing the old object and creating a new one.
As others has said, you also need to make sure you re-populate the #vender variable in your create method before rendering the view.
PS. I like to use the ardes resources_controller plugin for bog standard controller actions like these, makes life a lot easier for me and it handles nested resources really well.