Multiple Select Tag in Rails - ruby-on-rails

Im trying to implement a multiple level drop down list in Rails
I have three Tables in my DB.
vehicle_make.rb
class VehicleMake < ActiveRecord::Base
validates_uniqueness_of :make
has_many :appointments
end
vehicle_model.rb
class VehicleModel < ActiveRecord::Base
validates_uniqueness_of :model
has_many :appointments
end
vehicle_make_model.rb
class VehicleMakeModel < ActiveRecord::Base
validates_uniqueness_of :vehicle_make_id, :scope => :vehicle_model_id
end
and im trying to implement a multiple dropdown list in appointments.html.rb
on selecting the vehicle model only corresponding make should load..
<%= f.select :vehicle_make_id, options_for_select(vehicle_make.map {|s| [s.make, s.id]}, appointment.vehicle_make_id), {}, {class: "form-control"} %>
and in my js i have..
$('#appointment_vehicle_make_id').on('change', function() {
var vehicle_make_id = this.value;
$.ajax({
url : '/appointments/update_models',
type : 'GET',
data : {
make_id : vehicle_make_id
},
success : function(response) {
console.log(response);
}
});
});
and this is my controller method.
def update_models
#vehicle_models = VehicleModel.all
#model_ids = []
#selected_vehicle_models = VehicleMakeModel.where(vehicle_make_id: params[:make_id]).order(:vehicle_model_id) unless params[:make_id].blank?
#selected_vehicle_models.each do |t|
#model_ids << t.vehicle_model_id
end
respond_to do |format|
format.html { render layout: false }
format.js
end
end
I have html page named update_models.html.erb associated to the above action.
<%= select_tag :vehicle_model_id, options_for_select(#model_ids.map {|s| [s.model, s.first.id]}, nil), {}, {class: "form-control"} %>
I get an error in terminal saying
ActionView::Template::Error (wrong number of arguments (4 for 1..3)):
1: <%= select_tag :vehicle_model_id, options_for_select(#model_ids.map {|s| [s.model, s.first.id]}, nil), {}, {class: "form-control"} %>
Im stuck here. I dont know how to proceed from here.. please help

In your controller action update_models, you are trying to render js, so it's trying to find template named as update_models.js.erb.
You can try replacing your respond_to block with:
respond_to do |format|
format.json { render :json => #model_ids }
end
Afterwards, you will need to parse this data in your ajax success callback

Related

Create multiple records with one form submit

I have 3 models: User, Ingredient, and a map of which user has which ingredients - UserIngredient.
My current setup works for adding 1 ingredient at a time. What I want is to update the code so that users can enter a few ingredients and just click "submit" once rather than clicking it for each ingredient individually. I've looked into nested_resources but it seems like not the right place to use it.
what is the right way of doing this?
thank you
app/models/user.rb
class User < ApplicationRecord
...
has_many :user_ingredients, dependent: :destroy
has_many :ingredients, through: :user_ingredients
...
end
app/models/ingredient.rb
class Ingredient < ApplicationRecord
...
has_many :user_ingredients, dependent: :destroy
has_many :owners, through: :user_ingredients
...
end
app/models/user_ingredient.rb
class UserIngredient < ApplicationRecord
belongs_to :user
belongs_to :ingredient
validates :user, presence: true
validates :ingredient, presence: true
end
app/views/user_ingredients/new.html.erb
<div>
<%= turbo_frame_tag #user_ingredient do %>
<%= render "form", user_ingredient: #user_ingredient %>
<% end %>
</div>
app/views/user_ingredients/_form.html.erb
<div class="w-full mx-auto">
<%= form_for #user_ingredient do |f| %>
<div class="flex-row gap--md">
<%= f.select(
:ingredient_id,
options_from_collection_for_select(Ingredient.where(id: f.object.ingredient_id), :id, :name, :selected => f.object.ingredient_id),
{ prompt: 'Start typing to search' },
{ id: "drDwn_ingredient",
class: "w-full border border-black",
required: true,
data: {
controller: "selectIngredient",
selectIngredient_url_value: autocomplete_ingredients_path,
},
}) %>
<div class="flex-row gap--xxxs">
<label>
<input type="submit" class="add_cancel_ing gap--md" />
<%= inline_svg_tag "svg/circle-check.svg", class: "svg_add_ing" %>
</label>
<%= link_to user_ingredients_path do %>
<%= inline_svg_tag "svg/circle-xmark.svg", class: 'svg_cancel_ing' %>
<% end %>
</div>
</div>
<% end %>
</div>
app/controllers/user_ingredients_controller.rb
class UserIngredientsController < ApplicationController
before_action :authenticate_user!
before_action :set_user_ingredient, only: [:show, :destroy]
def index
#user_ingredients = current_user.user_ingredients
end
def new
#user_ingredient = UserIngredient.new
end
def create
#user_ingredient = UserIngredient.new(user_ingredient_params.merge(user: current_user))
if #user_ingredient.save
respond_to do |format|
format.html { redirect_to user_ingredients_path, notice: 'Ingredient was successfully added to your bar!' }
format.turbo_stream { flash.now[:notice] = 'Ingredient was successfully added to your bar!' }
end
else
render :new
end
end
def destroy
#user_ingredient.destroy
respond_to do |format|
format.html { redirect_to user_ingredients_path, notice: "Ingredient was removed!" }
format.turbo_stream { flash.now[:notice] = "Ingredient was removed!" }
end
end
private
...
def set_user_ingredient
#user_ingredient = current_user.user_ingredients.find(params[:id])
end
def user_ingredient_params
params.require(:user_ingredient).permit(:id, :ingredient_id)
end
end
app/javascript/controllers/selectIngredient_controller.js
import { Controller } from "#hotwired/stimulus";
import { get } from "#rails/request.js";
import TomSelect from "tom-select";
export default class extends Controller {
static values = { url: String };
multi_select_config = function () {
return {
plugins: ["remove_button", "no_active_items"],
valueField: "value",
load: (q, callback) => this.search(q, callback),
closeAfterSelect: true,
persist: false,
create: false,
};
};
async search(q, callback) {
const response = await get(this.urlValue, {
query: { q: q },
responseKind: "json",
});
if (response.ok) {
const list = await response.json;
callback(list);
} else {
console.log("Error in select_ctrl: ");
console.log(response);
callback();
}
}
connect() {
new TomSelect(this.element, this.multi_select_config());
}
}
You should use accepts_nested_attributes_for method for User
And try to create related records via User.
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Or you can try to make custom action for accepting custom form with multiple records at once. But first option will be more predictable and easier for supporting.
For views you can use cocoon gem. It's pretty old, but it still works good.
Or you can inspire by it and make your custom solution)
https://github.com/nathanvda/cocoon

Rails best_in_place Unprocessable Entity

I'm trying to edit the name field of a model with the best_in_place gem to edit items directly in line. When trying to do so I'm getting a an error 422 Unprocessable Entity.
I've done some research and found out in the response the controller is expecting not only the name attribute but also the group and some other attributes.
["Group must exist","Lumo can't be blank","Lumo is not a number"]
I have setup my controller in the correct way (I think).
materials_controller.rb
def update
#material = Material.find(params[:id])
if params[:commit] == "Save" then
success = #material.update(material_params)
params[:create_pure_composition] = false
else
#material = Material.new(material_params)
#material.user_id = current_user.id
success = #material.save
end
respond_to do |format|
if success
plot1 = prepare_e_plot(#material)
plot2 = prepare_st_plot(#material)
format.html { redirect_to #material }
format.json { render json: { plot1: plot1, plot2: plot2, status: 200 } }
else
format.html { render 'edit' }
format.json { respond_with_bip(#material) }
end
end
end
Is there a way with best_in_place to send these value's when updating the name attribute? I've tried with the params attribute from best_in_place.
<%= best_in_place #material, :name, as: :input, params: { group_id: #material.group_id } %>
This wasn't sending any extra params with the update.
Here's is what the Material model looks at.
material.rb
class Material < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :name, presence: true, uniqueness: {scope: :user_id}
validates :lumo, presence: true, numericality: true
end
Does anybody know why it's asking for other attributes and why best_in_place is not sending those along?
I figured out what the problem was and how to fix it. We use an option to Save or Save As when editing materials. In the controller we therefore check for the params[:commit].
By editing the url I was able to send in the params[:commit] with the update with best_in_place. Here is how the best_in_place code ended up like:
<%= best_in_place #material, :name, as: :input, url: material_path(#material, commit: "Save") %>

Using AJAX with two select boxes in Rails 4

I have a view containing two select boxes: company and employee. Both have a blank option and when a company is selected, it populates the employees based on the selected company. This works just fine. My issue is that when I submit a form that fails validation (as expected) and I select a company once more once the 'new' view renders again in extensions#create, my 'get' AJAX call has changed from /servers/1/extensions/get_company_employees (correct) to /servers/1/get_company_employees (incorrect) and is returning 404 Not found. Why is this happening and what should I do to remedy this? All relevant code is listed below
routes.config
resources :servers do
scope module: 'servers' do
resources :extensions, shallow: true
end
end
# Ajax call
get 'servers/:id/extensions/get_company_employees', to: 'servers/extensions#get_company_employees', as: 'get_company_employees'
app/controllers/servers/extensions_controller.rb
class Servers::ExtensionsController < ApplicationController
def get_company_employees
#server = Server.find(params[:id])
#extension = #server.extensions.build
#path = [#server, #extension]
#companies = Company.all
#employees = Employee.where("company_id = ?", params[:company_id])
respond_to do |format|
format.js
end
end
def new
#server = Server.find(params[:server_id])
#extension = #server.extensions.build
#path = [#server, #extension]
#companies = Company.all
#employees = Employee.none
end
def create
#server = Server.find(params[:server_id])
#extension = #server.extensions.build(extension_params)
#extension.password = "pass"
if #extension.save
flash[:success] = "Successfully created extension"
redirect_to #extension
else
#path = [#server, #extension]
#companies = Company.all
#employees = Employee.none
flash.now[:error] = "Failed to create extension"
render "new"
end
end
private
def extension_params
params.require(:extension).permit(:value, :password, :employee_id, :server_id, :phone_id)
end
end
app/views/servers/extensions/_form.html.erb
<%= form_for(#path) do |f| %>
<p>
<%= label_tag(:company) %>
<%= select_tag "company", options_from_collection_for_select(#companies, "id", "name"), include_blank: "Select a company" %>
</p>
<p>
<%= f.label(:employee) %>
<%= f.collection_select :employee_id, #employees, :id, :full_name, include_blank: "Select an employee" %>
</p>
<p>
<%= f.submit "Submit" %>
</p>
<% end %>
app/views/servers/extensions/get_company_employees.js.coffee
$("#extension_employee_id").empty()
.append("<option>Select an employee</option>")
.append("<%= j options_from_collection_for_select(#employees, :id, :full_name) %>")
app/assets/javascripts/servers/extensions.coffee
$ ->
$(document).on 'page:load', '#company', (evt) ->
$.ajax 'get_company_employees',
type: 'GET'
dataType: 'script'
data: {
company_id: $("#company option:selected").val()
}
$(document).on 'change', '#company', (evt) ->
$.ajax 'get_company_employees',
type: 'GET'
dataType: 'script'
data: {
company_id: $("#company option:selected").val()
}
Its because you have now specified complete URL in ajax call
It should be something like this in both cases.
$.ajax "/servers/"+ id +"/extensions/get_company_employees',
type: 'GET'
dataType: 'script'
data: {
company_id: $("#company option:selected").val()
}
// store and fetch id attribute from page in any of the dom element
Ideally you should write a function for your ajax call which can be called wherever required and code redundancy can be reduced.

How to handle strong parameters as a array in Nested Form? Rails 4

The context is as follows, I have entities that can have multiple roles. These roles are manageable by the user.
For example, Entity named "Lipsum" may be "Cashier and Salesperson". So, this is a relation many_to_many.
So I have my 3 models: Entity, type_entity and entity_by_type
class Entity < ActiveRecord::Base
has_many :entity_by_types
has_many :type_entities, :through => :entity_by_types
accepts_nested_attributes_for :entity_by_types
end
class EntityByType < ActiveRecord::Base
belongs_to :entity
belongs_to :type_entity
end
class TypeEntity < ActiveRecord::Base
has_many :entity_by_types
has_many :entities, :through => :entity_by_types
end
I have an ordinary CRUD for entity types.
Now, in the CRUD of entities, I have a field Select-Option Multiple. In which the user chooses has 1 or more types, the entity that is creating.
Then my Controller Entity is as follows:
class Logistics::EntitiesController < ApplicationController
def index
#type_entities = TypeEntity.all
render layout: false
# I use this for show All entities by TypeEntity in my view index
end
def show
end
def new
#type_entities = TypeEntity.all
#entity = Entity.new
render layout: false
end
def create
entity = Entity.new(entity_parameters)
if entity.save
flash[:notice] = "Succesfull!."
redirect_to :action => :index
else
flash[:error] = "Error."
redirect_to :action => :index
end
end
def edit
#entity = Entity.find(params[:id])
#type_entities = TypeEntity.all
#action = 'edit'
render layout: false
end
def update
entity = Entity.find(params[:id])
entity.update_attributes(entity_parameters)
flash[:notice] = "Succesfull."
redirect_to :action => :index
end
def destroy
#entity = Entity.destroy(params[:id])
render :json => #entity
end
private
def entity_parameters
params.require(:entity).permit(:name, :surname, entity_by_types_attributes: [:id, :entity_id, :type_entity_id])
end
end
And my partial form (for method create and Update) is:
= simple_form_for([:namespace, #entity], html: {class: 'form-horizontal' }) do |f|
= f.input :name, placeholder: "Nombre", input_html: { class: 'form-control' }, label: false
= f.input :surname, placeholder: "Apellidos", input_html: { class: 'form-control' }, label: false
%select.select2#type-entity-select{:name => "entity[entity_by_types_attributes][type_entity_id][]", :style => "width:100%;padding: 0;border: none;", :multiple => true}
- #type_entities.each do |tent|
%option{value: "#{tent.id}"}
= tent.name
But, when I click in button submit, and "type_entity_id" have 1 or more values; in my database only display a 1 record where, entity_id is OK, however type_entity_id is NULL.
Moreover only view a 1 record, when should see 1 or more records, depending on the number of types of choice in the form.
The problem here is the way of pass type_entity_id in form of array. So, How I can do that?
P.D
The following is how the params go to my controller:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ASD"1231+Dssr6mRJcXKh9xHDvuVDmVl4jnwIilRBsuE=", "entity"=>{"name"=>"Lorem", "surname"=>"Ipsum", "entity_by_types_attributes"=>{"type_entity_id"=>["1", "4"]}}}
Try this:
def entity_parameters
params.require(:entity).permit(:name, :surname, entity_by_types_attributes: [:id, :entity_id, {:type_entity_id => []}])
end
Edit:
In your form and in def entity_parameters replace type_entity_id with type_entity_ids
Thus, the parameter will refer to a set (array) not to a single object. These are the generic method syntaxes:
Model.associate_id = some integer
Model.associate_ids = an array (for a has_many relation)

Filter field based on previous field selection

How can I use simple_form to filter a field, based on a previous fields value?
For instance, I have an Opportunities form, with two fields, Company and Contact.
Company Field:
<div class="form-group">
<%= f.association :company, collection: Company.all.order(:account), prompt: "", :label_method => :account, :value_method => :id %>
</div>
Contact Field:
<div class="form-group">
<%= f.association :contact, collection: Contact.all.order(:first_name), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
Here is what I want to do: If I select a company called "Deviant" from the Company field above, I want the Contact field to only display those contacts associated with the company called "Deviant".
I am trying something like this, but can't get it to work:
<div class="form-group">
<%= f.association :contact, collection: Contact.where("company_id = ?", params[:id]), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
I don't know how to reference the value in the Company field.
How can I do this?
Thanks.
Update
Anyone? Surely this must be possible. This is a key functionality in any form. I would hope I don't need jQuery or something.
I think the best approach is to use ajax requests to update your contacts collection dinamically whenever the company's selected value is changed.
First you'll need an action in your contacts controller:
app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def contacts_list
if params[:company_id]
#contacts = Contact.where(company_id: params[:company_id])
else
#contacts = Contact.all
end
respond_with(#contacts) do |format|
format.json { render :json => #contacts.to_json(:only => [:id, :first_name, :last_name]) }
end
end
end
Add this to your routes:
config/routes.rb
post 'contacts_list' => "contacts#contacts_list", as: :contacts_list
Then use the coffeescript code bellow to populate your contacts' collection:
app/assets/javasctipts/companies.js.coffee
$(document).ready ->
if $("#opportunity_company_id")
populate_contacts()
$("#opportunity_company_id").change ->
populate_contacts()
populate_contacts = ->
$contacts_select = $("select#opportunity_contact_id")
$contacts_select.attr "disabled", "disabled"
company_id = $("select#opportunity_company_id").val()
if company_id is ""
$contacts_select.html "<option value=\"\">(select the company first)</option>"
else
$contacts_select.html "<option value=\"\">(loading contacts...)</option>"
data = {company_id: company_id}
data[window._auth_token_name] = window._auth_token
$.ajax "/contacts_list",
type: "post"
dataType: "json"
data: data
success: (contacts) ->
_html = '<option value="">Select the contact:</option>'
_html += '<option value="'+contact.id+'">'+contact.first_name + ' ' + contact.last_name + '</option>' for contact in contacts
$contacts_select.html _html
$contacts_select.removeAttr "disabled"
error: ->
alert 'Error trying to load contacts.'
Finally, inside your html's head tag:
<% if protect_against_forgery? %>
<script>
window._auth_token_name = "<%= request_forgery_protection_token %>";
window._auth_token = "<%= form_authenticity_token %>";
</script>
<% end %>
Hope it helps...
update:
Add the following line to your ApplicationController (app/controllers/application_controller.rb):
respond_to :html, :xml, :json, :js

Resources