Passing nested parameters for instance variables - javascript

strong textI'm trying to make dynamic select boxes via "data-remote attribute for select boxes". In console it seems that i'm getting right parameters(the id of selected make), but I can't figure it out how to pass it to controller to get models with matching make_id:s.
Heres the attached_vehicles form part from _form.html.erb
<div class="vehicle_field">
<%= f.fields_for :attached_vehicles do |av| %>
<p>Select make</p>
<%= av.select :make, (#makes.collect { |m| [m.make_name, m.id] }), { include_blank: "Select make" }, { data: { remote: true, url: "update_make_models", name: "make", update: "#diy_attached_vehicles_attributes_0_model"} } %><br>
<p>Select model</p>
<%= av.collection_select :model, #models, (render "make_models/make_model"), { prompt: "Select model" } %><br>
...
<% end %>
</div>
../views/diys/update_make_models.coffee
$.empty()
.append("<%= escape_javascript(render "make_models/make_model") %>")
../diys_controller.rb
...
def update_make_models
#models = MakeModel.where("make_id = ?", params[:make])
end
def new
#diy = Diy.new
#step = #diy.steps.new
#attached_vehicle = #diy.attached_vehicles.new
#step.add_images_to_steps.new
#makes = Make.all
#models = MakeModel.where("make_id = ?", params[:make_id])
end
...
../views/make_models/_make_model.html.erb
<% #models.collect do |models| %>
<option value="<%= models.id %>"><%= models.make_model_name %></option>
<% end %>
And here's what i'm getting in console after selecting make in makes select box
Started GET "/diys/update_make_models?diy%5Battached_vehicles_attributes%5D%5B0%5D%5Bmake%5D=12" for ::1 at 2016-02-18 20:22:35 +0200 Processing by DiysController#update_make_models as JS
Parameters: {"diy"=>{"attached_vehicles_attributes"=>{"0"=>{"make"=>"12"}}}}
MakeModel Load (1.0ms) SELECT "make_models".* FROM "make_models" WHERE (make_id = NULL)
Rendered make_models/_make_model.html.erb (3.0ms)
Rendered diys/update_make_models.coffee (491.0ms)
Completed 200 OK in 628ms (Views: 626.5ms | ActiveRecord: 1.0ms | Solr: 0.0ms)
------------------------------------------------------------------------------------------------------------------------------------
Edit
NameError (undefined local variable or method `attached_vehicles_attributes' for #<DiysController:0x5757648>):
app/controllers/diys_controller.rb:28:in `update_make_models'

Your params hash is(according to logs): {"diy"=>{"attached_vehicles_attributes"=>{"0"=>{"make"=>"12"}}}}. So, if you want to get :make_id from it, you should write:
def update_make_models
#models = MakeModel.where(make_id: params["diy"]["attached_vehicles_attributes"]["0"]["make"])
end

Related

Create dynamic form with has_many (many-to-many) association with a single form in Rails 7

I have 4 models, User, Products, Property & ProductProperty. Here's how they relate:
All attributes in these models, such as name, value, upc, and available_on, must have a presence of value!
Once a user is created successfully, they can create products (with the Products model) that can have many properties (with the Property model) and a corresponding value (with the ProductProperty model). And I have to create all of these in a single form.
With the help of some tutorials and Reddit users, I was able to successfully implement the product form which is able to create a new product, property, and corresponding value. Here's the snap of my implementation:
Now here comes the tricky part: to add more properties dynamically to create multiple properties and their values of a product. So here's my implementation of it so far:
Product Form
<%= form_with model: #product do | prod | %>
<%= prod.label :name %>
<%= prod.text_field :name %>
<br>
<%= prod.label :upc, "UPC" %>
<%= prod.text_field :upc %>
<br>
<%= prod.label :available_on %>
<%= prod.date_field :available_on %>
<br>
<h3>Properties</h3>
<table>
<thead>
<tr>
<th>Property Value</th>
<th>Property Name</th>
</tr>
</thead>
<tbody class="fields">
<%= prod.fields_for :product_properties do | prod_prop | %>
<%= render "property", prod_prop: prod_prop %>
<% end %>
</tbody>
</table>
<br>
<%= link_to_add_fields('Add More Properties', prod, :product_properties) %>
<br>
<%= prod.submit %>
<% end %>
Property Form Partial
<tr>
<td><%= prod_prop.text_field :value %></td>
<%= prod_prop.fields_for :property do | prop | %>
<td><%= prop.text_field :name %></td>
<% end %>
</tr>
Helper Method
def link_to_add_fields(name, prod, association)
new_object = prod.object.send(association).klass.new
id = new_object.object_id
fields = prod.fields_for(association, new_object, child_index: :id) do | builder |
render "property", prod_prop: builder
end
link_to(name, "#", class: 'add_fields', data: {id: id, fields: fields.gsub("\n", "") } )
end
application.js file:
$("form").on("click", ".add_fields", function (event) {
let regexp, time;
time = new Date().getTime();
regexp = new RegExp($(this).data("id"), "g");
$(".fields").append($(this).data("fields").replace(regexp, time));
return event.preventDefault();
});
Now when I click on the "Add more Properties" link it adds only one field:
Issue 1: I believe I need to do some modifications in the helper method to implement this correctly but I am not able to figure out the logic of it.
There's one more issue! When we create additional property instances of property name & value, they will all disappear if there're any validation errors:
Product Controller:
def new
#product = Product.new
#product.product_properties.build.build_property
end
def create
#product = current_user.products.new(product_params)
if #product.valid?
#product.save
redirect_to products_path, notice: "Success!"
else
render 'new', status: :unprocessable_entity
end
end
I can think of a couple of solutions to this issue: first, to implement the logic in the create action for the dynamically created property name and value fields to persist between requests, second, to use javascript.
Issue 2: So how do I make the additional properties created by the user persist in the view between requests?
I am running rails v7.0.4 and I am not allowed to use hotwire, only Javascript.

Save two models (which belong_to a third model) with one submit?

When the user clicks submit how can the info from two different models/DB tables be passed?
The user should be able to create a note in the missed_dates form and then that note should be saved to the respective #challenge the missed date is referring to.
missed_dates/form.html.erb
<%= simple_form_for(#missed_date, url: challenge_missed_dates_path({ routine_id: #challenge }), remote: request.xhr?, html: { data: { modal: true } }) do |a| %>
<%= form_for [#notable, #note] do |b| %>
<%= a.text_field :one %>
<%= b.text_field :two %>
<%= button_tag(type: 'submit') do %>
Save
<% end %>
<% end %>
<% end %>
missed_date.rb
class MissedDate < ActiveRecord::Base
belongs_to :user
belongs_to :challenge
end
missed_date_controller
def new
#challenge = current_user.challenges.find(params[:challenge_id])
#missed_date = current_user.missed_dates.build
#notable = #challenge
#note = Note.new
end
def create
challenge = current_user.challenges.find(params[:challenge_id])
challenge.missed_days = challenge.missed_days + 1
challenge.save
#missed_date = challenge.missed_dates.build(missed_date_params)
#missed_date.user = self.current_user
#missed_date.save
respond_modal_with #missed_date, location: root_path
flash[:alert] = 'Strike added'
end
Short: use "belongs_to" and "has_many :through" association between Note and MissedDates. Then you can use nested attributes.
Long version: This in probably an issue of an improper or incomplete structure of your models. Usually, you can use nested attributes (see http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html) to achieve this.
But, this implies that the models have a direct relation. You should consider if you can do a belongs_to/has_many relation between the note and the missed_date model. This could be done e.g. by "has_many :through..." (http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association) without changing your current db scheme.

Adding Forms Dynamically From Dropdown Menu Selection

I have a Ruby on Rails question about adding a dynamic form through a drop down selection rather than having individual buttons for each possible selection.
Right now, through the help of following some Railscasts, my application works where I have three individual buttons that are able to dynamically add three different types of nested forms to the parent, all corresponding to different models with different form partials.
The parent model here is a Workout, allowing traditional_blocks, emon_blocks, and tempo_blocks to be added dynamically using JS.
Workout Model
class Workout < ActiveRecord::Base
has_many :tempos
has_many :traditionals
has_many :emons
accepts_nested_attributes_for :tempos, allow_destroy: true
accepts_nested_attributes_for :emons, allow_destroy: true
accepts_nested_attributes_for :traditionals, allow_destroy: true
end
/app/views/workouts/new.html.erb
<div>
<%= button_to_add_fields "Add EMON Block", f, :emons %>
<%= button_to_add_fields "Add traditional Block", f, :traditionals %>
<%= button_to_add_fields "Add tempo Block", f, :tempos %>
</div>
/apps/helpers/application_helper.rb
module ApplicationHelper
def button_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
button_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
app/assets/javascripts/workouts.js.coffee
$(document).on 'click', 'form .add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
Like I said earlier, everything works as I want it to when I click the buttons. However, I would like to be able to put "emon block, traditional block, tempo block" inside a collection_select with one button next to the collection_select that says "create." When that "create" button is clicked, I would like it to call that same helper(button_to_add_fields) passing along the necessary parameters for it to work the same way it does now with the multiple button implementation but using the currently selected association in the collection select.
Any tips?
You'll want to use Ajax for this
The Railscast you viewed, although helpful, is somewhat limited in the way that it will only allow you to add a single nested form each time
--
Ajax
#config/routes.rb
resources :workouts do
get "ajax_fields/:type", on: :collection
end
#app/models/workout.rb
Class Workout < ActiveRecord::Base
...
def self.build type
workout = self.new
if type
workout.send(type).build
else
workout.tempos.build
workout.traditionals.build
workout.emons.build
end
end
end
#app/controllers/workouts_controller.rb
Class WorkoutsController < ApplicationController
def ajax_update
#workout = Workout.build params[:type]
render "form", layout: !request.xhr?
end
end
#app/views/workouts/_form.html.erb
<%= form_for #workout do |f| %>
<%= render "fields", locals: { f: f }, onject: params[:type] %>
<%= f.submit %>
<% end %>
#app/views/workouts/_fields.html.erb
<%= f.fields_for type.to_sym, child_index: Time.now.to_i do |t| %>
<%= t.text_field :your_field %>
<% end %>
#app/views/workouts/new.html.erb
<%= form_for #workout do |f| %>
<%= render: "fields", locals: { f: f}, collection: ["tempos", "traditionals", "emons"], as: :type %>
<%= ... dropdown code ...%>
<%= f.submit %>
<% end %>
--
This will allow you to send the following ajax request:
#app/assets/javascripts/workouts.js.coffee
$(document).on 'change', 'form .add_fields', (event) ->
type = $(this).val
$.ajax
url: "/workouts/ajax_fields/" + type,
success: function(data) {
$("form").append(data); // will have to work this out properly
}
});
This should give you the ability to append the extra fields you need to the application, which will then come back with the appropriate HTML for you to append to your DOM
Hopefully you can appreciate the sentiment here - it might not work right out of the box!

Creating Rails 4 Form with Join Table Metadata

Very new Rails 4 developer here. I've got a form where a user is creating Exercises. Exercises can have many Equipment, and Equipment can be optional( think push-up stands for doing push-ups ). I store this "optional" field on the join table exercise_equipment.
I cannot get the parameters to actually send through the values of the collection element that I pick. See below for the model, view, controller, and parameters.
Here are the attributes/relationships of my models:
# id :integer
# name :string
# is_public :boolean
Exercise
has_many :exercise_equipment
has_many :equipment, :through => :exercise_equipment
accepts_nested_attributes_for :exercise_equipment
# id :integer
# exercise_id :integer
# equipment_id :integer
# optional :boolean
ExerciseEquipment
belongs_to :exercise
belongs_to :equipment
accepts_nested_attributes_for :equipment
# id :integer
# name :string
Equipment
has_many :exercise_equipment
has_many :exercises, :through => :exercise_equipment
Here are some (maybe) relevant controller methods:
def new
#exercise = Exercise.new
#exercise.exercise_equipment.build
end
def create
#exercise = Exercise.new( exercise_params )
if #exercise.save
redirect_to #exercises
else
render 'new'
end
end
def edit
#exercise = Exercise.find( params[:id] )
end
def update
#exercise = Exercise.find( params[:id] )
if #exercise.update_attributes( exercise_params )
redirect_to #exercises
else
render 'edit'
end
end
def exercise_params
params.require( :exercise ).permit(
:name,
:is_public,
exercise_equipment_attributes: [
:id,
:optional,
equipment_attributes: [
:id,
:name
],
]
)
end
This is my shot at creating a view to do what I want:
exercises/new.html.erb
<%= form_for #exercise do |f| %>
<%= render 'form', f: f %>
<%= f.submit "New Exercise" %>
<% end %>
exercises/_form.html.erb
<%= f.label :name %><br />
<%= f.text_field :name %>
<%= f.check_box :is_public %> Public
<%= f.fields_for( :exercise_equipment ) do |eef|
<%= eef.fields_for( :equipment ) do |ef|
ef.collection_select :id, Equipment.all, :id, :name %>
<% end %>
<%= eef.check_box :is_optional %> Optional
<% end %>
When I put all of this together and submit an update to an already-existing exercise, the values all go through the params hash, but aren't changed to the new values I've selected...
Parameters: {
"utf8"=>"[checkbox]",
"authenticity_token"=>"[token]",
"exercise"=>{
"name"=>"Test",
"is_public"=>"1",
"exercise_equipment_attributes"=>{
"0"=>{
"equipment_attributes"=>{
"id"=>"1"
},
"optional"=>"1",
"id"=>"2"
}
}
},
"commit"=>"Save Exercise",
"id"=>"1"
}
If you can help me out, I'd be super appreciative. Just let me know if you need any more information and I can provide it.
EDIT
Here is the state of the database before updating:
postgres#=>db=# select id, name, is_public from exercises;
id | name | is_public
----+------+-----------
2 | Test | t
(1 row)
Time: 61.279 ms
postgres#=>db=# select id, exercise_id, equipment_id, optional from exercise_equipment;
id | exercise_id | equipment_id | optional
----+-------------+--------------+----------
2 | 2 | 1 | t
(1 row)
Time: 58.819 ms
postgres#=>db=# select id, name from equipment where id = 1;
id | name
----+-------------
1 | Freeweights
(1 row)
I then go to the update route for that exercise, select a different equipment from the collection, and submit the form. I get the following Rails Console results:
Started PATCH "/exercises/system-test" for 127.0.0.1 at 2014-08-12 23:48:18 -0400
Processing by ExercisesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"PsbbUPSCiIew2Fd22Swn+K4PmLjwNDCrDdwXf9YBcm8=", "exercise"=>{"name"=>"Test", "is_public"=>"1", "exercise_equipment_attributes"=>{"0"=>{"equipment_attributes"=>{"id"=>"1"}, "optional"=>"1", "id"=>"2"}}}, "commit"=>"Save Exercise", "id"=>"system-test"}
Exercise Load (60.5ms) SELECT "exercises".* FROM "exercises" WHERE "exercises"."slug" = 'system-test' ORDER BY "exercises"."id" ASC LIMIT 1
(57.3ms) BEGIN
ExerciseEquipment Load (76.2ms) SELECT "exercise_equipment".* FROM "exercise_equipment" WHERE "exercise_equipment"."exercise_id" = $1 AND "exercise_equipment"."id" IN (2) [["exercise_id", 2]]
Equipment Load (59.1ms) SELECT "equipment".* FROM "equipment" WHERE "equipment"."id" = $1 LIMIT 1 [["id", 1]]
User Load (60.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 10]]
Exercise Exists (60.5ms) SELECT 1 AS one FROM "exercises" WHERE ("exercises"."name" = 'Test' AND "exercises"."id" != 2 AND "exercises"."user_id" = 10) LIMIT 1
(64.8ms) COMMIT
Redirected to http://localhost:3000/exercises/system-test
Completed 302 Found in 590ms (ActiveRecord: 580.0ms)
Started GET "/exercises/system-test" for 127.0.0.1 at 2014-08-12 23:48:19 -0400
Processing by ExercisesController#show as HTML
Parameters: {"id"=>"system-test"}
Exercise Load (64.1ms) SELECT "exercises".* FROM "exercises" WHERE "exercises"."slug" = 'system-test' ORDER BY "exercises"."id" ASC LIMIT 1
Equipment Load (58.7ms) SELECT "equipment".* FROM "equipment" INNER JOIN "exercise_equipment" ON "equipment"."id" = "exercise_equipment"."equipment_id" WHERE "exercise_equipment"."exercise_id" = $1 [["exercise_id", 2]]
Rendered exercises/show.html.erb within layouts/application (122.7ms)
User Load (60.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 10 ORDER BY "users"."id" ASC LIMIT 1
Rendered shared/_header.html.erb (61.9ms)
Rendered shared/_alerts.html.erb (0.1ms)
Completed 200 OK in 264ms (Views: 21.3ms | ActiveRecord: 240.8ms)
Firstly, you need to make sure you define your associations correctly.
Any has_many association should be defined with a plural name -
#app/models/exercise.rb
Class Exercise < ActiveRecord::Base
has_many :exercise_equipments
has_many :equipments, :through => :exercise_equipments
accepts_nested_attributes_for :exercise_equipments
end
#app/models/exercise_equipment.rb
Class ExerciseEquipment < ActiveRecord::Base
belongs_to :exercise
belongs_to :equipment
end
#app/models/equipment.rb
Class Equipment < ActiveRecord::Base
has_many :exercise_equipments
has_many :exercises, through: :exercise_equipments
end
If you've already got it working, and are happy with what you've got, then I'd recommend keeping your current setup. However, you may wish to adopt the above for convention's sake
Edit I see from the deleted answer that Beartech investigated this, and turns out Rails treats Equipment / Equipments as the same. Will be worth ignoring the above, but I'll leave it for future reference
Params
I cannot get the parameters to actually send through the values of the
collection element that I pick. See below for the model, view,
controller, and parameters.
I think I get what you mean - you're looking to update the record, but it does not send through the updated parameters to your controller, hence preventing it from being updated.
Although I can't see any glaring problems, I would recommend the issue is that you're trying to populate the exercise_id of an Exercise object. You need to define it for the exercise_equipment object:
<%= f.fields_for :exercise_equipment do |eef| %>
<%= eef.collection_select :equipment_id, Equipment.all, :id, :name %>
<%= eef.check_box :is_optional %>
<% end %>
This will populate your exercise_equipment table as described here:
Time: 61.279 ms
postgres#=>db=# select id, exercise_id, equipment_id, optional from exercise_equipment;
id | exercise_id | equipment_id | optional
----+-------------+--------------+----------
2 | 2 | 1 | t
(1 row)
Currently, you're populating the Equipment model with equipment_id - which won't work. Populating the model in that way will server to create a new record, not update the ones already created
Extra Field
I want to have a link to add an additional equipment field when it is
clicked, similar to how Ryan Bates did it in this RailsCast, but the
helper method he writes( see "Show Notes" tab if you're not subscribed
to see the source ) seems to become substantially more complex when
dealing with the nested views shown in my code below. Any help in
dealing with this?
This a trickier mountain to overcome
Ryan uses quite an outdated method in this process (to pre-populate the link and then just let JS append the field). The "right" way is to build a new object & append the fields_for from ajax. Sounds tough? That's because it is :)
Here's how you do it:
#config/routes.rb
resources :exercises do
collection do
get :ajax_update #-> domain.com/exercises/ajax_update
end
end
#app/models/exercise.rb
Class Exercise < ActiveRecord::Base
def self.build
exercise = self.new
exercise.exercise_equipment.build
end
end
#app/controllers/exercises_controller.rb
Class ExercisesController < ApplicationController
def new
#exercise = Exercise.build
end
def ajax_update
#exercise = Exercise.build
render "add_exercise", layout: false #> renders form with fields_for
end
end
#app/views/exercises/add_exercise.html.erb
<%= form_for #exercise do |f| %>
<%= render partial: "fields_for", locals: { form: f } %>
<% end %>
#app/views/exercises/_fields_for.html.erb
<%= f.fields_for :exercise_equipment, child_index: Time.now.to_i do |eef| %>
<%= eef.collection_select :equipment_id, Equipment.all, :id, :name %>
<%= eef.check_box :is_optional %>
<% end %>
#app/views/exercises/edit.html.erb
<%= form_for #exercise do |f| %>
<%= render partial: "fields_for", locals: { form: f } %>
<%= link_to "Add Field", "#", id: "add_field" %>
<% end %>
#app/assets/javascripts/application.js
$(document).on("click", "#add_field", function() {
$.ajax({
url: "exercises/ajax_update",
success: function(data) {
el_to_add = $(data).html()
$('#your_id').append(el_to_add)
}
});
});

js.erb file is not been rendered

I'm trying to do an Ajax call on Ruby on Rails. When I select 2 collection_select's the third has to be rendered by Ajax. I'm new to Ruby on Rails, so if anybody could help me, I'd be thankful.
_form.html.erb:
<%= form_for(#preco_servico) do |f| %>
<div class="field">
<%= f.label :produto %>
<%= collection_select(:preco_servico, :produto, Produto.order('tipo'), :tipo, :tipo) %>
</div>
<div class="field" >
<%= f.label "Análise" %>
<%= collection_select(:preco_servico, :analise, TipoAnalise.order('tipo'), :tipo, :tipo) %>
</div>
<div class="field" id="parametro-select", :remote >
</div>
...
<% end %>
application.js:
jQuery(function($) {
$("#preco_servico_analise").change(function() {
var produto_id = $('select#preco_servico_produto :selected').val();
var analise_id = $('select#preco_servico_analise :selected').val();
$.get('/preco_servicos/update_parametro/' + produto_id + '/' + analise_id, function(data){
$("#parametro-select").html(data);
})
return false;
});
})
The method on controller:
def update_parametro
#precos = PrecoServico.where(:analise => params[:analise], :produto => params[:produto])
#sub_parametros = []
#precos.each do |preco|
#sub_parametros = Parametro.where("nome NOT IN (?)", preco.parametro)
end
respond_to do |format|
format.js
end
end
I've already included the code below on application.html.erb:
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
And the file update_parametro.js.erb:
sub_parametro <%= collection_select(:preco_servico, :parametro, #sub_parametros, :nome, :nome) %>
On console I get this:
Started GET "/preco_servicos/update_parametro/produto2/analise2"
for 127.0.0.1 at 2014-01-28 23:25:21 -0200 Processing by
PrecoServicosController#update_parametro as / Parameters:
{"preco_servicos"=>"preco_servicos",
"update_parametro"=>"update_parametro", "produto"=>"produto2",
"analise"=>"análise2"} PrecoServico Load (0.2ms)
SELECT preco_servicos.* FROM preco_servicos WHERE
preco_servicos.analise = 'análise2' AND preco_servicos.produto
= 'produto2' Rendered preco_servicos/update_parametro.js.erb (0.5ms) Completed 200 OK in 8ms (Views: 6.0ms | ActiveRecord: 0.2ms)
It tells me it is rendered, but nothing changes on screen and nothing appears in firebug.

Categories

Resources