In my application I have a project ,for each project user can upload many assets. The Upload is done by carrier wave.
I have two questions:
1)is there a better way of writing the create method?
2) How should I change my update method to update the attached files
The model,project.rb
class Project < ActiveRecord::Base
belongs_to :user
has_many :assets
end
the asset mode, asset.rb
class Asset < ActiveRecord::Base
belongs_to :project
belongs_to :user
mount_uploader :attachment, AttachmentUploader #CarrierWave
end
This is my Create method which works fine
def create
#project = Project.new(project_params)
respond_to do |format|
if #project.save
if params[:assets] && params[:assets]['attachment']
params[:assets]['attachment'].each do |a|
#asset = #project.assets.create!(:attachment => a, :user_id=>#project.user.id)
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
end
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def project_params
params.require(:project).permit(:user_id, :summary, :start_date,assets_attributes: [:id, :project_id, :attachment,:user_id] )
end
here is the Update method
def update
respond_to do |format|
if #project.update(project_params)
format.html { render :edit }
format.json { render json: #project.errors, status: :unprocessable_entity }
else
format.html { render :edit }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
You can use the accepts_nested_attributes_for method built into active record to easily handle creating, updating, and destroying nested models
http://railscasts.com/episodes/196-nested-model-form-revised?view=asciicast
EDIT FOR QUESTION CHANGES
You seem to have gone through many revisions to your original question, let me see if I can help clear some things up for you.
Let's assume that your asset class has a column called attachment and you have a carrierwave uploader class called AttachmentUploader. You can then mount that class to your asset class like you show in your question
mount_uploader :attachment, AttachmentUploader #CarrierWave
Your not showing your form markup in the question, but based on your controller's project_params method it seems that you're expecting a property called asset_attributes. You'll want to make sure that your form correctly builds that array, most easily accomplished by using the fields_for helper. If your confused about that part refer to this question Rails 4 Nested Attributes Unpermitted Parameters
Basically it should look like this
projects/_form.html.erb
<%= form_for #project, :html => {:multipart => true} do |f| %>
<%= f.error_messages %>
<%= f.fields_for :assets do |builder| %>
<%= render 'assets_fields', :f => builder %>
<% end %>
<p><%= f.submit %></p>
<% end %>
projects/_assets_fields.html.erb
<p>
<%= f.file_field :attachment %>
</p>
When the project form is submitted you don't need to loop over the assets manually determining if they are existing assets that are being updated, new assets that are being created, or assets flagged for deletion. That logic is all handled for you by adding this to your project class
accepts_nested_attributes_for :assets, :allow_destroy => true
Just calling
project.save
or
project.update
will automatically create/update/destroy the nested assets wrapping the whole process in a transaction so it all succeeds or all fails. The only expection to that being that active record can't wrap carrier wave actions in a database transaction so you may end up with orphaned file uploads on your server.
Related
Once again, i am having issues with form_for.
I have an activity model, and it look like:
class Activity < ActiveRecord::Base
has_many :acdocs, dependent: :destroy, autosave: true
accepts_nested_attributes_for :acdocs,
reject_if: proc { |attributes| attributes['descr'].blank?},
allow_destroy: true
end
And i have an acdoc model.
acdoc is short for activity document. o read somewhere that if i used the word "document" i could have some issues with JavaScript... then best safe than sorry.
class Acdoc < ActiveRecord::Base
belongs_to :activity
has_attached_file :document
validates_attachment :document,
:presence => true,
content_type: { content_type: ["image/jpeg", "image/gif", "image/png", "application/pdf"] }
end
Since activity can have many acdocs, i use a form_for to handles that:
<%= f.fields_for :acdocs do |acdocs| %>
<div>
<%= acdocs.label :descr" %>
<%= acdocs.text_field :descr %>
<%= acdocs.label :document %>
<%= acdocs.file_field :document b%>
</div>
<% end %>
<p>
<%= f.submit 'add doc', :name => "add_item" %>
</p>
<div class="actions">
<%= f.submit %>
</div>
And for the controller, i use this:
def new
#activity = Activity.new
#activity.acdocs.build
end
def create
#activity = Activity.new(activity_params)
if params[:add_item]
#activity.acdocs.build
render :action => 'new'
else
respond_to do |format|
if #activity.save
format.html { redirect_to #activity, notice: 'Activity was successfully created.' }
format.json { render action: 'show', status: :created, location: #activity }
else
format.html { render action: 'new' }
format.json { render json: #activity.errors, status: :unprocessable_entity }
end
end
end
end
def update
if params[:add_item]
unless params[:activity][:acdocs_attributes].blank?
for attribute in params[:activity][:acdocs_attributes].permit!
#activity.acdocs.build(attribute.last.except(:_destroy)) unless attribute.last.has_key?(:id)
end
end
#activity.acdocs.build
render :action => 'edit'
else
respond_to do |format|
if #activity.update(activity_params)
format.html { redirect_to #activity, notice: 'Activity was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #activity.errors, status: :unprocessable_entity }
end
end
end
end
This setup, kinda work. If i "add_item" a bunch of fields and select the files, all of then get uploaded.
The issue for me is, when a user press add_item, or even, edit a saved activity, the form will open. With the itens, the attach file button and the text: "no file attached". I am sure this file is meant to only tell the user were the file is being uploaded from (from his own computer) rather than the file stored on the app... but this will make the user think that no file was uploaded.
How can i put a text saying that the file is there, when it is there?
Also, this is not the fist time i have similar problems with form for. Sometimes, i want to show something if the object is already on the database. or if its a new one. (like, show a "destroy checkbox" for existing items, but hide for new ones)
How can i do these things?
You can check if the file exists. For example, you can check if the file_name attribute is set in your db, something like this acdoc.document?, or you can check if the file actually exists on the filesystem, like this: acdoc.document.exists?
show_destroy_checkbox if acdoc.document? or
show_destroy_checkobx if acdoc.document.exists?
Cheers!
i am new to ruby on rails...
i'm making a project on office space rent
each listing has many images
there's a listing table and images table
class Listing < ActiveRecord::Base
attr_accessible :agent_id, :description, :key_feature, :location_id, :size, :title, :office_type, :parking_ratio, :floors, :price, :nearest_metro, :distance, :image, :location_title, :display_order, :images_attributes
has_many:images, :dependent => :destroy
accepts_nested_attributes_for :image
end
class Image < ActiveRecord::Base
# attr_accessible :title, :body
attr_accessible :id, :image_url, :title_image, :listing_id, :created_at, :updated_at
belongs_to :listing
end
I want to make form for listings so that it also asks for adding images for that particular listing.It should make a blank tab for image_url and upload small image of it to its right.And below it should ask for add new image... clicking that renders another image_url tab and so on...
Listing should be saved in listings table and images should be saved in images table refering that listing..Please help me!!!
I've made form using following code :-
<%=f.fields_for :images do |image| %>
<%= image.label :image %><br />
<%= image.text_field :image_url %><br /> <br />
<%= image.hidden_field :listing_id, :value => params[:id] %>
It would show images already manually inserted in DB for listings and thereby, showing them when editing a particular listing.But when asking for making a new listing,it never shows image tab... So m helpless..I suspect whole structure might be wrong.. Please help me!
And here's my listing controller for update and create
def create
#listing = Listing.new(params[:listing])
respond_to do |format|
if #listing.save
format.html { redirect_to ([:member, #listing]), notice: 'Listing was successfully created.' }
format.json { render json: #listing, status: :created, location: #listing }
else
format.html { render action: "new" }
format.json { render json: #listing.errors, status: :unprocessable_entity }
end
end
end
def update
#listing = Listing.find(params[:id])
respond_to do |format|
if (#listing.update_attributes(params[:listing]))
format.html { redirect_to ([:member, #listing]), notice: 'Listing was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #listing.errors, status: :unprocessable_entity }
end
end
end
If I leave the input box blank. I get this error everytime. I don't want it to make new record when it's blank. when not, I want it to make new record.
this input box is nested and the code of controller is written like this to avoid error
def create
# Check if there is any contact info added
if params[:girl][:contact_attributes][:mail].empty?
params[:girl].delete(:contact_attributes)
end
#girl = Girl.new(params[:girl])
respond_to do |format|
if #girl.save
format.html { redirect_to #girl, notice: 'Girl was successfully created.' }
format.json { render json: #girl, status: :created, location: #girl }
else
format.html { render action: "new" }
format.json { render json: #girl.errors, status: :unprocessable_entity }
end
end
end
view is like this
<%= form_for(#girl) do |f| %>
....
<div class="field">
<%= f.label :mail %><br />
<%= f.fields_for :contact_attributes, #girl.contact do |contact| %>
<%= contact.text_field :mail %>
<% end %>
</div>
....
<div class="actions">
<%= f.submit %>
</div>
<% end %>
my model
class Girl < ActiveRecord::Base
has_many :users
has_one :contact
accepts_nested_attributes_for :contact
attr_accessible :id, :name_en, :name_ja, :gender_id, :contact_attributes, :photo, :tag_list
searchable do
text :name_en, :name_ja
text :contact do
contact.mail
end
end
has_attached_file :photo,
:styles => {
:thumb=> "100x100>",
:small => "400x400>" }
acts_as_taggable_on :tags
acts_as_commentable
end
You have to set
#girl = Girl.new
inside your else block, just before
format.html { render action: "new" }
The error happens because you render the new template and inside it the form_for(#girl) gets a nil object - #girl. In order to render the line <%= f.label :mail %><br /> it tries to call the mail method on the given #girl object in order to get its default value. Since the #girl object is nil and not set in the create action before you render the new template you get this error.
UPDATE:
I misunderstood your situation in the answer on the first part of this post. The solution in my opinion is redirecting to the new girl path instead of just rendering the new action. While rendering only renders the view redirecting will make a full-stack request process. Assuming you have the route new_girl_path set you should replace format.html { render action: "new" } with
format.html { redirect_to new_girl_path }
You can run `rake routes and see what named routes you have set.
I problem is the following few lines of code.
if params[:girl][:contact_attributes][:mail].empty?
params[:girl].delete(:contact_attributes)
end
If mail is empty in user contact you have removed the contact attributes and created only the user object.
So if you call #girl.contact you will get nil.
I don't know why you have removed the contact attributes.If you still want to do it you need to add one more line.
if #girl.save
format.html { redirect_to #girl, notice: 'Girl was successfully created.' }
format.json { render json: #girl, status: :created, location: #girl }
else
#Assuming you have the association like: user has_one contact
#user.build_contact
format.html { render action: "new" }
format.json { render json: #girl.errors, status: :unprocessable_entity }
end
And one more thing
<%= f.fields_for :contact_attributes, #girl.contact do |contact| %>
can be simply written as
<%= f.fields_for :contact do |contact| %>
Replace same line of code with <%= form_for( :girl, :url => {:action => :create}) do |f| %>
I'm new to Rails and making application where college members (teachers and students) can create posts and comment on them. Later on I wish to add nesting (ancestry) and points system in it.
I have Post, Comment and Member model. The Post model was made via Scaffolding, Member model was made with help of Devise, and Comment is just a model.
In my show page of Post, I'd like to have comments beneath the posts, I've made some progress (thanks to SO I came to know quite a bit) but now I am stuck with a problem that whenever I attempt to post a blank comment, rails was redirecting to the edit page. How to change this so that rails stays only on the show page and display errors?
For this I searched a bit, created a new method 'update_comments' in post_controller.rb and tried modifying the forms_for tag attributes, as in the code below, but now I get routing error on submitting.
app/models/member.rb
class Member < ActiveRecord::Base
#Associations
belongs_to :department
has_one :student, :dependent => :destroy
accepts_nested_attributes_for :student
has_one :nstudent, :dependent => :destroy
accepts_nested_attributes_for :nstudent
has_many :posts, :dependent => :destroy
has_many :comments, :dependent => :destroy
end
app/models/post.rb
class Post < ActiveRecord::Base
#Associations
belongs_to :member
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
app/models/comment.rb
class Comment < ActiveRecord::Base
# Associations
belongs_to :member
belongs_to :post
validates_presence_of :content
end
config/routes.rb
Urdxxx::Application.routes.draw do
devise_for :members
resources :posts do
member do
get 'update_comment'
end
end
root :to => 'posts#index'
app/controllers/posts_controller.rb
class PostsController < ApplicationController
# Devise filter that checks for an authenticated member
before_filter :authenticate_member!
# GET /posts
# GET /posts.json
def index
#posts = Post.find(:all, :order => 'points DESC')
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
...
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
#post = Post.new(params[:post])
#post.member_id = current_member.id if #post.member_id.nil?
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render json: #post, status: :created, location: #post }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.json
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.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
# Not made by scaffold
def update_comment
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Comment was successfully created.' }
format.json { head :no_content }
else
format.html { render action: "show" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
end
app/views/posts/show.html.erb
<p> Have your say </p>
<%= form_for #post, :url => {:action => 'update_comment'} do |p| %>
<%= p.fields_for :comments do |c| %>
<!-- Following 3 lines saved my life -->
<% if c.object.new_record? %>
<%= c.text_area :content, :rows => 4 %>
<%= c.hidden_field :member_id, value: current_member.id %>
<% end %>
<% end %>
<%= p.submit "Reply" %>
<% end %>
image of my show page:
http://i.stack.imgur.com/TBgKy.png
on making a comment:
http://i.stack.imgur.com/JlWeR.png
Update:
Looked back and made changes here, following what Ken said. I don't know how but it works for now.
app/controllers/posts_controller.rb
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.json { head :no_content }
elsif :comments
format.html { render action: "show" }
format.json { render json: #post.errors, status: :unprocessable_entity }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
You don't need a custom method. It is not very RESTful. See, e.g., http://www.sitepoint.com/restful-rails-part-i/ for info on REST. This is not a case where there is justification to use a custom method.
Whenever you find yourself adding custom methods you should think long and hard about whether it's necessary. Usually if you need custom methods what you actually need is another controller (or a different set of controllers).
The update method here is all you need. If you really want to go to the show method after a failed update (though I don't know why) then change the render edit call in the block in the update method after the update fails.
It seems like your real problem is the edit view isn't showing errors. Although the scaffold generated view should do that so maybe you changed it.
In case you missed it you may also benefit from this screencast:
http://railscasts.com/episodes/196-nested-model-form-part-1
You need to update the method type in route and also needs to sets the form post method to your new action, also when you submit a form its an post request not a get request.
Urdxxx::Application.routes.draw do
devise_for :members
resources :posts do
collection do
post :update_comment
end
end
root :to => 'posts#index'
<p> Have your say </p>
<%= form_for :post, :url => {:action => 'update_comment'} do |p| %>
<%= p.fields_for :comments do |c| %>
<!-- Following 3 lines saved my life -->
<% if c.object.new_record? %>
<%= c.text_area :content, :rows => 4 %>
<%= c.hidden_field :member_id, value: current_member.id %>
<% end %>
<% end %>
<%= p.submit "Reply" %>
<% end %>
I'm saving nested objects within the objects they belong to, but when i do that they do not use the controller im saving but the parents controller.
class Project < ActiveRecord::Base
belongs_to :company
belongs_to :user
has_many :tasks
accepts_nested_attributes_for :tasks, :allow_destroy => true
end
in the views i have something like this
<% form_for #project do |c| %>
<% c.fields_for :tasks, #project.tasks.last do |p| %>
<%= p.text_field :name %>
<% end %>
<%= submit_tag '+' %>
<% end %>
so what i'm trying to do, is update the user field with the fields for, that last field is specified in the controller.
def show
#project = Project.find(params[:id])
#project.tasks.build
#project.tasks.last.user = current_user # this should pass to the show.html.erb, to be saved back
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #project }
end
end
I'm thinking maybe the solution would be to check if the username is set in the nested objects, and if not to populate it with the current user in:
def update
#project = Project.find(params[:id])
#project.user = current_user
#find anything #project.....user blank and set to current user
respond_to do |format|
if #project.update_attributes(params[:project])
format.html { redirect_to(#project, :notice => 'Project was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #project.errors, :status => :unprocessable_entity }
end
end
end
I'm hoping that is the solution, and how do it do it?
an example of it running currently is at http://severe-fire-37.heroku.com