In my application I have form:
.row
= simple_form_for #post, html: { class: 'form-horizontal' },
wrapper: :horizontal_form do |f|
= f.error_notification
.col-md-8
= f.input :text, as: :prepend
= f.input :active
= f.button :submit
and custom input:
class PrependInput < SimpleForm::Inputs::Base
def input
template.content_tag(:div, class: 'col-md-12') do
template.content_tag(:div, class: 'input-group') do
template.concat cal_addon
template.concat #builder.text_field(attribute_name, input_html_options)
end
end
end
def cal_icon
"<i class=\"fa fa-calendar\"></i>".html_safe
end
def cal_addon
template.content_tag(:span, class: 'input-group-addon') do
template.concat cal_icon
end
end
def input_html_options
{class: 'form-control datepicker margin-top-none'}
end
end
Now I want to pass additional attributes to this option "as" and use them in this custom input, is there any possibility to do this thing?
Related
The :new view when redirected if validations are not matched and where I'd like to see the error messages:
<%= simple_form_for ([ #recipe, #recipe.comments.build]), class:"comment-form" do |f| %>
<%= f.error_notification %>
<%= f.object.errors.full_messages.join(", ") if f.object.errors.any? %>
<%= f.input :name, label: false, placeholder: "Your name", input_html: { value: #comment.name } %>
<%= f.input :comment, label: false, placeholder: "Tell us about your experience", input_html: { value: #comment.comment } %>
<%= f.submit "Submit", class: "btn-comment-submit" %>
<% end %>
This is my controller:
def new
#recipe = Recipe.find(params[:recipe_id])
#comment = Comment.new
#comment = #recipe.comments.build
end
def create
#comment = Comment.new(comment_params)
#recipe = Recipe.find(params[:recipe_id])
#comment.recipe = #recipe
if #comment.save
redirect_to recipe_path(#recipe)
else
render :new
end
end
You're not binding the #comment instance you have created in your controller to the form. Instead #recipe.comments.build always creates a new instance of Comment.
You can set the model with a conditional:
<%= simple_form_for([#recipe, #comment || #recipe.comments.build]) do |form| %>
<%= f.error_notification %>
<%= f.object.errors.full_messages.join(", ") if f.object.errors.any? %>
<%= f.input :name, label: false, placeholder: "Your name" %>
<%= f.input :comment, label: false, placeholder: "Tell us about your experience" %>
<%= f.submit "Submit", class: "btn-comment-submit" %>
<% end %>
Note that you don't need to set the values for the inputs. The form builder will do that for you. Thats kind of the whole point of it.
Or you can preferably ensure that you're setting #comment in the controller to keep the view as simple as possible:
class RecipiesController < ApplicationController
before_action :set_recipe
# ...
def show
#comment = #recipe.comments.new
end
# ...
end
<%= simple_form_for([#recipe, #comment]) do |form| %>
# ...
<% end %>
And you can clean up your create action and just create the comment off the recipe:
def create
#recipe = Recipe.find(params[:recipe_id])
#comment = #recipe.comments.new(comment_params)
if #comment.save
redirect_to #recipe
else
render :new
end
end
I have followed Nicolas Blanco's tutorial to make a "goal" wizard for my app.
There are two steps in the wizard. The first consisting of the form fields "name", "description" and "plan", the second has "deadline", which is a datetimepicker, "reporting frequency" and "days missed tolerance".
It seems to work when I click continue in the first step, but on clicking finish in the second step, the object #goal_wizard doesn't seem to include the parameters from the first step.
My goal.rb:
module Wizard
module Goal
STEPS = %w(step1 step2).freeze
class Base
include ActiveModel::Model
attr_accessor :goal
delegate *::Goal.attribute_names.map { |attr| [attr, "#{attr}="] }.flatten, to: :goal
def initialize(goal_attributes)
#goal = ::Goal.new(goal)
end
end
class Step1 < Base
validates :name, presence: true, length: { maximum: 50 }
validates :description, presence: true, length: { maximum: 300 }
validates :plan, presence: true, length: { maximum: 1000 }
end
class Step2 < Step1
validates :reporting_frequency, presence: true,
numericality: { greater_than_or_equal_to: 0 }
validates :days_missed_tolerance, presence: true,
numericality: { greater_than_or_equal_to: 0}
validates :deadline, presence: true
end
end
end
wizards_controller.rb:
class WizardsController < ApplicationController
before_action :load_goal_wizard, except: :validate_step
def validate_step
current_step = params[:current_step]
#goal_wizard = wizard_goal_for_step(current_step)
#goal_wizard.goal.attributes = goal_wizard_params
session[:goal_attributes] = #goal_wizard.goal.attributes
if #goal_wizard.valid?
next_step = wizard_goal_next_step(current_step)
create and return unless next_step
redirect_to action: next_step
else
render current_step
end
end
def create
# #user = current_user
# #goal = #user.goals.new(#goal_wizard.goal)
if #goal_wizard.goal.save
session[:goal_attributes] = nil
redirect_to root_path, notice: 'Goal succesfully created!'
else
redirect_to({ action: Wizard::Goal::STEPS.first }, alert: 'There were a problem creating the goal.')
end
end
private
def load_goal_wizard
#goal_wizard = wizard_goal_for_step(action_name)
end
def wizard_goal_next_step(step)
Wizard::Goal::STEPS[Wizard::Goal::STEPS.index(step) + 1]
end
def wizard_goal_for_step(step)
raise InvalidStep unless step.in?(Wizard::Goal::STEPS)
"Wizard::Goal::#{step.camelize}".constantize.new(session[:goal_attributes])
end
def goal_wizard_params
params.require(:goal_wizard).permit(:name, :description, :plan, :deadline, :reporting_frequency, :days_missed_tolerance)
end
class InvalidStep < StandardError; end
end
step1.html.erb:
<ol class="breadcrumb">
<li class='active'>Step 1</li>
<li>Step 2</li>
</ol>
<%= form_for #goal_wizard, as: :goal_wizard, url: validate_step_wizard_path do |f| %>
<%= render "error_messages" %>
<%= hidden_field_tag :current_step, 'step1' %>
<%= f.label :name %>
<%= f.text_field :name, class: "form_control" %>
<%= f.label :description %>
<%= f.text_field :description, class: "form_control" %>
<%= f.label :plan %>
<%= f.text_field :plan, class: "form_control" %>
<%= f.submit 'Continue', class: "btn btn-primary" %>
<% end %>
step2.html.erb:
<ol class="breadcrumb">
<li><%= link_to "Step 1", step1_wizard_path %></li>
<li class="active">Step 2</li>
</ol>
<%= form_for #goal_wizard, as: :goal_wizard, url: validate_step_wizard_path do |f| %>
<%= render "error_messages" %>
<%= hidden_field_tag :current_step, 'step2' %>
<%= f.label :deadline %>
<div class='input-group date' id='datetimepicker1'>
<%= f.text_field :deadline, class: "form-control" %>
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
<%= f.label "How often do I want to report? (1 = every day)" %>
<%= f.number_field :reporting_frequency, class: "form_control" %>
<%= f.label "How many times can I miss my report?" %>
<%= f.number_field :days_missed_tolerance, class: "form_control" %>
<script type="text/javascript">
$(function () {
$('#datetimepicker1').datetimepicker({
minDate:new Date()
});
});
</script>
<%= f.submit "Finish", class: "btn btn-primary" %>
<% end %>
Over here you're passing the goal_attributes to initialize, but you're never using them.
def initialize(goal_attributes)
#goal = ::Goal.new(goal)
end
If you look at Nicolas Blanco's code he doesn't make that mistake.
Here is my form:
<%= simple_form_for #item do |item_builder| %>
<div class="well">
<%= item_builder.input :name %>
<%= item_builder.input :description, as: :text %>
<%= item_builder.input :tag_list %>
<%= item_builder.simple_fields_for :user_items do |user_item_builder| %>
<%= user_item_builder.input :foo, as: :hidden, input_html: { value: "bar" } %>
<%= user_item_builder.simple_fields_for :user_item_images do |user_item_images_builder| %>
<%= user_item_images_builder.input :picture, as: :file,
input_html: { multiple: true,
name: "item[user_items_attributes][user_item_images_attributes][][picture]" } %>
<% end %>
<% end %>
</div>
<div class="clearfix">
<%= item_builder.submit 'Submit new item request'%>
</div>
<% end %>
and my items_controller
def new
#item = Item.new
#user_item = #item.user_items.build
#user_item.user_item_images.build
end
def create
#item = Item.new item_params
#item.user_items.first.user_id = current_user.id
if #item.save
redirect_to items_path, notice: "Thank you for your item request!"
else
render :new
end
end
def item_params
params.require(:item).permit(:name, :description, :tag_list,
user_items_attributes: [:item_id,
user_item_images_attributes: [:user_item_id, :picture]]).merge(created_by: current_user.id, status: Item::STATUS[:pending])
end
I am getting an error no implicit conversion of String into Integer that points to the first line in my create action. item_params is undefined in the webconsole. Any ideas where my error might be?
I have been struggeling with a nested form for a while and I dont know why it is not working.
The form updates/creates the venue without complaint, but not the associated venue_image.
Problem: I want to create/update a model and one of its associated models at the same time. Please note that the controllers are namespaced under "admin"
_form.haml
.col-md-12
= form_for([:admin, venue], html: { class: 'form-horizontal', multipart: true }) do |f|
- if venue.errors.any?
.col-md-12.alert.alert-danger{role: "alert"}
%h2
= pluralize(venue.errors.count, "Error")
%ul
- venue.errors.full_messages.each do |message|
%li= message
.form-group
= f.label :name, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :name, class: 'form-control'
.form-group
= f.label :category, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :category, class: 'form-control'
.form-group
= f.label :description, class: 'col-md-2 control-label'
.col-md-10
= f.text_area :description, rows: 5, class: 'form-control'
.form-group
= f.label :street, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :street, class: 'form-control'
.form-group
= f.label :zip, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :zip, class: 'form-control'
.form-group
= f.label :city, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :city, class: 'form-control'
.form-group
= f.label :homepage, class: 'col-md-2 control-label'
.col-md-10
= f.url_field :homepage, class: 'form-control'
%h3 Add images
= f.fields_for(:venue_image, html: { class: 'form-horizontal', multipart: true }) do |vi|
.form-group
= vi.label :name, class: 'col-md-2 control-label'
.col-md-10
= vi.input :name, label: false, class: 'form-control'
.form-group
= vi.label :venue_id, class: 'col-md-2 control-label'
.col-md-10
= vi.input :venue_id, label: false, class: 'form-control'
.form-group
= vi.label :default, class: 'col-md-2 control-label'
.col-md-10
= vi.input :default, as: :radio_buttons, label: false, class: 'form-control radio radio-inline'
.form-group
= vi.label :image_file, class: 'col-md-2 control-label'
.col-md-10
= vi.file_field :image_file, label: false, class: 'form-control'
.actions
= f.submit 'Save', class: 'btn btn-primary pull-right'
= link_to 'Cancel', :back, class: 'btn btn-default'
venues_controller
class Admin::VenuesController < Admin::BaseController
before_action :set_venue, only: [:show, :edit, :update, :destroy]
def index
#venues = Venue.all
end
def show
end
def new
#venue = Venue.new
#venue.venue_images.build
end
def edit
end
def create
#venue = Venue.new(venue_params)
if #venue.save
redirect_to admin_venue_path(#venue), notice: 'Venue was successfully created.'
else
render :new
end
end
def update
if #venue.update(venue_params)
redirect_to admin_venue_path(#venue), notice: 'Venue was successfully updated.'
else
render edit_admin_venue
end
end
def destroy
#venue.destroy
redirect_to admin_venues_url, notice: 'Venue was successfully destroyed.'
end
private
def set_venue
#venue = Venue.find(params[:id])
end
def venue_params
params.require(:venue).permit(:name, :category, :description, :street, :zip, :city, :homepage,
venue_image_attributes: [:name, :default, :image_file])
end
end
venue_images_controller
class Admin::VenueImagesController < Admin::BaseController
def new
image = VenueImage.new
render locals: { image: image }
end
def create
# TODO: Remove # if possible
#image = VenueImage.new(venue_images_params)
if #image.save
redirect_to admin_venue_path(#image.venue.id), notice: 'Image was successfully created.'
else
render admin_new_venue_path
end
end
private
def venue_images_params
params.require(:venue_image).permit(:name, :default, :image_file, :venue_id)
end
end
routes
namespace :admin do
resources :venues do
resources :venue_images
end
resources :users
end
Thanks in advance for any help! Please let me know if you need more of the code.
Seems like you have a has_many relation with the Venue(i.e, has_many :venue_images), then this line
= f.fields_for(:venue_image, html: { class: 'form-horizontal', multipart: true }) do |vi|
should be
= f.fields_for(:venue_images, html: { class: 'form-horizontal', multipart: true }) do |vi|
And your venue_params method should be like below
def venue_params
params.require(:venue).permit(:id, :name, :category, :description, :street, :zip, :city, :homepage, venue_images_attributes: [:id, :name, :default, :image_file])
end
Notice the plural venue_images and also I added :id to venue_params for Update to work correctly.
My form creates a question and answers. For true and false I currently have one answer being submitted that is the correct answer. I am trying to figure out how to make another answer with the same attributes except have the opposite value, either True or False.
Since the answers are created with the question I'm not sure what to do. I was thinking of the controller having something like, #question.answers.build(question: params[:content]) but am lost. Is there a #question.answers.build.where(content: (#question.content.opposite)) or something? Any help aprreciated
The controller:
def new_tf
#question = Question.new
#question.answers.build
end
def create
#question = Question.new(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
format.json { render action: 'show', status: :created, location: #question }
else
format.html { render action: 'new' }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
def question_params
params.require(:question).permit(:content, :question_type, :category, :product_id, :active, :user_id, answers_attributes: [ :content, :correct, :question_id ] ).
merge user_id: current_user.id
end
The form:
<h1>New True/False Question</h1>
<%= link_to 'Back', questions_path %>
<%= form_for #question, url: new_tf_question_path(#question) do |f| %>
<%= render 'shared/error_questions' %>
<%= f.label :content, "Question" %><br>
<%= f.text_field :content, class: "input-lg" %>
<%= f.label :category %><br>
<%= f.select :category, [ ["IP Voice Telephony", "ip_voice"], ["IP Video Surveillance", "ip_video_surveillance"], ["IP Video Telephony", "ip_video_telephony"], ["Enterprise Gateways", "enterprise_gateways"], ["Consumer ATAs", "consumer_atas"], ["IP PBX", "ip_pbx"] ], {prompt: "Select Category"}, class: "input-lg" %>
<%= f.label :product_id %><br>
<%= f.collection_select :product_id, Product.all, :id, :name, {prompt: "Select a product"}, {class: "form-control input-lg"} %>
<%= f.label :active %><br>
<%= f.check_box :active %>
<%= f.fields_for :answers do |builder| %>
<%= render 'tf_answers', :f => builder %>
<% end %>
<%= f.select :question_type, [["True False", "TF"]], {class: "form-control input-lg"}, style: "visibility: hidden" %>
<%= f.submit "Create Question", class: "btn btn-lg btn-primary", style: "margin-top: 45px;" %>
<% end %>
The _tf_answers.erb.rb
<%= f.check_box :correct, {checked: true, style: "visibility: hidden"} %>
<%= f.label :content, "Answer" %>
<%= f.text_field :content, :value => 'True', :readonly => true, :class => "input-lg", :id => "answer" %>
<%= button_tag "Toggle True/False", :id => "toggle", :class => "btn btn-small btn-inverse", :type => "button" %>
<script>
function checkAnswer() {
var answer = $('#answer').val();
if ('False' == answer) {
$("#answer").val('True');
} else {
$("#answer").val('False');
}
}
$(document).ready(function(){
$('#toggle').click(function () {
checkAnswer();
});
});
</script>
I ended up creating a new answer after the first one saved.
if #question.save
#answer = Answer.find_by_question_id(#question.id)
if #answer.content == "True"
Answer.create(content: "False", question_id: #answer.question_id, correct: false)
end
if #answer.content == "False"
Answer.create(content: "True", question_id: #answer.question_id, correct: false)
end
Ta-da!
Why doesn't your model automatically create both answers? Extend the save method in your model and you won't have to have this logic in your controller. It probably belongs there anyway.