The project which I am working in, is developed on Rails using haml markup to views. There is a view with a simple form like this:
= simple_form_for #message, url: [:admin, #request, #message], html: { class: 'form vertical-form} do |f|
= f.input :text, as: :text, input_html: { class: 'form-control', rows: 5 }
%br
= f.input :link_url, input_html: { class: 'form-control'}
%br
- if #message.has_picture_image?
= f.label :image
=link_to #message.picture_image, target: "_blank" do
= image_tag #message.picture_image(:thumb)
= f.file_field :image, class:'imagen-button'
= f.input_field :remove_picture, as: :boolean, inline_label: 'Remove'
%br
.form-actions
= f.submit(t('accept'), class: 'btn btn-large btn-primary')
= link_to(t('cancel'), [:admin, #message.request, #message], class: 'btn btn-large btn-danger')
and in Message model there is the bellow method:
def remove_picture
self.picture.destroy
end
The input_field is used to check if I want to remove the message image if it exists. I understood that input_filed gives me the option to check it so that when I click on accept button, it call the method remove_picture in the Message model. But, before the browser deploys the form, it rise the next error:
undefined method `to_i' for #<Picture:0x007f7675162b58>
Extracted source (around line #39):
37: = image_tag #message.picture_image(:thumb)
38: = f.file_field :image, class:'imagen-button'
39: = f.input_field :remove_picture, as: :boolean, inline_label: 'Remove'
40: %br
41: .form-actions
42: = f.submit(t('accept'), class: 'btn btn-large btn-primary')
and if I reload the page, this time the form is deployed. I guess this is because in the first time, as the picture exists then immediatly the remove_picture is called and the picture removed, and when I reload the form, as the picture already does not exist, the form is shown.
Obviously I am undestanding wrongly the input_field usage.
SimpleForms input_field is a helper which binds an input to a model attribute. It does not create a box which calls your method when the box is ticked! But rather it will call your remove_picture method when it rendering the form.
In some cases like checkboxes you will want to bind inputs to attributes that are not saved in the database. We call these virtual attributes. They are just like any old Ruby attributes:
class Message < ActiveRecord::Base
attr_accessor :remove_picture
# since this method is destructive it should have a bang (!)
def remove_picture!
self.picture.destroy
end
end
You could use it like this:
class MessagesController < ApplicationController
def update
#message.update(update_params)
#message.remove_picture! if message.remove_picture
# ...
end
def update_params
params.require(:message).allow(:remove_picture)
end
end
But there is a better way:
class Message < ActiveRecord::Base
has_one :picture_image
accepts_nested_attributes_for :picture_image, allow_destroy: true
end
accepts_nested_attributes_for lets us create an image with picture_image_attributes and destroy an image with:
#picture.update(picture_image_attributes: { _destroy: true })
This is how we would set up the form:
= simple_form_for #message, url: [:admin, #request, #message], html: { class: 'form vertical-form} do |f|
= f.input :text, as: :text, input_html: { class: 'form-control', rows: 5 }
%br
= f.input :link_url, input_html: { class: 'form-control'}
%br
- if #message.has_picture_image?
f.simple_fields_for :picture_image do |pif|
= pif.label :image
= link_to #message.picture_image, target: "_blank" do
= image_tag #message.picture_image(:thumb)
= pif.file_field :image, class:'imagen-button'
= pif.input_field :_destroy, as: :boolean, inline_label: 'Remove'
%br
.form-actions
= f.submit(t('accept'), class: 'btn btn-large btn-primary')
= link_to(t('cancel'), [:admin, #message.request, #message], class: 'btn btn-large btn-danger')
And your strong parameters whitelist:
def update_attributes
params.require(:message).allow(
:text,
:link_url,
picture_image_attributes: [:image, :_destroy]
)
end
Related
I have a form that I am using for new and edit, and the functionality for index and clicking for a new form and edit works, but when I go to create or update the form returns a load error to another controller.
The controller is PathCreationsController in system and it wants to use the creations controller in another place and it wants to use it to define it..which is weird because I don't have anything set to do that.
I tried adding a url to the form and setting the method to put but when I do that to try and force the controller to work correctly it sets all the params to null in the database so I assumed thats not a valid way to fix this.
Here is the controller:
class System::PathCreationsController < ApplicationController
def index
#paths = Path::Account.all
end
def new
#paths = Path::Account.new
end
def edit
#paths = Path::Account.friendly.find(params[:id])
end
def create
#paths = Path::Account.new
if #paths.save
redirect_to system_path_creations_path(#paths)
end
end
def update
#path = Path::Account.find_by(slug: params[:id])
if #path.update
redirect_to system_path_creations_path(#path)
end
end
end
Here is the form:
= form_for #paths do |f|
%br
.form-group
= f.label :name, class: 'control-label'
= f.text_field :name, class: 'form-control'
.form-group
= f.label :slug, class: 'control-label'
= f.text_field :slug, maxlength: 28, class: 'form-control'
.form-group
%p.text-muted Click to upload new icon.
.fileinput.fileinput-new{"data-provides" => "fileinput"}
%div
.fileinput-thumbnail.thumbnail{style: 'max-width: 100%;'}
.fileinput-preview{data: {trigger: "fileinput"}, style: 'max-width: 100%;'}
= image_tag #firms.try(:logo).try(:present?) ? #life_event.try(:logo).try(:url) : asset_path('/path.svg')
%div
%span.btn.btn-default.btn-file.btn-sm{style: 'display: none;'}
= f.file_field :logo, class: 'file'
= f.hidden_field :logo_cache
.form-group
= f.label :user_id
= f.select :user_id, User.all.collect {|u| [#{u.email}", u.id] }
= f.submit class: 'btn btn-primary btn-sm'
In the create action you never set update the parameters on the model
def create
#paths = Path::Account.new(path_params)
if #paths.save
redirect_to system_path_creations_path(#paths)
end
end
you will also need to add
def path_params
params.require(:path_account).permit(:name, :slug, :other, :params, :to, :permit)
end
I am trying to achieve Paypal integration with rails. Following this (http://railscasts.com/episodes/141-paypal-basics) I have a function in model which call paypal service with a return url. I have added a link in view that links to method in model.But some how rails is not able to get function in model.
What am i doing wrong?
My View :
form_for #order do |f|
- if #order.errors.any?
#error_explanation
h2 = "#{pluralize(#order.errors.count, "error")} prohibited this order from being saved:"
ul
- #order.errors.full_messages.each do |message|
li = message
.field
= f.label :first_name
= f.text_field :first_name
.field
= f.label :last_name
= f.text_field :last_name
.field
= f.label :card_number
= f.text_field :card_number
.field
= f.label :card_verification, "Card Verification Value (CVV)"
= f.text_field :card_verification
.field
= f.label :card_expires_on
= f.date_select :card_expires_on, {start_year: Date.today.year, end_year: (Date.today.year+10), add_month_numbers: true, discard_day: true}, {class: "browser-default"}
.field
= link_to 'PayPal', #order.paypal_url(current_user)<= link to model function
.actions
= f.submit
My Model : defined following function.
def paypal_url(return_url)
values = {
:business => 'xxxxx.XXXXX#techflex.com',
:cmd => '_cart',
:upload => 1,
:return => return_url,
:invoice => id
}
values.merge!({
"amount_#{1}" => item.unit_price,
"item_name_#{1}" => item.product.name,
"item_number_#{1}" => item.id,
"quantity_#{1}" => item.quantity
})
"https://www.sandbox.paypal.com/cgi-bin/webscr?" + values.to_query
end
Error :
NoMethodError in Orders#new
Showing C:/Users/Suniljadhav/SourceCode/TrainStation/app/views/orders/_form.html.slim where line #24 raised:
private method `paypal_url' called for #<Order:0x5e49bf8>
Trace of template inclusion: app/views/orders/new.html.slim
Rails.root: C:/Users/Suniljadhav/Source Code/TrainStation
It seems that paypal_url is a private method and you are trying to call it from outside of its class.
In your Order class you probably have the keyword private. Every method below that keyword will be declared private (as opposed to public) and an error will be thrown if you try to call it from outside the class. Private methods can only be called from within the class where they are defined. So try moving the definition of paypal_url so that it appears before private in your class.
You can read more about how this works here, and about the reasons behind it here.
I've just started a new app where I want to take a postcode in a form and save it to the database. My problem is that the create action doesn't seem to be being called no matter what I try.
Routes:
root 'postcodes#new'
resources :postcodes, only: [:new ,:create]
Controller: postcodes_controller.rb
class PostcodesController < ApplicationController
def new
#postcode = Postcode.new
end
def create
#postcode = Postcode.new(postcode_params)
if #postcode.save
flash[:success] = 'Success'
else
flash[:error] = 'Error'
end
end
private
def postcode_params
params.require(:postcode).permit(:code)
end
end
Model: postcode.rb
class Postcode < ApplicationRecord
validates :code, presence: true, uniqueness: true
end
View: postcodes/new.haml
.container
%form
%fieldset.form-group
= form_for #postcode do |f|
= f.label :postcode
= f.text_field :code, placeholder: 'Example Postcode', class: 'form-control'
= f.submit 'Submit', class: 'btn btn-primary'
I've attempted to pass more options in the form_for such as the method and action and now I have a feeling it's a routing error.
Any help will be appreciated.
Thanks.
I believe the problem you are experiencing is a result of your HAML.
You do not need to use, nor should you use, a form HTML element outside the form_for method call.
The form_for method will handle generating this HTML element/tag for you.
You have:
.container
%form
%fieldset.form-group
= form_for #postcode do |f|
= f.label :postcode
= f.text_field :code, placeholder: 'Example Postcode', class: 'form-control'
= f.submit 'Submit', class: 'btn ban-primary'
Which outputs an empty <form> element.
You should have:
.container
= form_for #postcode do |f|
%fieldset.form-group
= f.label :postcode
= f.text_field :code, placeholder: 'Example Postcode', class: 'form-control'
= f.submit 'Submit', class: 'btn ban-primary'
That should generate a proper <form> tag with the required action and method attributes populated with the right URL and 'post' so that your create action is called.
in my app I have form that looks like this
= simple_form_for #user do |f|
= f.input :name, error: false
= f.input :surname, error: false
Is there any way to avoid this repetitions (error: false)?
If they're all of the same type, something like this should work:
= simple_form_for #user do |f|
- [ :name , :surname ].each do |field|
= f.input field, error: false
If not, you could use a hash or something, instead of an array, and specify the type, as well.
It appears that simple form has the following option:
If you want to pass the same options to all inputs in the form (for
example, a default class), you can use the :defaults option in
simple_form_for. Specific options in input call will overwrite the
defaults:
<%= simple_form_for #user, defaults: { input_html: { class: 'default_class' } } do |f| %>
<%= f.input :username, input_html: { class: 'special' } %>
<%= f.input :password, input_html: { maxlength: 20 } %>
<%= f.input :remember_me, input_html: { value: '1' } %>
<%= f.button :submit %>
<% end %>
From https://github.com/plataformatec/simple_form
So, in your case:
= simple_form_for #user , defaults: { error: false } do |f|
= f.input :name
= f.input :surname
You could loop through an array of symbols
simple_form_for #user do |f|
[:name, :surname].each do |element|
f.input element, error: false
end
end
I have a large psychological test of 251 question. Each user can complete that test many times. So I created Summary model to represent each completion. Each Summary has many Answers. For each Summary I created a form that represents collection of answers, using Slim templater and simple_form gem:
= simple_form_for(#summary) do |f|
= f.simple_fields_for :answers do |a|
.question
= a.input :question_id, as: :hidden
div= a.object.question.title
- if a.object.question.kind_of? SpiritualityQuestion
ol class="sortable"
- a.object.question.sortable_variants.each do |sortable_variant|
= content_tag_for :li, sortable_variant
= sortable_variant.title
= a.input :text_data, as: :hidden, input_html: { class: 'sortable_data' }
- elsif a.object.question.kind_of? MultilineQuestion
div Time remaining: <span class="time">60</span> s.
= button_tag 'Start', type: 'button', class: 'start_button btn btn-primary'
= a.input :text_data, label: false, input_html: { class: 'span8 timed_text', cols: '60', rows: '20', disabled: true }
- else
= a.association :variant, collection: a.object.question.variants, as: :radio, label: false
br
= f.input :user_id, as: :hidden
= f.input :psy_test_id, as: :hidden
.actions
= f.button :submit, value: 'Save', class: 'btn btn-large btn-success'
And I have related controller action:
#summary = Summary.where(completed: false, user: current_user, psy_test: PsyTest.first)
.includes(:answers => { :question => :variants })
.first_or_initialize
#summary.build_answers if #summary.new_record?
Summary.build_answers:
def build_answers
# Creating answers for questions
psy_test.questions.includes(:variants).each do |q|
answers.new(question: q)
end
end
Now I'm trying to make the test to be paginated, because it's very large and the form generates very slowly. So I want to add limit and offset to answers. Something like that:
= f.simple_fields_for answers.limit(#limit).offset(#offset) do |a|
How it can be made?
I've looked to do field_for source and found simple answer, which I wasn't able find in any guide:
= f.simple_fields_for :answers, #summary.answers.limit(#limit).offset(#offset) do |a|