Rails using a layout for new and edit form - ruby-on-rails

In my application I have a form for a new action that is the same, four or five lines apart, that my edit form.
The problem that i encounter is that my new.html.haml gives me an error explainning that it doesn't know what "f" is.
The f is the one in "form_for #object do |f|".
Obviously in my new the form_for's object is not the same that the edit one.
I think that it should be a common behaviour but i can't seem to find anything that suits my needs in the rails documentation.
Here a quick exemple of code
#new.html.haml
%div{ id: "StaticForms" }
= f.label :project_id
= f.collection_select :project_id, #projects, :id, :name,
{ selected: #edit.task.project_id },
{ class: "form-control", onchange: 'get_projects(this.options[this.selectedIndex].value);' }
- index = 1
- #project_tree.each do |task|
= render partial: "edit", locals: {task: task, index: index} // render here was okay before "layouting"
- index += 1
#form_layout.html.haml%html
%div{ :class => "col-md-12" }
%h2 Reporting
%div{ :id => "Form"}
= form_for #activity do |f|
= f.label :consultant_id
= f.collection_select :consultant_id, #consultants, :id, :name,
{ selected: current_user.consultant.id },
{ class: "form-control" }
= yield
The JS tag "onchange: ..." you can see on the new.html.haml file is supposed to be triggered by JS that i have in the layout file fyi.
The error i have is : "undefined local variable or method `f' for #<#:0x007feb84e6b848>"
Can someone help with a link or an explanation ? :)
Thank you.
Best regards.
Edit : Added some code, did not think it was relevent for that type of issue :>

#app/views/activities/_form.haml
= form_for activity do |f|
= f.label :consultant_id
= f.collection_select :consultant_id, #consultants, :id, :name,
{ selected: current_user.consultant.id },
{ class: "form-control" }
...
#app/views/activities/new.haml
= render "form", activity: #activity
#app/views/activities/edit.haml
= render "form", activity: #activity

So i finnaly get around my problem and i was able to fix it.
new and edit views :
= render layout: 'form_activity', locals: {activity: #object} do |f| # here #objet depends on the controller method i.e. #activity for new, #edit for edit
= f.label ...
...
#relativly the same code in edit and new
_from_activity.html.haml (in the same folder than the views above) :
= form_for activity do |f|
... # some form stuff
= yield f # here i get back to new or edit code
... # some stuff - rest of the form.
This works perfectly !
Layout stuff -- before the yield.
--
Views stuff - thanks to the line : 1) render layout: ... do | f | in the view and 2) yield f in the layout
--
Layout stuff - after the yield.
Thx all of you for your answers.
By the way, i'm aware that the first question wasn't asking the result i just post. If so my apologies :/
Best regards.

Related

Using js.erb in rails

In Rails 5 app with devise, I need to use a new.js.erb file to update select tag in my registrations view and controller. I cant seem to figure out why my new.js.erb file isn't working.
I've tried to use respond_to in controller as below,
registrations-controller.rb
def new
super
#cities = CS.get(:us,params[:state])
respond_to do |format|
format.js { render '/new.js.erb' }# layout: false }
format.html
end
end
new.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), :remote => true) do |f| %>
<div class="signup-input-container">
<div class="field">
<%= f.text_field :firstname, autofocus: true, autocomplete: "firstname", placeholder: "First name", class: "signup-input-container--input" %>
</div>
<div class="field">
<%= f.select :state, options_for_select(CS.states(:us).map { |code, name| [name, code] }),{:prompt => "State"}, {:class => "signup-input-container--input", :id => "state-picker"} %>
</div>
<div class="field">
<%= f.select :city, options_for_select([]),{}, {:class => "signup-input-container--input", :id => "city-picker"} %>
</div>
</div>
<% end %>
new.js.erb
var city = document.getElementById("city-picker");
while (city.firstChild) city.removeChild(city.firstChild);
var placeholder = document.createElement("option");
placeholder.text = "Choose a city";
placeholder.value = "";
city.appendChild(placeholder);
<% #cities.each do |c| %>
city.options[city.options.length] = new Option('<%= c %>');
<% end %>
main.js
var state = document.getElementById("state-picker");
state.addEventListener("change", function() {
$.ajax({
url: "/states?state=" + state.value,
type: "GET"
})
})
I'm expecting this to create select tag options with my array of cities in my controller. Does anyone know how to get this to work?
To solve this you should just setup a separate controller where you can fetch the data from asynchronously and alternatively there are also several free API's which can be used for geographical lookup such as Googles Geocoding API and Geonames.
To setup a separate controller you can do it by:
# /config/routes.rb
get '/states/:state_id/cities', to: 'cities#index'
# /app/controllers/cities_controller.rb
class CitiesController < ApplicationController
# GET
def index
#cities = CS.get(:us, params[:state_id])
respond_to do |f|
f.json { render json: #cities }
end
end
end
I would skip using a .js.erb template altogether and just return JSON data which you can use directly in your JS or with one of the many existing autocomplete solutions. .js.erb only makes sense for extensive HTML templating (like for example rendering an entire form) where you want to reuse your server side templates - it greatly increases the complexity and generally makes a mess of your javascript which is not worth it just to output a list of option tags.
// If you are using jQuery you might as well setup a delegated
// handler that works with turbolinks,
$(document).on('change', '#state-picker', function(){
$.getJSON("/states/" + $(this).value() + "/cities", function(data){
// using a fragment avoids updating the DOM for every iteration.
var $frag = $('<select>');
$.each(data, function(city){
$frag.append$('<option>' + data + '</option>');
});
$('#city-picker').empty()
.append($('frag').children('option'));
});
});

Rails Simple Form - Add an error not related to attribute

I wanna do a validation that sums the values of nested fields so I make sure it's 100%. So, in my parent model, I would do the validation, and do a self.errors.add and add the error if the validation fails. The problem is, the errors.add as long as I know expects some attribute as argument, but it's not related to any attribute on my parent model, so I would like to display that message on the top of the form, for example. Any ideas of how I can do that? thank you!
UPDATE:
This is my parent model, where I wanna validate. The form has nested fields for :arrendamento_contrato_unidades.
class ArrendamentoContrato < ApplicationRecord
has_many :arrendamento_contrato_unidades, dependent: :destroy
validate :check_total_percentual_credito
def check_total_percentual_credito
if arrendamento_contrato_unidades.sum(&:percentual_credito).to_f != 100.0
self.errors.add :base, "Tem que ser 100%"
end
end
end
My create method, which it's the one I'm testing:
def create
#arrendamento_contrato = ArrendamentoContrato.new(arrendamento_contrato_params)
respond_to do |format|
if #arrendamento_contrato.save
format.html {
flash[:notice] = flash_notice
redirect_to action: "index"
}
format.json { render json: {status: 1, redirect: arrendamento_contratos_url} }
else
format.html { render :new }
format.json { render json: {status: 0, errors: #arrendamento_contrato.errors, status: :unprocessable_entity} }
end
end
end
--- Also, I debugged my object.errors.full_messages on the form, and the error is there. It's only not being displayed!
I guess that add errors to the base it's what I'm looking for. But now, it's not showing my message, but only that I have validation errors. My form code:
= simple_form_for(#arrendamento_contrato, validate: true, html: { id:"dropzoneForm", class: "dropzone dz-clickable"}) do |f|
= f.error_notification
.form-inputs
.row
.col-md-6
= f.input :numero
.col-md-6
= f.association :usina, input_html: {class: "chosen-select"}
.hr-line-dashed
.row
.col-md-6
= f.association :esco_contrato, input_html: {class: "chosen-select"}
.col-md-6
= f.association :om_contrato, input_html: {class: "chosen-select"}
.hr-line-dashed
.row
.col-md-4
= f.input :data_inicio, as: :string, input_html: {"data-mask" => "date"}
.col-md-4
= f.input :data_fim, as: :string, input_html: {"data-mask" => "date"}
.col-md-4
= f.input :valor_mensal, as: :string, input_html: {"data-mask" => "decimal"}
.hr-line-dashed
#arrendamento_contratos_unidades
- if !#arrendamento_contrato.arrendamento_contrato_unidades || #arrendamento_contrato.arrendamento_contrato_unidades.empty?
h3 = I18n.t('activerecord.models.unidade_consumidora.other')
i
'Aguardando ESCO...
- else
.row
.col-md-6
label class='control-label'
= I18n.t('activerecord.models.unidade_consumidora.other')
.col-md-6
label class='control-label'
= I18n.t('activerecord.attributes.arrendamento_contrato_unidade.percentual_credito')
.hr-line-dashed
.blockquote
= f.simple_fields_for :arrendamento_contrato_unidades do |f|
= render 'arrendamento_contrato_unidade_fields', f: f
.hr-line-dashed
i guess it should work for you
https://apidock.com/rails/ActiveRecord/Errors/add_to_base
just
errors.add_to_base("")
I think Jeff was on the right path, but I think the method you are supposed to use is model_instance.errors[:base].
I think you also might want to take into account the over all design of this feature (not that I have the full context of your app). if you have a validation of the parent model on its children model's it means that you will be saving erroneous children model to your data base only to then notify the user. Since it seems like this would be done with nested attribute you may want to consider doing this in the controller but there is an argument to be made about having too much logic in your controller.
Updating the answer for this common question and if anybody finds it useful. You can use Errors#add(:base, msg) instead
I.E.
def validate_method
errors.add(:base, 'Error message') if some_logic
...
end

How to pass parameters to autocomplete_source jquery autocomplete?

I'm using Rails 4.0.2 with jquery-rails (3.1.0) and jquery-ui-rails (4.1.1) gems. I'm added autocomplete in order to do a specific search based on what user typed and other fields at form.
The form:
<%= text_field_tag :field , some_value, data: { autocomplete_source: select_path( { :id => #order.Id , :type => #order.type } ) } %>
Form.js:
$('#field').autocomplete
minLength: 0
source: $('#field').attr('data-autocomplete-source')
select: ( event, ui ) ->
$('#pedido_venda_CodTransp').val(ui.item.value)
$('#transportadora_escolhido').val(ui.item.label)
this.form.submit()
false
...
The controller:
def select
# retrieve parameters
id_cliente = params[:id]
retira_entrega = params[:type]
term = params[:term]
# do the query, etc...
end
When I run the code, everything is OK. The controller receives all parameters and run the query flawlessly.
The parameter type, however, is based on a SELECT control and, in order to change it, I put the following code in the SELECT control.
<%= f.select :type, options_for_select( [['RETIRA','R'],['ENTREGA','E']] , #pedido.RetiraEntrega ) ,{}, { :onchange => "change_type();" } %>
JS Code function:
function change_type()
{
var e = document.getElementById("type");
var option = e.options[ e.selectedIndex ].value;
var field = document.getElementById("field");
var origem = "type=";
source = field.attributes["data-autocomplete-source"].value;
// pesquisa a string retira_entrega=
index = source.search(origem);
field.setAttribute("data-autocomplete-source", source.substring(0,index+origem.length) + String(option));
}
The JS function is called, the last line is run, the attribute is set (I put an alert at the end retrieving the attribute).
The problem is that the controller never receives the changed value (it always receives the value when the form is created).
So, the question is: how can I change a parameter passed on to autocomplete in order to use it in rails controller?
Not sure if this is what your looking for but I was struggling with the same issue because I had two input fields that I wanted different lists loaded to in the autocomplete widget. So what I did was pass an extra param to the auto complete source like this:
<!-- /_form.html.erb -->
<%= f.text_field :auto1, :size => "100", class: "form-control", data: { autocomplete_source: root_path(:fieldType => "numerouno")} %>
<%= f.text_field :auto2, :size => "100", class: "form-control", data: { autocomplete_source: root_path(:fieldType => "numerodos")} %>
Then in my controller I used that extra param to determine which list I needed to show:
if param[:fieldType] == "numerouno"
format.json { render :json => #unoList}
elsif param[:fieldType] == "numerodos"
format.json { render :json => #dosList }
else
flash[:danger] = "Error loading list for autocomplete!"
end
param[:term] still goes through too!

Rails: using partials for inputs is correct?

I'm building a Rails app. I've done all the logic (db, controllers, models, etc). Now its time to make it nice.
In order to centralize the view of the app I was thinking in creating partials for the common stuff. For example one partial called common/_text_input.html.erb that will contain
<div class="field">
<%= f.label id %><br />
<%= f.text_field id %>
</div>
This will be called from inside a form using
<%= render partial: "common/text_input", locals: { f: f, id: :name } %>
Is this approach correct? Is there any other option in rails to do this?
If this is the correct way to do this, how can I acchieve this for a form tag, for example (where content is inserted inside it)?
Thanks
1 - There is another option to do this, Helpers and content_tag:
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
Usage:
= form_for #resource do |f|
= text_input(f, :first_name)
= text_input(f, :last_name, input: { style: 'color: red;' }, label: { class: :another_class })
2 - It is correct to do with partials, but it is not as flexible as the Helpers are (see the options hash and the possibility to use another method in specific cases). To handle the form_tag (i.e. no form_builder), you can implement a new method:
# usage
= form_tag root_path, method: :get do
= text_input(nil, :search, input: { value: params[:search] }, label: { content: "Search for something!" })
# helper methods
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
return text_input_tag(attribute, options) if form_builder.blank?
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
def text_input_tag(attribute, options = {})
value = options[:input].try(:delete, :value)
label_content = options[:label].try(:delete, :content)
content_tag :div, options[:div] do
label_tag(attribute, label_content, options[:label]) + content_tag(:br) + text_field_tag(attribute, value, options[:input])
end
end

Rails problem display attribute key along with attributes value

I have the following problem. I have a form which takes input for a "Chart" object. But after processing the form, i wish to display one of the values, and it adds the key of this value.
Class model
class Chart
attr_accessor :title, :series
def initialize(title = nil, series = [])
#title, #series = title, series
end
end
View of form:
<% form_for :chart , :url => { :action => "show" } do |f| %>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>...
<% end %>
Chart controller, show method:
def show
#chart = Chart.new(params[:chart])
end
View of show:
<h2><%=h #chart.title %></h2>
Which displays: "title"input_forms_title""
for example: writing in the input form: Economy, prints in the show view: "titleEconomy"
Any ideas?
I have just figured it out. The problem was in the constructor or initialize method. By changing the initialize method to:
def initialize( options = {} )
#title = options[:title]
#series = []
end
It now accepts all params perfectly!

Resources