acts_as_list problem with scope - ruby-on-rails

The problem I have is at the bottom.
Models:
class Skill
has_many :tags
acts_as_list :column => 'sequence'
end
class Tag
belongs_to :skill
acts_as_list :column => 'sequence', :scope => :skill
end
View:
<table id="skills">
<% #skills.each do |s| %>
<tr id="skill_<%= s.id %>">
<td>
<%= s.name %>
</td>
<td>
<ul id="tags">
<% s.tags.each do |t| %>
<li id="tag_<%= t.id %>">
<%= t.name %>
</li>
<% end %>
</ul>
</td>
</tr>
<% end %>
</table>
jQuery for drag and drop:
$( "#skills" ).sortable({
axis: 'y',
dropOnEmpty: false,
handle: '.handle',
cursor: 'move',
items: 'tr',
opacity: 0.4,
scroll: true,
update: function(){
$.ajax({
type: 'post',
data: $('#skills').sortable('serialize') + "&authenticity_token=" + "<%= form_authenticity_token %>",
dataType: 'script',
complete: function(request){
$('#skills').effect('highlight');
},
url: '<%= url_for :action => 'sort', :controller => 'skills' %>'
})
}
});
$( "#tags" ).sortable({
axis: 'y',
dropOnEmpty: false,
handle: '.handle',
cursor: 'move',
items: 'li',
opacity: 0.4,
scroll: true,
update: function(){
$.ajax({
type: 'post',
data: $('#tags').sortable('serialize') + "&authenticity_token=" + "<%= form_authenticity_token %>",
dataType: 'script',
complete: function(request){
$('#tags').effect('highlight');
},
url: '<%= url_for :action => 'sort', :controller => 'tags' %>'
})
}
});
Controller for Tags:
def sort
#tags = Tag.all
#tags.each do |tag|
tag.sequence = params['tag'].index(tag.id.to_s) + 1
tag.save
end
render :nothing => true
end
PROBLEM:
After dragging the tags there is an error:
NoMethodError (You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.+):
app/controllers/tags_controller.rb:12:in `block in sort'
app/controllers/tags_controller.rb:11:in `each'
app/controllers/tags_controller.rb:11:in `sort'
I found the error is gone if I load tags belonging to a specific Skill like this:
def sort
#tags = Skill.find(1).tags
Question - how to tell the controller which tags to load (but not all tags)?
A SOLUTION I FOUND...
Tags controller:
def sort
#tag = Tag.find(params[:tag]).first
#skill = Skill.find_by_id(#tag.skill_id)
#tags = #skill.tags
Is this the best way to do it?

The error occurred while evaluating nil.+):
This means that whatever params['tag'].index(tag.id.to_s) + 1 (from sort in controller) is supposed to do is actually resulting in nil + 1.
If your solution performs as expected, then I don't see a problem with it.
As a tip, #skill = Skill.find_by_id(#tag.skill_id) could be shortened to #skill = #tag.skill if you do belongs_to :skill in your Tag model.

Related

Ajax call on a <select> in a formtastic form

I need in my app to select from a list of provider a provider, and then, via ajax, I could see below a list of categories, that belongs to a specific provider. I have a form in activeadmin:
<%= semantic_form_for [:admin, #game], builder: ActiveAdmin::FormBuilder do |f| %>
<%= f.semantic_errors :state %>
<%= f.inputs do %>
<%= f.input :categorization_id, label: 'Provider', as: :select,
collection: Provider.all.map { |provider| ["#{provider.name}", provider.id] },
input_html: { class: (:provider_select), 'data-url': category_select_path(provider: 4) } %>
<%= f.input :categorization_id, label: 'Category',input_html: { class: ('category_dropdown') }, as: :select,
collection: Category.all.map { |category| ["#{category.name}", category.id]}%>
...
<% end %>
<%= f.actions %>
<% end %>
In activeadmin controller I have:
controller do
def ajax_call
#provider = Provider.find(params[:provider])
#categories = #provider.categories
respond_to do |format|
format.json { render json: #categories }
end
end
end
JS:
$(document).on('ready page:load', function () {
$('.select.input.optional').last().addClass('hidden_row');
$('#game_categorization_id').change(function () {
var id_value = this.value;
$('.hidden_row').removeClass('hidden_row');
$.ajax({
type: 'GET',
url: '/admin/games/category_select'
// data: id_value
})
});
});
And the routes: match '/admin/games/category_select' => 'admin/games#ajax_call', via: :get, as: 'category_select'
I don't have an idea, how can I pass providers id from collection into url. Currently, I have there category_select_path(provider: 4), but actually it has to be smth. like this - category_select_path(provider: provider.id) In the browser, in a Network tab of devtools I can see my category_select, but there is an error: Couldn't find Game with 'id'=category_select. I can't figure out from where does it come. Any suggestions? Thanks.
The problem was in routes: match '/admin/games/category_select' => 'admin/games#ajax_call', via: :get, as: 'category_select'. It is reserved by a show action of Activeadmin controller. So I changed my routes to: get '/admin/select_category' => 'admin/games#get_providers_categories', as: 'select_category', and added to ajax call data: {provider: provider}, so I could fire in params id of provider:
$.ajax({
type: 'GET',
url: '/admin/select_category',
data: {
provider: provider
},
success: (function (data) {
$('#select_category').children('option').remove();
$('#select_category').prepend('<option value=""></option>');
$.each(data.categories, function () {
$('#select_category').append('<option value="' + this.id + '">' + this.name + '</option>')
})
})
})

Searchkick in another controller

I am a newbie in Ruby on rails, please help me solve my problem.
I already use gem searchkick, and in console I can get the result.
this is my code
route.rb
resources :m_customers
resource :patient, except: [:index, :show] do
collection do
get 'autocomplete'
end
end
root :to => 'home#index'
get 'patient/PatientList', to:'patient#PatientList'
get 'patient/PatientList/autocomplete', to:'patient#autocomplete'
get 'patient/PatientHistory/:kode/:histid', to:'patient#PatientHistory', as: 'patient/PatientHistory'
get 'patient/PatientSub/:id', to:'patient#PatientSub', as: 'patient/PatientSub'
get 'patient/PatientObj/:objid', to:'patient#PatientObj', as: 'patient/PatientObj'
get 'patient/PatientAsses/:assid', to:'patient#PatientAsses', as: 'patient/PatientAsses'
get 'patient/PatientPlan/:planid', to:'patient#PatientPlan', as: 'patient/PatientPlan'
m_customer.rb => model
class MCustomer < ActiveRecord::Base
self.primary_key = :Cust_ID
searchkick match: :word_start, searchable: [:Cust_Name, :Cust_ID]
MCustomer.search "tipping poi"
end
patient_controller.rb
class PatientController < ApplicationController
require 'will_paginate/array'
def PatientList
#date_now = Time.now
#patients = TRegistration.paginate(:page => params[:page], :per_page => 7).where(:Reg_status => 'N')
#patient2s = TRegistration.paginate(:page => params[:page], :per_page => 7).where(:Reg_status => 'P')
if params[:query].present?
#MCustomers = MCustomer.search(params[:query])
else
#MCustomers = []
end
end
def autocomplete
render json: MCustomer.search(params[:query], autocomplete:true, limit: 10).map do |customer| {name: customer.Cust_Name, value: customer.Cust_ID}
end
end
end
when I go to the page below, I can find the result from searchkick :
//localhost:3000/patient/PatientList/autocomplete?query=s
but when I inserted serch input, nothing could be shown
PatientList.html.erb
<div class="cust-search">
<%= form_tag patient_PatientList_path, method: :get do %>
<div class="form-group">
<%= text_field_tag :query, params[:query], class: 'form-control twitter-typeahead' %>
<%= submit_tag 'Search', class: 'btn btn-default' %>
</div>
<% end %>
</div>
java script patient.js
var ready;
ready = function() {
var engine = new Bloodhound({
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.name); },
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: { url: '../patient/PatientList/autocomplete?query=%QUERY' }
});
// initialize the bloodhound suggestion engine
var promise = engine.initialize();
promise
.done(function() { console.log('success!'); })
.fail(function() { console.log('err!'); });
// instantiate the typeahead UI
$( '.twitter-typeahead').typeahead(null, {
displayKey: 'Cust_Name',
source: engine.ttAdapter()
});
}
$(document).ready(ready);
$(document).on('page:load', ready);
please help. thanks anyway.
This is what your code should look like if you want a working autocomplete (only the relevant parts)
m_customer.rb (you should really consider naming it just customer)
class MCustomer < ActiveRecord::Base
searchkick autocomplete: ['Cust_Name']
end
you should use the autocomplete option and provide the column you want to autocomplete, e.g: cust_name
patient_controller.rb ( you should really consider naming it customer_controller )
class PatientController < ApplicationController
def autocomplete
render json: MCustomer.search(params[:query], autocomplete:true, limit: 10).map do |customer| { name: customer.Cust_Name }
end
end
end
here you are formatting your json response
patient.js (read this carefully and see the modifications)
var engine = new Bloodhound({
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.name); },
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: { url: '/patient/autocomplete?query=%QUERY' }
});
var promise = engine.initialize();
$( '.twitter-typeahead').typeahead(null, {
name: "customer",
displayKey: "name",
limit: 20,
source: engine.ttAdapter()
});
}
I think this is where you made your mistake, you should provide the right key so the method will read your json as it should and retrieve the names you want.
you should check out this wonderful guide, you can read and just copy-paste with the modifications that suit your code:
https://rubyplus.com/articles/4031-Autocomplete-using-Typeahead-and-Searchkick-in-Rails-5
hope it helps anyone

Rails collection select when collection is way big

I have a form partial inside which I select associated users through a multiple: true collection select:
= f.collection_select(:user_ids, User.all, :id, :email, {selected: #limit_group.user_ids, include_blank: true}, {multiple: true, "data-placeholder" => "Add users to group"})
But how can I do this more efficiently to avoid big load times when the database has like thousands of users?
You'll be better using something called AutoComplete / LiveSearch with a text box (like Pardeep Saini mentioned).
We've done this before:
You could achieve this relatively simply:
= f.text_field :user_ids, placeholder: "Search for users"
You'd then have to use javascript:
#app/assets/javascripts/application.js
$(document).on("keyup", "input[type=text]#user_ids", function(){
$.getJSON("users/search", {name: $(this).val()}).done(function(json){
var users = [];
$.each(json.users, function(user) {
users.push("" + user.name + "");
});
$(".search").html(users).show();
});
});
$(document).on("click", ".search a", function(e) {
e.preventDefault();
// add hidden field with user name to form
});
You'd have to back it up with the relevant controller action:
#config/routes.rb
resources :users do
get :search, on: :collection
end
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def search
#users = User.where("name LIKE ?", "%" + params[:name] + "%")
respond_to do |format|
format.json (render json: #users.to_json)
end
end
end
The above code should be refactored.
--
To get this working with multiple values would be a little bit more involved. It could be done, but you'd have to do it like the tags setup in StackOverflow...
The way they do that is to basically use a similar principle to the above (each tag will be a returned piece of data from the search).
Here's the actual code we used in the cosmetics example above:
#app/assets/javascripts/extra/jquery.livesearch.js
(function($) {
$.searchbox = {}
$.extend(true, $.searchbox, {
settings: {
url: 'search',
param: 'search',
dom_id: '#livesearch',
minChars: 2,
loading_css: '#livesearch_loading',
del_id: '#livesearch_del'
},
loading: function() {
$($.searchbox.settings.loading_css).show()
},
idle: function() {
$($.searchbox.settings.loading_css).hide()
},
start: function() {
$.searchbox.loading()
$(document).trigger('before.searchbox')
},
stop: function() {
$.searchbox.idle()
$(document).trigger('after.searchbox')
},
kill: function() {
$($.searchbox.settings.dom_id).fadeOut(50)
$($.searchbox.settings.dom_id).html('')
$($.searchbox.settings.del_id).fadeOut(100)
},
reset: function() {
$($.searchbox.settings.dom_id).html('')
$($.searchbox.settings.dom_id).fadeOut(50)
$('#SearchSearch').val('')
$($.searchbox.settings.del_id).fadeOut(100)
},
process: function(terms) {
if(/\S/.test(terms)) {
$.ajax({
type: 'GET',
url: $.searchbox.settings.url,
data: {search: terms.trim()},
complete: function(data) {
$($.searchbox.settings.del_id).fadeIn(50)
$($.searchbox.settings.dom_id).html(data.responseText)
if (!$($.searchbox.settings.dom_id).is(':empty')) {
$($.searchbox.settings.dom_id).fadeIn(100)
}
$.searchbox.stop();
}
});
return false;
}else{
$.searchbox.kill();
}
}
});
$.fn.searchbox = function(config) {
var settings = $.extend(true, $.searchbox.settings, config || {})
$(document).trigger('init.searchbox')
$.searchbox.idle()
return this.each(function() {
var $input = $(this)
$input
.keyup(function() {
if ($input.val() != this.previousValue) {
if(/\S/.test($input.val().trim()) && $input.val().trim().length > $.searchbox.settings.minChars){
$.searchbox.start()
$.searchbox.process($input.val())
}else{
$.searchbox.kill()
}
this.previousValue = $input.val()
}
})
})
}
})(jQuery);
... and ...
#app/assets/javascripts/application.js
$(document).ready( function() {
var base_url = window.location.protocol + "//" + window.location.host;
$('#SearchSearch').searchbox({
url: base_url + '/search/',
param: 'search',
dom_id: '#livesearch',
loading_css: '#livesearch_loading'
})
});
$(document).on('click', '#livesearch_del', function() { $.searchbox.reset(); })
$(document).on('submit', '#SearchForm', function() { $.searchbox.kill(); });
$(document).on('click', '.livesearch_results tr', function() { window.location = $('a:first', this).attr('href'); });
The routes & controller:
#config/routes.rb
match 'search(/:search)', :to => 'products#search', :as => :search, via: [:get, :post]
#app/models/product.rb
class Product < ActiveRecord::Base
def self.search(search)
where("name LIKE ? OR description LIKE ?", "%#{search}%", "%#{search}%").take(5)
end
end
#app/controllers/product_controller.rb
class ProductsController < ApplicationController
def search
#products = Product.search params[:search]
respond_to do |format|
format.js { render :partial => "elements/livesearch", :locals => {:search => #products, :query => params[:search]} }
format.html {
render :index
}
end
end
end
The views:
#app/views/elements/_livesearch.html.erb
<div class="livesearch_container">
<table class="livesearch_results">
<% unless search.blank? %>
<% search.each_with_index do |item,i| %>
<% pos ||= '' %>
<% if (i == 0) then pos = 'first' end %>
<% if (i == search.size - 1) then pos += ' last' end %>
<tr data-link="<%= "/#{item.slug}" %>" class="<%= "#{pos}" %>">
<td class="image">
<% model = item.images.first || item.images.build %>
<%= image_tag(model.image.url(:thumb), :title => item.name, data: {"placement" => "left"}, :height => "85") %><br/>
</td>
<td class="information">
<%= link_to image_tag(item.brand.images.first.image.url(:thumb), :width => "55", :title => "View #{item.brand.name}"), "/#{item.brand.slug}", :class => "brand" if defined?(item.brand.images.first) %>
<div class="name"><%= link_to item.name, "/#{item.slug}" %></div>
</td>
<td class="price">
<%= number_to_currency(item.price, unit: "£") %>
</td>
</tr>
<% end %>
<tr class="results"><td colspan="3"><%= link_to "See all #{search.size} results here »", search_path(query) %></td></tr>
<% else %>
<tr class="results"><td colspan="3"><%= link_to 'No results found', search_path(query) %></td></tr>
<% end %>
</table>
</div>
I also made a gist here: https://gist.github.com/richpeck/2310ff3ab1ffcd6a9138

Searchkick + Bloodhound + Typeahead for autocomplete

I am trying to implement a simple autocomplete feature for a single attribute.
Model:
searchkick text_start: [:name],autocomplete: ['name']
After reindexing the behaviour on the Rails console is ok.
2.2.0-p0 :002 >Doctor.search("a", autocomplete: true).map(&:name)
gives the output-
=> ["a", "aa", "aaa", "aaaa"]
After this i added the Autocomplete action to the controller and a new route to the routes.rb file.
Controller:
def autocomplete
console.log("In auto")
render json: Doctor.search(params[:query], autocomplete: false, limit: 10).map(&:name)
end
Routes:
resources :doctors do
collection do
get :autocomplete
end
end
At this point if i simply test the following URL:
http://localhost:3000/doctors/autocomplete?query="a"
Then i get the expected result in the browser:
["a", "aa", "aaa", "aaaa"]
Now adding a search box.
_header.html.erb:
<%= form_tag doctors_path, method: :get do %>
<div class="form-group">
<%= text_field_tag :query, params[:query], class: 'form-control typeahead', autocomplete: "off" %>
<%= submit_tag 'Search', class: 'btn btn-primary' %>
</div>
<% end %>
And finally the Javascript:
var ready;
ready = function() {
var numbers = new Bloodhound({
remote: {url: "/doctors/autocomplete?query=%QUERY"},
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.name); },
queryTokenizer: Bloodhound.tokenizers.whitespace
});
// initialize the bloodhound suggestion engine
var promise = numbers.initialize();
promise
.done(function() { console.log('success!'); })
.fail(function() { console.log('err!'); });
// instantiate the typeahead UI
$( '.typeahead').typeahead(null, {
displayKey: 'name',
source: numbers.ttAdapter()
});
}
$(document).ready(ready);
$(document).on('page:load', ready);
And this is the script tag used:
<script type="text/javascript" src="https://twitter.github.io/typeahead.js/releases/latest/typeahead.bundle.js"></script>
There is no response shown by the search box on typing anything,also there is no error shown on the console of Google Chrome.
You need to return hash in response of your autocomplete action:
def autocomplete
render json: Doctor.search(params[:query], autocomplete: true, limit: 10).map {|doctor| {name: doctor.name, value: doctor.id}}
end

How do I create a hash of hashes inside of a .each in Ruby

I'm working on a Rails application that tracks different events and their statuses.
Here's my Status model:
class Status < ActiveRecord::Base
attr_accessible :value
has_many :events
end
There is an interface to add additional status types.
My Event model looks like this:
class Event < ActiveRecord::Base
attr_accessible :status_id
belongs_to :status
class << self
Status.all.each do |status|
define_method(status.value.downcase) do
send("where", :status_id => Status.find_by_value(status.value.downcase))
end
end
end
end
So for example I have three different status values: Outage, Slow, Error, etc.
With this I can do:
Event.outage
or:
Event.slow
and I'll get back an ActiveRecord::Relation of all events with that status. This works as expected.
I have a view that generates some graphs dynamically using Highcharts. Here's my view code:
<script type="text/javascript" charset="utf-8">
$(function () {
new Highcharts.Chart({
chart: { renderTo: 'events_chart' },
title: { text: '' },
xAxis: { type: 'datetime' },
yAxis: {
title: { text: 'Event Count' },
min: 0,
tickInterval: 1
},
series:[
<% { "Events" => Event,
"Outages" => Event.outage,
"Slowdowns" => Event.slow,
"Errors" => Event.error,
"Restarts" => Event.restart }.each do |name, event| %>
{
name: "<%= name %>",
pointInterval: <%= 1.day * 1000 %>,
pointStart: <%= #start_date.to_time.to_i * 1000 %>,
pointEnd: <%= #end_date.to_time.to_i * 1000 %>,
data: <%= (#start_date..#end_date).map { |date| event.reported_on(date).count}.inspect %>
},
<% end %>]
});
});
</script>
<div id="events_chart"></div>
I'd like to generate this hash dynamically with a list of Status types from the database:
<% {
"Outage" => Event.outage,
"Slow" => Event.slow,
"Error" => Event.error,
"Restart" => Event.restart }.each do |name, event|
%>
using something like this:
hash = Status.all.each do |status|
hash.merge("#{status.value}" => Event) ||= {}
end
I want to call each on the hash to generate my charts. This doesn't give me a hash though, it gives me an Array, just like Status.all would by itself.
This is how I would do it, with Enumerable#each_with_object and Object#send:
hash = Status.select(:value).each_with_object({}) do |s, h|
h[s.value.upcase] = Event.send s.value.downcase
end

Resources