Inserting data into multiple tables using one form - ruby-on-rails

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

Related

Ruby on rails, Save data in two tables

I have a question.
I have this model:
class Project < ApplicationRecord
has_many :documents
belongs_to :course_unit
belongs_to :user
has_and_belongs_to_many :people
has_one :presentation
has_and_belongs_to_many :supervisors, :class_name => "Person", :join_table => :projects_supervisors
end
and this model:
class Presentation < ApplicationRecord
belongs_to :project
has_and_belongs_to_many :juries, :class_name => "Person", :join_table => :juries_presentations
end
When I create a new project, I have many attributes of the model Project and two attributes (room and date) from Presentation model, so I don't know how to send data from room and date attributes to the presentation model.
So my question is: How can I create a new project that saves data in project table and presentation table?
UPDATE #1
My project controller:
def new
#project = Project.new
end
def edit
end
def create
#project = Project.new(project_params)
#project.build_presentation
respond_to do |format|
if #project.save
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #project.update(project_params)
format.html { redirect_to #project, notice: 'Project was successfully updated.'}
format.json { render :show, status: :ok, location: #project }
else
format.html { render :edit }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
private
def set_project
#project = Project.find(params[:id])
end
def project_params
params.require(:project).permit(:title, :resume, :github, :grade, :project_url, :date, :featured, :finished, :user_id, :course_unit_id, presentation_attributes: [ :date , :room ])
end
My index view for Projects is:
<%= form_for #project do |f| %>
<%= f.fields_for :presentations do |ff| %>
<%= ff.label :"Dia de Apresentação" %>
<%= ff.date_field :date %>
<%= ff.label :"Sala de Apresentação" %>
<%= ff.text_area :room %>
<% end
<%= f.submit %>
<% end %>
You can try something like this:
project = Project.new(name: 'project 1')
project.build_presentation(room: 'room 1', date: Time.current)
project.save
It will save project with name project 1 and presentation belongs to that project, with room room 1 and date is Time.current.
And you need to update your models to avoid presence validation.
class Project < ApplicationRecord
has_one :presentation, inverse_of: :project
end
class Presentation < ApplicationRecord
belongs_to :project, inverse_of: :presentation
end

Rails, form_for and paperclip

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!

Update controller does not save the attached file, ruby on rails

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.

Rails 4 Mass Assignment Whitelisting Parameters for Admin User

I've been looking around and trying to see how I would handle mass-assignment with Rails 4. I know this question has been beaten to death, but from my search, I've only come across answers that require the protected_attributes gem and attr_accessible. Now I'm not sure if that is still the industry standard on this, so I wanted to ask.
I'm using Rails 4.0.1 and I am trying to figure out how I can limit specific parameter updates to admin accounts only.
Here are the parameters:
:title, :summary, :content, :status
Now when a user creates a post, they can only update the following attributes:
:title, :summary, :content
However, if an admin updates the post, they are able to update
:title, :summary, :content AND :status
post_controller.rb
def create
#post = Post.new(post_params)
#post.status = 'pending'
respond_to do |format|
if #post.save
PostMailer.new_post(#post).deliver
format.html { redirect_to #post, notice: 'Post was successfully submitted.' }
format.json { render action: 'show', status: :created, location: #post }
else
format.html { render action: 'new' }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
def update
#categories = Category.all
respond_to do |format|
#post.slug = nil
if params[:post][:featured]
#image = Imgurruby::Imgur.new('20e2a9ef8542b15873a0dfa7502df0b5')
#image.upload(params[:post][:featured])
params[:post][:featured] = #image.url.to_s
end
if #post.update(post_params)
expire_fragment(#post)
#post.friendly_id
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
def post_params
params.require(:post).permit(:title, :summary, :category, :tags, :content, :user_id, :category_id, :slug, :featured, :views)
end
Would I be right in assuming that the best way would be to use an operator in the post_params method to check if the user is an admin, and if so, permit a different set of parameters?
of course, you can use different sets of parameters for different users or for different actions. you would do something like:
def update
if user.is_a? Admin
#post.update(post_params_admin)
else
#post.update(post_params_user)
end
end
def post_params_user
params.require(:post).permit(:title, :summary, :category, :content)
end
def post_params_admin
params.require(:post).permit(:title, :summary, :category, :tags, :content, :user_id, :category_id, :slug, :featured, :views)
end

How to avoid the error undefined method 'mail' for nil:NilClass

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| %>

Resources