Good day,
I'm trying to create simple form with list of links in RoR 4 which can be edited and removed.
I've allowed "destroy" at the main post model file
controller->posts.rb
class Post < ActiveRecord::Base
has_many(:links, :dependent => :destroy)
accepts_nested_attributes_for :links, :reject_if => lambda { |a| a[:link].blank? }, :allow_destroy => true
and I'm accepting the parameters for destroy at the create and update controller
def create
#new_post = Post.new(params[:post].permit(:title, :body, :tag_list, links_attributes:[:link, :_destroy]))
if #new_post.save
redirect_to posts_path, :notice =>"Saved!"
else
render new
end
end
def update
#post_to_update = Post.find(params[:id])
if #post_to_update.update(params[:post].permit(:title, :body, :tag_list, links_attributes:[:link, :_destroy]))
redirect_to posts_path, :notice =>"Updated!"
else
render edit
end
end
I'm using jQuery to remove the link field and set its destroy value as "true"
<h1> Edit post </h1>
<%= form_for #post_to_edit do |f|%>
Title <%= f.text_field :title %> </br>
Body <%= f.text_area :body %> </br>
<%= f.fields_for :links do |b| %>
<li class = "enter_link">
<%= b.text_field :link %>
<%= b.hidden_field :_destroy %>
<%= link_to_function "Remove", "remove_fields(this)" %></br>
</li>
<% end %>
Tags <%= f.text_field :tag_list %>
<%= f.submit "Update that bitch!" %>
<% end %>
Javascript
function remove_fields(link) {
$(link).prev("input[type=hidden]").val("true");
$(link).closest(".enter_link").hide();
}
And here's the problem:
suppose I've got a list of 3 links
"link 1"
"link 2"
"link 3"
And I wish to edit that list by removing link number 2 and 3.
once I press update the destroy parameters is passed on to the controller, but it wont delete the original lines.
Now I'll get the following list
"link 1"
"link 2"
"link 3"
**"link 1" (again, after removing link number 2 and 3)**
As always,
your help is appreciated.
Let me make your life easier and recommend this gem called Cocoon (https://github.com/nathanvda/cocoon)
It creates simple nested forms.
Simply paste this code into your Post form view.
f.fields_for :links do |link|
render 'link_fields', :f => link
link_to_add_association 'add link', f, :tasks
With cocoon a partial is needed for the nested form, so create a file called _link_fields.html.erb
and inside make sure you place everything inside a div. Their documentation isnt clear on this, but from experience I do know its required.
<div class="nested-fields">
f.label :link
f.text_field :link
link_to_remove_association "remove link", f
</div>
And thats it!
change this:
def update
#post_to_update = Post.find(params[:id])
if #post_to_update.update(params[:post].permit(:title, :body, :tag_list, links_attributes:[:link, :_destroy]))
redirect_to posts_path, :notice =>"Updated!"
else
render edit
end
end
to this:
def update
#post_to_update = Post.find(params[:id])
if #post_to_update.update(
params[:post].permit(:title, :body, :tag_list,
## add `:id` to this one
links_attributes:[:id, :link, :_destroy])
##
)
redirect_to posts_path, :notice =>"Updated!"
else
render edit
end
end
you have to permit the id in your links_attributes params, so that the records don't get duplicated and for _destroy to work
Related
In a rails 5.2.3 app, I have a model Post, which uses active_storage to attach a file and has fields for duration and place. The duration must be present.
class Post
has_one_attached :video
validates :duration, presence: true
end
Using simple_form
the fields in the form are declared as
<%= f.input :duration %>
<%= f.input :place %>
<%= f.input :video %>
The controller has the following logic for the create
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = 'Post has been saved'
redirect_to root_path
else
#title = 'New Post'
#user = current_user
render :new
end
end
private
def post_params
params.require(:post).permit(:duration, :video)
end
If the validation fails, the form shows value of place, but I lose the file name for the video. This means the user has to choose the file again. How do I fix this?
Following Thanh's suggestion, I did check this SO question, and tried changing the simple_form field to
<%= f.hidden_field :video, value: f.object.image.signed_id if f.object.video.attached? %>
<%= f.file_field :video %>
This remembered the file name, but did not display it. So I did the following work around:
<% if f.object.video.attached? %>
<span><strong>Video File Name:</strong> <%= f.object.video.blob.filename.to_s %>. To change, choose different file below:</span>
<% end %>
<%= f.hidden_field :video, value: f.object.image.signed_id if f.object.video.attached? %>
<%= f.file_field :video %>
Hi I am using gem "nested_form" and included has_many association in my app sample code is :
class Question < ActiveRecord::Base
has_many :choices
accepts_nested_attributes_for :choices
end
and in my controller have included this :
class QuestionsController < ApplicationController
before_action :set_questions, only: [:edit, :update]
def edit
end
def update
if #question.update_attributes(question_params)
redirect_to questions_path
else
render :action => :edit
end
end
private
def set_questions
#question = Question.where(:id => params[:id]).first
end
def question_params
params.require(:question).permit(:content,
choices_attributes: [:option, :is_correct,
:question_id])
end
end
and in edit.html.erb
<%= nested_form_for #question do |f|%>
<%= f.label :content %>
<%= f.text_field :content %>
<%= f.fields_for :choices do |c| %>
<%= c.label :option %>
<%= c.text_field :option %>
<%= c.check_box :is_correct%>
<%= c.label :is_correct %>
<% end %>
<%= f.link_to_add "Add Choices", :choices%>
</br>
<%= f.submit %>
<% end %>
so in edit it adds choices even they are present and I have not even edit/add any of choices.
If I already have 3 choices with respect to question_id=1 so at the time of edit I have not edited any of choices nor I have added any new for that question_id but then too at the time of submit it creates 3 more choices. It gives this params on submit
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"jTLaIz0BdKbSZgnMl4T2GhZyYbKvo0JG2VD8e1zbvQGp6ILyKqLOZy19QvZrXhVGr5OClcwibWL0HJwIAGJ/rQ==",
"question"=>{"content"=>"Business logic is defined in ?",
"choices_attributes"=>{"0"=>{"option"=>"Model", "is_correct"=>"1",
"id"=>"36"}, "1"=>{"option"=>"view", "is_correct"=>"0", "id"=>"37"},
"2"=>{"option"=>"controller", "is_correct"=>"0", "id"=>"38"},
"3"=>{"option"=>"helpers", "is_correct"=>"0", "id"=>"39"}}},
"commit"=>"Update Question", "id"=>"10"}
Please guide me how to solve this. Thanks in advance.
The problem is in your question_params. You have to add :id for edit/update to work correctly else it will create new records on every successful submit.
def question_params
params.require(:question).permit(:id, :content, choices_attributes: [:id, :option, :is_correct, :question_id])
end
It might be happening because you haven't permitted id of choice in choices_attributes.
nested_form treats every choice attributes as creating new record on submit if it don't contain id.
You rails console must be giving unpermitted parameter as :id because you haven't passed id to the update_attributes thats why it is creating new object , all you need to do is
def question_params
params.require(:question).permit(:id, :content, choices_attributes: [:id, :option, :is_correct, :question_id])
end
I wanted to know how to go about have a submit button that will change a boolean attribute, instead of using radio buttons.
As a example if I display a list of 'published' and unpublished' article posts on the Post#index page and I wanted a button called 'Publish' that sets the :is_published field on the Post model to true.
I'm using strong_parameters on Rails 3.2.13
I was thinking in the Post controller I would have
def index
#post = Post.recent
end
def update
#post = Post.find_by_slug(params[:id])
if params[:publish_button]
#post.is_published = true
#post.save
redirect_to root_path
end
end
private
def post_params
params.require(:person).permit(:body, :publish_button)
end
In my Post#index view I have a form_for that has <%= f.submit 'Publish', name: publish_button %>
Here is the view in Post#index
<%= form_for #post do |f| %>
<div class="field">
<%= f.label :body %><br />
<%= f.text_field :body %>
</div>
<div class="actions">
<%= f.submit %>
<%= f.submit_tag 'Publish Post', name: 'publish_post' %>
</div>
<% end %>
The simple model follows
class Post < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
scope :recent, -> { order('updated_at DESC') }
end
But I'm getting an error of Required parameter missing: post
Thanks in advance.
Update
I've added a model and view that corresponds to the question. I hope it helps.
pass in your values as hidden fields, e.g.
= form_for #post do |f|
= f.hidden_field :is_published, value: "1"
= f.submit "Publish"
and if you want it to be inline like a button_to, give it the button_to class:
= form_for current_user.profile, html: { class: 'button_to' } do |f|
...
I've tried setting up a form validation that would ensure that at least 1 and at most 3 tags must be included in the form. But it isn't working as an empty form is still processed, but a form with 4 comma-seperated tags is validated correctly.
Controller
def update
#product = Product.find(params[:id])
current_user.tag(#product, :with => params[:product][:tag_list], :on => :tags)
if #product.update_attributes(params[:product])
redirect_to :root, :notice => "Added"
else
render :action => 'edit'
end
end
Form
<%= form_for #product do |f| %>
<%= f.label :tag_list, "Your tags" %> <%= f.text_field :tag_list, :value => #product.tags_from(current_user) %>
<p><%= f.submit "Change" %></p>
<%= f.error_messages %>
<% end %>
Model
validate :required_info
validates_size_of :tag_list,
:maximum => 3
private
def required_info
if( tag_list.empty? and description.empty? )
errors.add_to_base "Add one"
end
end
You could use a custom validation:
validates :tag_list_length
private
def tag_list_length
errors.add(:tag_list, "Must include at least one and no more than three tags") unless tag_list.length.between?(1,3)
end
if( tag_list.empty? and description.empty? )
errors.add_to_base "Add one"
end
Just looking at this part of the model, I think you'd rather do if(tag_list.empty? or description.empty?) because you want both of them to be filled.
For the second validation, I'm not an act_as_taggable user so I can't answer you now.
def create
#addpost = Post.new params[:data]
if #addpost.save
flash[:notice] = "Post has been saved successfully."
redirect_to posts_path
else
flash[:notice] = "Post can not be saved, please enter information."
end
end
If the post is not saved then it redirects to http://0.0.0.0:3000/posts , but i need to stay on the page, with text input fields so that user can input data.
post model
class Post < ActiveRecord::Base
has_many :comments
validates :title, :presence => true
validates :content, :presence => true
validates :category_id, :presence => true
validates :tags, :presence => true
end
new method
def new
#arr_select = { 1=>"One",2=>"Two" ,3=>"Three" }
#categories_select = Category.all.collect {|c| [ c.category_name, c.id ] }
end
new.html.erb
<h3>Add post</h3>
<%= form_tag :controller=>'posts', :action=>'create' do %>
<%= label :q, :Title %>
<%= text_field :data, :title, :class => :addtextsize %><br/>
<%= label :q, :Content %>
<%= text_area :data, :content, :rows=>10 , :class => :addtextarea %><br/>
<%= label :q, :Category %>
<%= select :data, :category_id, #categories_select %><br/>
<%= label :q, :Tags %>
<%= text_field :data, :tags, :class => :addtextsize %><br/>
<%= label :q, :Submit %>
<%= submit_tag "Add Post" %>
<% end %>
What should i do ?
flash.now with render is what you're looking for.
flash.now[:notice] = "Post can not be saved, please enter information."
render :new
Also instead of
flash[:notice] = "Post has been saved successfully."
redirect_to posts_path
you can just write
redirect_to posts_path, :notice => "Post has been saved successfully."
and it will do the same thing. It works only with redirect_to though, not with render!
Something like this should do what you want:
flash[:notice] = "Post can not be saved, please enter information."
render :new
UPDATE: You updated your question so I have to update my answer. Render is the right way to do this. However, it looks like you load some categories and some other collection of stuff in your new method. Those same instance variables should be available to your create method. The cleanest way to do this is put them into another method and have that method used as a before_filter applied to both create and new. Something like this:
before_filter :load_stuff, :only => [:create, :new]
def load_stuff
#arr_select = { 1=>"One",2=>"Two" ,3=>"Three" }
#categories_select = Category.all.collect {|c| [ c.category_name, c.id ] }
end
Then your new method is pretty much blank and calling render :new in your create method should work.
Hey this answer is super late but thought I'd add it for anyone that comes across it. Probably the most simple solution for what you want to achieve is to add required: true to all of the form inputs you want filled out. E.g
f.text_field :title, required: true, class: "whateverclassyouwant"
This way the form will ONLY be submitted if these fields have been filled in correctly and if not an error flash message will pop up on the field that it needs to be completed. The default flash messages that pop up can be custom styled also, Google how to do so.
This way you can remove the else redirect all together in your create method as it will never get to that point, and just have the if save, flash success etc.