Rails Admin: too many dropdown entries - ruby-on-rails

in my rails_admin application, I have a table "Branch" that references another table called "Alarms". In "Branches", I'd like to select exactly one alarm and have a search box to search these alarms.
Currently, I'm only capable to create a drop down with all alarms related to that branch which causes many entries to be rendered (over 25k) and memory to be consumed.
I couldn't find an example how to create a dropdown that says "too many entries, search to select", similar to the has_and_belongs_to_many.
Here's how it looks:
class Branch < ApplicationRecord
...
# this is fine and works as expected, selecting only the first (and only) entry
show do
field :maintenance_period_trigger_alarm_id do
formatted_value do
alarm = Alarm.where(id: bindings[:object].maintenance_period_trigger_alarm_id).first
path = bindings[:view].show_path(model_name: 'alarm', id: alarm.id)
bindings[:view].link_to(alarm.name, path)
end
end
end
...
# this is where all elements are automatically loaded and rendered in the dropdown
edit do
field :maintenance_period_trigger_alarm_id, :enum do
enum do
Alarm.where(branch_id: bindings[:controller].params[:id]).collect{ |alarm| [alarm.name, alarm.id] }
end
end
end
I'm not sure how I can enable a simple "Too many entries" message to not show 25k entries in there.

#zokkker13, here is a very basic fleshed-out example that will query a people_controller.rb for people by name.
Add select2 to your page
Either download select2.js and select2.css, put them into your vendor folder and add them to your applications.js and application.css files, or just
include them from a CDN into your page. Load your page and use your browser's dev tools to make sure that select2.js is loaded.
Add a new route to config/routes.rb
resources :people do
collection do
get 'search'
end
end
Restart your server.
Add search action to people_controller.rb
# coding: utf-8
class PeopleController < ApplicationController
def search
# https://select2.org/data-sources/ajax
term = params[:term]
matches = Person.where("name ILIKE ?", "%#{term}%").to_a
# https://select2.org/data-sources/formats
matches.map!{ |m| { id: m.id, text: m.name } }
respond_to do |format|
format.json {
render status: 200, json: { results: matches }
}
end
end
end
Add search box to page
Add this to your html page:
<%= select_tag :search, nil, id: 'select2-live-search-people' %>
<script>
$(document).ready( function() {
$('#select2-live-search-people').select2({
ajax: { url: '/people/search', dataType: 'json' },
placeholder: 'Start typing to search...',
width: '300px',
minimumInputLength: 3
});
} );
</script>
.. or this, if you use haml:
= select_tag :search, nil, id: 'select2-live-search-people'
:javascript
$(document).ready( function() {
$('#select2-live-search-people').select2({
ajax: { url: '/people/search', dataType: 'json' },
placeholder: 'Start typing to search...',
width: '300px',
minimumInputLength: 3
});
} );
Modify the code so that it will query an existing model w/ existing data. Then relaod the page and start typing. You should now see the select option getting adjusted to the result of your search term. Use the minimumInputLength parameter to control how many characters are minimally needed to issue an ajax call. With potentially 25k options, you might want to increase it even more.

Related

In Rails, how do I set up the Twitter Typeahead.js gem for Rails?

The bootstrap-typeahead-rails gem's README kicks the question over to Twitter's typeahead.js README. This left much to be desired.
This Stack Overflow answer provides detailed instructions for the twitter-typeahead-rails gem. I wanted to see something like that for the bootstrap-typeahead-rails gem.
Here's my guide. It follows #ihaztehcodez's example. This example assumes a model Thing and adds a form to the index view for searching things by the model's name attribute.
A few notes:
I'm using Rails 4 (4.2.1).
For search queries, I'm using the Searchlight gem.
For templates, I'm using the slim-rails gem.
Styling is left as an exercise for the developer.
Add gem to gemfile
# Gemfile
# Typeahead gem
gem 'bootstrap-typeahead-rails'
# Optional gems
gem 'searchlight'
gem 'slim-rails'
Include typeahead files in asset manifests
Stylesheet (SASS)
# app/assets/stylesheets/application.scss
*= require bootstrap-typeahead-rails
Javascript
# app/assets/javascripts/application.js
//= require bootstrap-typeahead-rails
//= require_tree .
Add typeahead route to routes file
# config/routes.rb
get 'things/typeahead/:query' => 'things#typeahead'
Add typeahead javascript code
# app/assets/javascripts/things.js
var onReady = function() {
// initialize bloodhound engine
var searchSelector = 'input.typeahead';
var bloodhound = new Bloodhound({
datumTokenizer: function (d) {
return Bloodhound.tokenizers.whitespace(d.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
// sends ajax request to remote url where %QUERY is user input
remote: '/things/typeahead/%QUERY',
limit: 50
});
bloodhound.initialize();
// initialize typeahead widget and hook it up to bloodhound engine
// #typeahead is just a text input
$(searchSelector).typeahead(null, {
displayKey: 'name',
source: bloodhound.ttAdapter()
});
// this is the event that is fired when a user clicks on a suggestion
$(searchSelector).bind('typeahead:selected', function(event, datum, name) {
//console.debug('Suggestion clicked:', event, datum, name);
window.location.href = '/things/' + datum.id;
});
};
Add relevant methods/actions to controller
# app/controllers/things_controller.rb
# GET /things
# GET /things.json
def index
#search = ThingSearch.new(search_params)
#things = search_params.present? ? #search.results : Thing.all
end
# GET /things/typeahead/:query
def typeahead
#search = ThingSearch.new(typeahead: params[:query])
render json: #search.results
end
private
def search_params
params[:thing_search] || {}
end
Add search form to index view (using SLIM gem)
# app/views/things/index.html.slim
div.search.things
= form_for #search, url: things_path, method: :get do |f|
div.form-group.row
div.col-sm-3
div.col-sm-6
= f.text_field :name_like, {class: 'typeahead form-control',
placeholder: "Search by name"}
= f.submit 'Search', {class: 'btn btn-primary'}
div.col-sm-3.count
| Showing <strong>#{#things.length}</strong> Thing#{#things.length != 1 ? 's' : ''}
Create Searchlight search class
If you prefer not to use Searchlight, use the ActiveRecord query interface in the model.
# app/searches/thing_search.rb
class ThingSearch < Searchlight::Search
search_on Thing.all
searches :name_like, :typeahead
# Note: these two methods are identical but they could reasonably differ.
def search_name_like
search.where("name ILIKE ?", "%#{name_like}%")
end
def search_typeahead
search.where("name ILIKE ?", "%#{typeahead}%")
end
end
#klenwell's answer is out of date. Here's how I got it to work:
I'm using:
Bootstrap v3.3.6
bloodhound 0.11.1
bootstrap3-typeahead 3.1.0
jQuery 2.2.0
My model is called Destination.
app/models/destination_search.rb:
class DestinationSearch < Searchlight::Search
def base_query
Destination.all
end
def search_typeahead
query.where("name ILIKE", "%#{typeahead}%")
end
end
controller:
class DestinationsController < APIController
def typeahead
render json: DestinationSearch.new(typeahead: params[:query]).results
end
end
JS:
var bloodhound = new Bloodhound({
datumTokenizer: function (d) {
return Bloodhound.tokenizers.whitespace(d.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/api/destinations/typeahead?query=%QUERY',
wildcard: "%QUERY",
},
limit: 10
});
bloodhound.initialize();
$(document).ready(function () {
$(".destination-typeahead").typeahead({
source: function (query, process) {
return bloodhound.search(query, process, process);
},
});
});
and in the view:
<%= text_field_tag :destination, class: "destination-typeahead" %>
It feels a little bit hacky how I'm passing the process method into bloodhound.search twice - this is because bloodhound#search takes two callbacks as arguments, one that deals with cached/prefetched data and one that deals with data pulled dynamically via AJAX. I might not be using #search 100% correctly, but this approach works, and it's a simple start.

Ruby on Rails Previous Searches Display when Typing in Search Bar

Ok so what I have is a library database system where a user can type in books in a search bar and then the books that match their search are displayed when they click enter. What I want to do is get it so that when the user types "a" into the search bar all records will be displayed in a drop down below the menu with the letter "a", and then when they input the next letter "l" all records with "al" will be displayed in a drop down, and then "ali", and so on. I do not have much knowledge of gems, but want to learn. Is their a way I can do this? I am using rails 4.0.1 and at the minute have a fuzzy search method.
Or if you want to roll your own...
This is the gist of it, might require a bit of tweaking:
routes.rb
get '/search' => 'search#autocomplete', as: :search_autocomplete
search_controller.rb
def autocomplete
search_term = params[:search]
#results = YourModel.where( "your_field LIKE search_term" )
respond_to do |format|
format.json #results
end
end
your_view.html.erb
<%= form_tag( search_autocomplete_path, method: "get" ) do %>
<%= text_field_tag( :search, params[:search], placeholder: 'Enter a search term...', :id => 'autocomplete_search' ) %>
<%= submit_tag( "Go" ) %>
<div id="autocomplete_search_results">
<% end %>
some_coffeescript_file.js.coffee
$( document ).ready ->
$.ajax '/search',
type: 'GET'
parameters: $( "#autocomplete_search" ).val()
dataType: 'json'
success: ( data ) ->
# here you'll have to append the results to whichever div/container you have in place
$( '#autocomplete_search_results' ).append "#{ data }"
Checkout Twitter typeahead.js jquery plugin https://github.com/yourabi/twitter-typeahead-rails.

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 %>');

Ruby on Rails and how to render partial using json and jquery

Ruby on Rails newbie whose confused and frustrated :) I've spent over a day on this and think I've probably just confused myself.
Basically, I'm trying to render a partial in a view. Here's what I have specifically:
A form with 2 basic fields: Category and SubCategory. SubCategory changes depending on what the user selected in Category. I'm using "JQuery" with the assets pipeline enabled. This part works:
contact_infos.js.coffee
jQuery(document).ready(->
$("select#contact_info_category_id").change(->
id_value_string = $(#).val()
if id_value_string == ""
# if the id is empty remove all the sub_selection options from being selectable and do not do any ajax
$("select#contact_info_subcategory_id option").remove()
row = "" + "" + ""
$(row).appendTo("select#contact_info_subcategory_id")
else
# Send the request and update sub category dropdown
tmp = '/subcategories/for_categoryid/' + id_value_string + '.json'
$.ajax(
type: 'GET',
dataType: 'json',
url: tmp,
timeout: 2000,
error: (XMLHttpRequest, errorTextStatus, error) -> alert "Failed to submit : " + errorTextStatus + " ;" + error,
success: (data) ->
# Clear all options from sub category select
$("select#contact_info_subcategory_id option").remove()
# put in a empty default line
row = "" + "" + ""
$(row).appendTo("select#contact_info_subcategory_id")
# Fill sub category select
$.each(data, (i, j) ->
row = "" + j.name + ""
$(row).appendTo("select#contact_info_subcategory_id")
)
)
)
)
It generates a json response correctly.
When the form loads, in addition to Category and SubCategory, I also have 2 text fields - previous_value and current_value; however, if
SubCategory == "Full"
then I hide previous_value and current_value and need to insert a partial with new fields.
I'm having no problem hiding previous_value and current_value fields with JQuery works and looks like this (this is inserted into the code above):
$("select#contact_info_subcategory_id").change(->
id_text_string = $("#contact_info_subcategory_id option:selected").text()
if id_text_string == "Full"
$('#contact_info_previous_value_id').hide()
$('#contact_info_current_value_id').hide()
else
$('#contact_info_previous_value_id').show()
$('#contact_info_current_value_id').show()
)
I created a div called "test" in my form where I want to insert the new fields if SubCategory is "Full" and of course, inserting this line into the contact_infos.js.coffee doesn't work:
$('#test').html('<%= escape_javascript render("contact_infos/_full_name_info") %>')
as all I get on the page is the string "<%= escape_javascript render("contact_infos/_full_name_info") %>"
I've tried the following but can't get any to work:
1. creating a new.json.erb file with the following code:
<% self.formats = ["html"] %>
test = {
"html":"<%= raw escape_javascript(render :partial => 'contact_infos/full_name_info',
:content_type => 'text/html')}
%>"
}
This json file never triggered. My controller has this line:
format.json { render json: #contact_info }
Is this the best way to do this? If yes, what can I try next?
2. I saw a posting yesterday (I can't find it now - was on another computer) about creating a javascript variable (I called it fullnamefield) in the application.html.erb layout file as well as adding the js variable to the new.html.erb view, which I did. I also added this line to the contacts_infos.js.coffee:
('#test').html(fullnamefield)
and it worked!! EXCEPT that then when I went to any other area of the site, I got an error.
3. As a workaround, I thought about trying to change the json that my jquery produces to a js and then trying to trigger the new.js.erb. I ran into trouble trying to convert the ajax call. I could create "json" and also "text" dataTypes but not script (not sure why).
So... any ideas/help? I've really been searching and I'm frustrated enough that I'm considering just creating all the fields and hidings/showing them as needed from JQuery, which would be so simple to implement but is just wrong.
UPDATE: Attempt 4 (or is it 40?):
What you wrote got me thinking... I think I'm close but not there yet.
In my _form.html.erb, I added to the Subcategory field data-remote, data-url and data-type:
<div class="field">
<%= f.label :category_id %>
<br/>
<%= collection_select(:contact_info, :category_id, Category.all, :id, :name, options ={:prompt => "--Select a Category--"}) %>
</div>
<div class="field">
<%= f.label :subcategory_id %>
<br/>
<%= collection_select(:contact_info, :subcategory_id, Subcategory.find_all_by_category_id(#contact_info.category_id), :id, :name, options ={:prompt => "--Select a SubCategory"}, "data-remote" => true, "data-url" => "/contact_infos/get_full_fields", "data-type" => :json ) %>
</div>
Then in the contact_infos_controller.rb I added:
def get_full_fields
#full_name = FullName.new
respond_to do |format|
format.js
end
end
In my routes.rb I modified contact_infos by adding collection do...
resources :contact_infos do
collection do
get 'get_full_fields'
end
end
I created contact_infos\get_full_fields.js.erb:
var full_fields_form = $('<%= j(render(:partial => "contact_infos/full_name_info"))%>');
$('#test').html(full_fields_form);
Now when I test this in the browser with debugger and change SubCategory to "Full" I can see that it runs correctly (I think) in that I'm getting this back:
Request URL:http://localhost:3000/contact_infos/get_full_fields?contact_info%5Bsubcategory_id%5D=3
Request Method:GET
Status Code:200 OK
The "Type" is showing up as "text/javascript." The Response tab is just showing the javascript code but nothing is happening/triggering. Even when I place just a
alert('hello');
in the js file nothing happens.
Any ideas why?
Why not do it the same way you get subcategory data? Create a view containing the partial (and corresponding controller action) and call it via ajax when you want to display that content.

RoR live-search (text_field_with_auto_complete) submit

I have a "Movies" and a "Actors" table and "Casts" as join-model. To be more specific "Casts" has movie_id, actor_id and rolename.
I want in "Movies" form to add a live search to search through actors and a "rolename" text_field and save those to "Casts".
I don't know if text_field_with_auto_complete is the right choice but i prefer not to use much javascript because i am not familiar with it.
I've been searching all over the internet to find something similar to this without any result.
I've manage to get it working with "#actors.each do" but it makes a very long list.
It's not a plugin, but with a little jQuery magic, you can make use of http://github.com/chadisfaction/jQuery-Tokenizing-Autocomplete-Plugin. The nice thing about this is that since it is pure JS in its implementation, you can create the AJAX call yourself in Rails and only display what you want. It even allows you to add a stylesheet to the dropdown if you want to make it more Facebook like. In your controller, add a function so the AJAX call will return a list of rows in JSON:
def taglist
tags = []
sql = "SELECT id,name ... LIMIT 15" # Enter SQL here to produce a possible result set
result = ActiveRecord::Base.connection.execute(sql)
# Iterate over the hash values and push them into an array
result.each { |field| tags.push( {"id" => field[0], "name" => field[1]} ) }
result.free
render :json => tags, :layout => false
end
In the view, add the following code:
<%= javascript_include_tag 'jquery.tokeninput' %>
<%= stylesheet_link_tag 'token-input-facebook' %>
<script type="text/javascript">
jQuery(document).ready(function () {
jQuery("#actors_role").tokenInput("/actors/rolesearch", {
allowNewValues: false,
canCreate: false,
hintText: "Enter the actor's name or role they played",
});
});
</script>
See text_field_with_auto_complete inside form_for
In the auto_complete controller action, make sure your SQL query is restricting the actors names using the passed param.

Resources