jquery token input plugin not passing the id from the select list - ruby-on-rails

I am working on a simple rails plugin where a user can select a form containing the list of school available in the database. My problem is that the list of schools is being fetched but if I select the rest of the schools in the array I get the first item's id. What could I be doing wrong? My code sample is as follows
my user model has
belongs_to :school
attr_reader :school_tokens
def school_tokens=(id)
self.school_id = id.split(",")
end
then in my school model I have
has_many :users
in my form I have
<%= f.label :school, "School Name" %>
<%= f.text_field :school_tokens %>
my application.js looks like
$(function(){
$('#user_school_tokens').tokenInput("/school_streets.json", {
crossDomain: false,
tokenLimit: 1
});
finally my schools controller is like this
def index
#school_streets = SchoolStreet.where("name LIKE ?", "%#{params[:q]}%")
respond_to do |format|
format.html # index.html.erb
format.json { render json: #school_streets.map(&:attributes) }
end
end
Note: the schools display very well, but when I select a school, for instance the second school in the list, and submit the form it saves with only the first school's id in the list.
Thanks for your help.

The line tokenLimit: 1 means you can only select one 'school' at a time. Remove this to submit multiple.

Related

Params hash from a SimpleForm with multiple records has a different structure for one record than for two

I struggled to digest this into a title.
I'm using SimpleForm to construct a bulk-edit page with one or more fieldsets - one for each record in a collection of ActiveRecord models that have been built but not yet saved.
My form looks like this:
= simple_form_for :courses, method: :patch do |f|
- #courses.each do |course|
= field_set_tag do
= f.simple_fields_for 'courses[]', course do |c|
= c.input :title
.row
.medium-6.columns
= c.input :start_date, as: :string, input_html: { class: 'input-datepicker' }
.medium-6.columns
= c.input :end_date, as: :string, input_html: { class: 'input-datepicker' }
= f.submit 'Save', class: 'primary button'
The params hash for one record looks like this:
"courses"=>{"courses"=>[{"title"=>"Course Y", "start_date"=>"2017-09-26", "end_date"=>"2017-07-31"}]}
with an array, while for two records it looks like this:
"courses"=>{"courses"=>{"1"=>{"title"=>"Course X", "start_date"=>"2018-01-16", "end_date"=>"2018-07-30"}, "2"=>{"title"=>"Course Y", "start_date"=>"2017-09-26", "end_date"=>"2018-07-30"}}}
with a stringy-integer-keyed hash.
This becomes a problem when I try and use strong parameters. After much hacking, I ended up with this piece of code, which works for multiple records but fails when only one is submitted:
ActionController::Parameters
.new(courses: params[:courses][:courses].values)
.permit(courses: [:title, :start_date, :end_date])
.require(:courses)
It fails with param is missing or the value is empty: courses highlighting the .require(:courses) line above.
The problem is "solved" by harmonising the single-record case with the multiple-record case:
if params[:courses][:courses].is_a?(Array)
params[:courses][:courses] = { '1': params[:courses][:courses][0] }
end
but it feels like there should be a simpler way of doing it.
Is there a better way to write the form for this use-case? Am I missing a trick with strong parameters?
I'm using rails 5.0.5 and simple_form 3.5.0.
"but it feels like there should be a simpler way of doing it."
Yes, use ajax to send individual create/update requests. This can be done transparently to the user and provides simpler code and a far better user experience.
Rails has fields_for and accepts_nested_attributes that can be used to create/update multiple child records and the parent record in a single request. But it really requires a association that groups the records together and even at this can get really hacky and convoluted when it comes to validations.
You want to set it up so that you have a seperate form for each record:
- courses.each do |c|
= render partial: 'courses/_form', course: c
There is really nothing to the form:
# courses/_form.haml.erb
= simple_form_for course, remote: true, html: { 'data-type' => 'json', class: 'course_form'} do |f|
= c.input :title
.row
.medium-6.columns
= c.input :start_date, as: :string, input_html: { class: 'input-datepicker' }
.medium-6.columns
= c.input :end_date, as: :string, input_html: { class: 'input-datepicker' }
= f.submit 'Save', class: 'primary button'
Instead of using a js.erb template we use 'data-type' => 'json' and write our own handler as its easier to target the correct form:
$(document).on('ajax:success', '.course_form', function(event, xhr, status){
var $form = $(this);
alert('Course created');
if (this.method.post) {
// changes form to update instead.
this.method = 'patch';
this.action = xhr.getResponseHeader('Location');
}
});
$(document).on('ajax:error', '.course_form', function(event, xhr, status){
var $form = $(this);
// #todo display errors
});
Creating the controller is very straight forward:
class CoursesController
def create
#course = Course.new(course_params)
respond_to do |format|
if #course.save(course_params)
format.json { head :created, location: #course }
else
format.json do
render json: {
errors: #course.errors.full_messages
}
end
end
end
end
def update
#course = Course.find(params[:id])
respond_to do |format|
if #course.update(course_params)
format.json { head :ok }
else
render json: {
errors: #course.errors.full_messages
}
end
end
end
end
Keep your form, change strong params to this:
params.require(:courses).permit(
courses: [
:id,
:title,
:start_date,
:end_date
]
)
With this code params should be without index key, #courses is just an array:
# CoursesController
def new
#courses = []
# creating 3 items for example
3.times do
#courses << Course.new
end
end
def create
errors = false
#courses= []
# keep courses in the array for showing errors
courses_params[:courses].each do |params|
course = Course.new(params)
#courses << course
unless course.valid?
errors = true
end
end
if errors
render :new
else
# if no errors save and redirect
#courses.each(&:save)
redirect_to courses_path, notice: 'courses created'
end
end
It turns out that the f.simple_fields_for 'courses[]' ... method only gives that fieldset an ID if the form is populated by an existing record, and the params structure of a string ID mapping to a course hash is only used in this case. For "fresh" records, there is no ID and the course hashes are placed in a plain array.
This bit of code was running in the context of "rolling over" courses from one year to another - copying a previous course and changing the dates. This meant that each fieldset had the ID of the original course.
When the form was submitted, a new record was created and validated with the new attributes, and it was this fresh record with no ID that repopulated the form. The "it only happens when one course is submitted" thing was a red herring - a product of the test scenario.
So worth noting: f.simple_fields_for 'courses[]' ... creates an array for new records and a hash mapping IDs to attributes for existing records.

pre-populating form field from database with "second level" association

I have three models: Appointment, Client, and InsuranceProvider
A client has_many :appointments
And a client has_many :insurance_providers (the idea being I"d like to store historical info there).
in my view to create a new appointment, I have this (among other things):
<%= f.association :client, label_method: lambda { |c| "#{c.first_name} #{c.last_name}" }, collection: current_user.clients %>
this is fine, but I'd like to get to the copay field in insurance_providers.
Basically, this is how you'd get there:
appointment.client.insurance_provider.copay
What I'd like to do is pre-populate the "copay amount" field based on the client selected from the dropdown.
How can I do this?
Please let me know if you need to see my models or views explicitly.
If I understand correctly, you want a second select to be populated with values based on the value in the association.
Basically, you need JQuery/AJAX to do this for you. JQuery to watch the first select, and then AJAX to get data from rails based on the value chosen, and JQuery again to add values to the second select.
An alternative would be to use an in-place editor like best_in_place for each select, which would do the AJAX-y stuff for you.
Use ajax to to fetch the values for copay based on the return of the select.
Because there are a lot of steps, I'll lay them out, but you can find them in probably a dozen other SO questions.
Add the Javascript, this coffeescript but it's just your basic on change -> send-data call - so change at will.
#appointment.js.coffee
$(document).ready ->
$(".client_select").on "change", ->
$.ajax
url: "/appointments/new"
type: "GET"
dataType: "script"
data:
client: $(".client_select").val()
Make sure your form has the 2 jquery elements to get data from and push data to.
# First the field to pull from
<%= f.association :client, label_method: lambda { |c| "#{c.first_name} #{c.last_name}" }, collection: current_user.clients, input_html: { class: 'client_select' } %>
# And then the field to push to
<%= f.input :copay_amount, input_html: { class: 'copay_from_client' } %>
This is going to make a request on your "new" action of your appointments controller, so you'll need to add a javascript respond to to make sure it can render the next step, the UJS file.
# appointments_controller.rb
def new
# ... All the stuff you're normally doing and additionally:
#you'll have to adjust the params argument to match your select field
insurance_copay = Client.find(params[:client]).insurance_provider.copay
respond_to do |format|
format.html # new.html.erb
format.js { render "new", locals:{insurance_copay: insurance_copay} }
format.json { render json: #appointment }
end
end
Now add the UJS, new.js.erb
$(".copay_from_client").val('<%= #insurance_copay %>');

Ransack search not working if there is 'space' in search term

I am using ransack for search in my rails 3.2 application using postgres as database.
I have a Invoice model and every invoice belongs_to a buyer. Below is my search form in index page.
views/invoices/index.html.erb
<%= search_form_for #search do |f| %>
<%= f.text_field :buyer_name_cont %>
<%= f.submit "Search"%>
<% end %>
And here is my controller code.
controllers/invoices_controller.rb
def index
#search = Invoice.search(params[:q])
#invoices=#search.result(:distinct => true).paginate(:page => params[:page], :per_page => GlobalConstants::PER_PAGE )
respond_to do |format|
format.html # index.html.erb
format.json { render json: #invoices }
end
end
Let's say a invoice is there of a buyer having name "Bat Man".
If I search "Bat", I get the invoice in results.
Again if I search "Man", I get the invoice in results.
But if I search "Bat Man", I don't get the invoice in results.
I know it might be something trivial but I am not able to resolve.
Update
When I tried the sql query formed directly in database using pgAdmin, I realized that in database there were multiple spaces in the buyer name, something like "Bat.space.space.space.Man".
Can something be done so that "Bat.space.Man" search also finds "Bat.space.space.space.Man" in results?
You could sanitize your data. For instance with regexp_replace(). Run in the database once:
UPDATE invoice
SET buyer = regexp_replace(buyer, '\s\s+', ' ', 'g')
WHERE buyer <> regexp_replace(buyer, '\s\s+', ' ', 'g');
And sanitize new inserts & updates likewise.
\s .. class shorthand for "white space" (including tab or weird spaces).
The 4th parameter 'g' is for "globally", needed to replace all instances, not just the first.
Ransack not support cont search for multi terms, I solved the requirement my customized way. the details as following:
Add scope to your model:
scope :like_search, ->(column, value) {
keywords = value.to_s.split.map{ |k| "%#{k}%" }
where(Array.new(keywords.size, "#{column} ILIKE ?").join(' AND '), *keywords)
}
in your view. instead of using f.text_field :buyer_name_cont provided by ransack, use normal field helper text_field_tag :buyer_name, params[:buyer_name]
then restrict your ransack in scope:
scope = Invoice.like_search(:name , params[:buyer_name])
#q = scope.ransack(params[:q])

Rails 3, change or fill submitted form values in controller

I have two independent "component" models, contacts and articles. Contacts come in many types (producers, exporters etc...).
In the new article form I have a dropdown selector with contacts (id and title) and want to store the selected value and text in the article table.
In the new article form view:
<%= f.select :producer_id, options_for_select(producers, #article.producer_id) %>
That works and producer_id is stored in article table.
That's clear and logical to me, but in some cases I also need to store the selected contact's title in producer_title.
I have read many different options like "do it in model, before save", or "do it in controller", and I have done it inside controller.
Article controller (only part from update):
#cont_name is producer title from Contacts
def update
params[:article][:producer_title] = Contact.where(id: params[:article][:producer_id]).pluck(:cont_name).first
end
This works, but is it the best-practices approach to this problem?
Also, why I can't get it to work if I change the params[producer_id] part to use: id: params[:producer_id] ?
Best regards and thanks.
How about something like the following instead:
def edit
#article = Article.find(params[:id])
#producers = Contact.where(type: "producer") # or however you distinguish between producers and other contacts
end
Then in your form change it to:
f.select :producer_id, options_from_collection_for_select(#producers, "cont_name")
# you might need to do (#producers, "cont_name", "cont_name"), can't quite remember
Then your update action will be much simpler:
def update
#article = Article.find(params[:id])
if #article.update_attributes(params[:article])
...
else
...
end
end
The reason :id => params[:producer_id] doesn't work is that params is a nested hash, so something like:
params = { :article => { :producer_id => 34, :title => "Cool Article" }, :another_key => { :key => :value } }
So to access the producer_id you first have to retrieve the article hash, otherwise it will only look through the first set of keys which include article and another_key in the example above, but don't include producer_id.

Rails Craiglist like list of items

So I have a state model and city model associated with has_many and belongs_to. I want to display a page with each state with its associated cities underneath.
I created a page controller and page called "Locations" and manually entered in
<%= link_to "Allentown", allentown_path %>
which then takes you to the allentown page.
On the allentown page I filtered the listings by adding this code to the pages controller
def allentown
#title = "Allentown Listings"
#tattoo_briefs = TattooBrief.where( :city_id => 1 ).find(:all, :order => "id DESC" )
end
I know this isn't DRY. Also can get very cumberson if I have 200 cities. Is there a better way?
You need to add a resource to your routes:
routes.rb
resources :city
That essentially gives you all the RESTful actions for the City model. Then, in your controller, use the show action to..wait for it..show your city page
cities_controller.rb
def show
#city = City.find(params[:id])
#title = "#{#city.name} Listings"
#tattoo_briefs = TattooBrief.where( :city_id => params[:id] ).find(:all, :order => "id DESC" )
end
you can still modify this by studying more on routes and controllers from the rails api. With added knowledge, you can get to allentown by modifying your route to use the city name instead of the id.

Resources