Autocomplete action parses a large JSON file at each ajax call (make it faster) - ruby-on-rails

I am trying to make an autocomplete model for cities and zipcode using Rails 4 and Jquery ui.
I have a large JSON file (about 500,000 lines) that contains an array (cp_autocomplete) that contains zipcode (CP) and cities (CITY).
In my current model, on every ajax query the autocomplete action reads the JSON file, parses it and then search for a zipcode or city, makes a list of nine zipcode+cities (if found) and render the list as json.
This works but it makes more than 2 seconds to render a list.
Any ideas to make it runs faster?
- How could I parse the json file once and not on every ajax query?
- I thought of caching the parsed json value and then delete it after 1 minute for example.
Here is my autocomplete action:
def autocomplete
require 'auto_compeletion_cp_city' #a class that has PostalCode and City attributes
postal_file = File.read("#{Rails.root}/public/postal_code_fr.json")
postal_parse = JSON.parse(postal_file)
#list = []
if !(ActionController::Base.helpers.sanitize(params[:Postalcode]).blank?)
search_term = ActionController::Base.helpers.sanitize(params[:Postalcode])
search = /\A#{search_term}/
postal_parse["cp_autocomplete"].each do |f|
if search.match("#{f['CP']}",0) && (#list.length < 9)
selected = AutoCompletionCpCity.new
selected.PostalCode = f['CP']
selected.City = f['CITY']
#list << selected
end
end
else
if !(ActionController::Base.helpers.sanitize(params[:city]).blank?)
search_term = ActionController::Base.helpers.sanitize(params[:city])
search = /\A#{Regexp.escape(search_term)}/i
postal_parse["cp_autocomplete"].each do |f|
if search.match("#{f['CITY']}",0) && (#list.length < 9)
selected = AutoCompletionCpCity.new
selected.PostalCode = f['CP']
selected.City = f['CITY']
#list << selected
end
end
end
end
respond_to do |format|
format.json {
render json: #list
}
end
end

Create a small module to house the postal code data. It can parse the file once when the process starts and hold onto it forever. Since postal codes don't change often (and this file is in your repo, so changing it requires a deploy), there's no reason to cache for just 1 minute.
module PostalCodes
DATA = JSON.parse(File.read("#{Rails.root}/public/postal_code_fr.json"))
end
This module can also be responsible for searching the codes and other behavior you need.
Before you get too deep in this, as with all performance optimization, you should verify the part of your code that's causing search to take 2 seconds. If it's the JSON parsing, caching the data will help, but if it's running a regular expression over ever postal code, you need a different solution.

Related

What is the best way to add seven days recursively to a date in ruby on rails 5.2.3

I currently a create multiple bookings endpoint, this takes weeks as "qty" as a param.
So far I have this:
In my controller action:
def multiple
#qty = params[:qty]
#booking = Booking.new(booking_params)
if #booking.save
#newbookings = #booking.createmore(#qty)
render json: #newbookings, status: :created
else
render json: #booking.errors, status: :unprocessable_entity
end
end
And in my model i have a routine to create multiple.
def createmore(quantity)
bookings = []
quantity.to_i.times do
bookings.push(self)
end
puts "#{#bookings}"
newbookings = []
firstBooking = self
bookings.each do | booking |
booking.start = firstBooking.start
booking.end = firstBooking.end
booking.name = firstBooking.name
booking.email = firstBooking.email
booking.contact = firstBooking.contact
newbookings.push(booking)
end
newbookings.each do | booking |
booking.save
end
end
The question is, how to add a week to the date recursively. i.e add 7 days to the second booking and 14 to the 3rd and 21 to the 4rth etc until qty is zero.
I can do this in JavaScript with moment but have no clue where to even start in ruby. I would really appreciate any assistance.
You can use the each_with_index method from the Enumerable module together with the additions to the time management extensions included in Active Support. A simplified example would look like this:
bookings.each_with_index do |booking, i|
booking.start = firstBooking.start + i.weeks
end
The index i starts from 0, so the first booking will keep the original start date (adding 0 weeks). The rest of the weeks will start i weeks later than the original one.
EDIT
As Scott points out, whenever one of the elements is updated, all of them are. The key here is that there is not an array with n objects that can be updated independently, there is an array with n references to the same object, so a change made to one of them applies to all of them.
Probably you want to push a copy of the original object every time instead of pushing the original element:
quantity.to_i.times do
bookings.push(self.dup)
end
By doing so, there will effectively be n copies of the original object and you will be able to update each one of them separately.

building a simple search form in Rails?

I'm trying to build a simple search form in Ruby on Rails, my form is simple enough basically you select fields from a series of options and then all the events matching the fields are shown. The problem comes when I leave any field blank.
Here is the code responsible for filtering the parameters
Event.joins(:eventdates).joins(:categories).where
("eventdates.start_date = ? AND city = ? AND categories.name = ?",
params[:event][:date], params[:event][:city], params[:event][:category]).all
From what I get it's that it looks for events with any empty field, but since all of them have them not empty, it wont match unless all 3 are filled, another problem arises when I try to say, look events inside a range or array of dates, I'm clueless on how to pass multiple days into the search.
I'm pretty new to making search forms in general, so I don't even know if this is the best approach, also I'm trying to keep the searches without the need of a secialized model.
Below is probably what you are looking for. (Note: If all fields all blank, it shows all data in the events table linkable with eventdates and categories.)
events = Event.joins(:eventdates).joins(:categories)
if params[:event]
# includes below where condition to query only if params[:event][:date] has a value
events = events.where("eventdates.start_date = ?", params[:event][:date]) if params[:event][:date].present?
# includes below where condition to query only if params[:event][:city] has a value
events = events.where("city = ?", params[:event][:city]) if params[:event][:city].present?
# includes below where condition to query only if params[:event][:city] has a value
events = events.where("categories.name = ?", params[:event][:category]) if params[:event][:category].present?
end
To search using multiple days:
# params[:event][:dates] is expected to be array of dates.
# Below query gets converted into an 'IN' operation in SQL, something like "where eventdates.start_date IN ['date1', 'date2']"
events = events.where("eventdates.start_date = ?", params[:event][:dates]) if params[:event][:dates].present?
It will be more easy and optimised . If you use concern for filter data.
Make one concern in Model.
filterable.rb
module Filterable
extend ActiveSupport::Concern
module ClassMethods
def filter(filtering_params)
results = self.where(nil)
filtering_params.each do |key, value|
if column_type(key) == :date || column_type(key) ==
:datetime
results = results.where("DATE(#{column(key)}) = ?",
Date.strptime(value, "%m/%d/%Y")) if
value.present?
else
results = results.where("#{column(key)} Like ? ", "%#{value}%") if
value.present?
end
end
results
end
def resource_name
self.table_name
end
def column(key)
return key if key.split(".").count > 1
return "#{resource_name}.#{key}"
end
def column_type(key)
self.columns_hash[key].type
end
end
end
Include this concern in model file that you want to filter.
Model.rb
include Filterable
In your controller Add this methods
def search
#resources = Model.filter(class_search_params)
render 'index'
end
def class_search_params
params.slice(:id,:name) #Your field names
end
So, It is global solution. You dont need to use query for filter. just add this concern in your model file.
That's it.

How to get an array of objects after several ajax calls

In a project, I implement checkbox filters via ajax. In my controller I have an action:
def casinos_filters
providers_from_categories = Provider.joins(:categories).where('categories.id = ?', params[:category_id])
providers = Casino.joins(:providers).where('providers.id = ?', params[:provider_id])
providers_from_categories.each do |provider|
#filtered_casinos = provider.casinos
end
casinos_list = []
casinos_list << #filtered_casinos if params[:category_id]
casinos_list << providers if params[:provider_id]
render json: { casinos: casinos_list.uniq }, status: 200
end
As a result, I want the array casinos_list include the casinos' objects. Clicking on a category checkbox, I get the json. But when I click the next category checkbox, the json doesn't have previous results. I think it's due to the initializing an empty array in the action. Is there any way to initialize the array only once, not on every ajax call? Thanks.
HTTP requests are stateless. You either need to store them in client side (as Javascript models/collections) or on the server side to be able to collect them together.

Automatically Scheduling Conference

I'm trying to create a Ruby on Rails site that manages conferences. It should fill in time slots without any gaps in between. I've got it to the point where it fill in the the slots. But in most instances it leaves some time slots empty. I'm not able to find the flow in my logic.
app/services/conference_service.rb
class ConferenceService
def initialize(conference, temp_file)
self.first_track = conference.tracks.first
self.second_track = conference.tracks.last
self.file = temp_file
self.talks = []
end
def call
create_talks
set_track(1, 'Lunch')
set_track(2, 'Lunch')
set_track(1, 'Networking Event')
# set_track(2, 'Networking Event')
set_second_track_evening
end
private
def create_talks
file.read.split(/\n/).each do |line|
next if line.blank?
title = line.split(/\d|lightning/).first
length = line.scan(/\d+/).first
length = length.nil? ? 5 : length.to_i
talks << Talk.create(title: title, length: length)
end
end
attr_accessor :first_track, :second_track, :file, :talks
def set_track(track_number, track_portion)
track = track_number == 1 ? first_track : second_track
time = track_portion == 'Lunch' ? Time.zone.now.change(hour: 9) : Time.zone.now.change(hour: 13)
minutes = track_portion == 'Lunch' ? 180 : 240
talks.shuffle!
local_talks = []
n = 0
while local_talks.map(&:length).inject(0, &:+) < minutes
local_talks << talks[n]
n += 1
end
if local_talks.map(&:length).inject(0, &:+) == minutes
local_talks.each do |talk|
talk.start_time = time
track.talks << talk
time = time.advance(minutes: talk.length)
end
track.talks << Talk.create(title: track_portion, start_time: time, length: 60)
track.save
(0..local_talks.count - 1).each do |i|
talks.delete_at(i)
end
else
set_track(track_number, track_portion)
end
end
def set_second_track_evening
time = Time.zone.now.change(hour: 13)
talks.each do |talk|
talk.start_time = time
time = time.advance(minutes: talk.length)
end
second_track.talks << talks
second_track.talks << Talk.create(title: 'Networking Event', start_time: time.change(hour: 17), length: 60)
end
end
app/controllers/conference_controller.rb
def create
#conference = Conference.new(conference_params)
build_tracks
conference_service = ConferenceService.new(#conference, input_file)
conference_service.call
respond_to do |format|
if #conference.save
format.html { redirect_to #conference, notice: 'Conference was successfully created.' }
format.json { render :show, status: :created, location: #conference }
else
format.html { render :new }
format.json { render json: #conference.errors, status: :unprocessable_entity }
end
end
end
def input_file
params['conference']['input_file']
end
input file
Writing Fast Tests Against Enterprise Rails 60min
Overdoing it in Python 45min
Lua for the Masses 30min
Ruby Errors from Mismatched Gem Versions 45min
Common Ruby Errors 45min
Rails for Python Developers lightning
Communicating Over Distance 60min
Accounting-Driven Development 45min
Woah 30min
Sit Down and Write 30min
Pair Programming vs Noise 45min
Rails Magic 60min
Ruby on Rails: Why We Should Move On 60min
Clojure Ate Scala (on my project) 45min
Programming in the Boondocks of Seattle 30min
Ruby vs. Clojure for Back-End Development 30min
Ruby on Rails Legacy App Maintenance 60min
A World Without HackerNews 30min
User Interface CSS in Rails Apps 30min
error when calling set_track(2, 'Networking Event')
undefined method `length' for nil:NilClass #line 42
Recommend you do a few things before worrying about the algorithm:
Separate concerns / Single Responsibility. The code that parses the file should be independent from the code that runs the business logic, which should be independent from the code that saves to your database. Separating these things may seem unnecessary for simple logic (and may be), but is necessary as your app complexity grows.
Write tests. As you refactor your code, you're going to want to ensure it still works. Bonus: Writing code that you can test forces you to create interfaces that you can understand, which can make the code easier to understand!
Come up with a design first. Reading this code I have no idea what the intention of the sections are. One of my favorite ways to do this is to use Class, Responsibilities, Collaborators post cards (see https://en.wikipedia.org/wiki/Class-responsibility-collaboration_card and http://agilemodeling.com/artifacts/crcModel.htm).
It seems like you could break this code down into:
Parse input file into generic 'Talk' objects that have a length (in minutes) and a name. I would not have these be DB backed. If it's the same concept as an ActiveRecord model, we often name this a TalkDouble (or similar). I'd also recommend just using CSV here rather than your own custom (and hard to parse) format.
Schedule talk objects into tracks. It seems like you're trying to randomize the talks across two tracks, with some built-in lunch breaks (?). Whatever the desired behavior, this also doesn't need to use anything but plain old ruby objects. I've found it best to have the logic be stateless/idempotent and return a new object each time it's run as the result.
For example:
class TalkScheduler
def schedule(talks, number_of_tracks: 2)
# Logic goes here, returns an array of `Tracks`
# each with a set of talks.
tracks = build_tracks(number_of_tracks)
talks.each do |talk|
tracks.sample.add_talk(talk)
end
tracks
end
def build_tracks(number)
(0..number).times.map do { Track.new }
end
end
However, if you're looking for an algorithm that chooses "best fit" of available talks into open spaces, you're essentially trying to solve the Knapsack problem (https://en.wikipedia.org/wiki/Knapsack_problem). It may not become combinatorially hard due to the limited number of talk lengths (e.g. only 30, 45 and 60) but realize that you're slipping into challenging territory.
I'd also question the value to anyone of the ability to create conference with a random order of talks vs. just being able to organize them by hand.
In any case, you could handle solving the problem of determining a (random?) selection of talks in a given time-space with something like the following:
class Schedule
SLOT_LENGTH = 15
attr_accessor :start, :length, :talks
def initialize(start:, length:)
#start = start
#length = length
#slots = length / SLOT_LENGTH
#talks = []
end
def add_talk(talk)
talks.push(talk)
end
def slots_remaining
slots - talks.map(&:length).sum / SLOT_LENGTH
end
def can_fit?(talk)
talk.length / SLOT_LENGTH <= slots_remaining
end
end
class TalkScheduler
def schedule(talks, schedules)
unscheduled_talks = talks.dup.shuffle # Always dup, even if you don't shuffle
schedules.each do |schedule|
while(talks.any?)
index = unscheduled_talks.index{|t| schedule.can_fit?(t) }
break unless index
talk = unscheduled_talks.delete_at(index)
schedule.add_talk(talk)
end
end
end
end
I'd think a bit more about to model lunches, networking breaks, etc. before deciding to model them as talks or as something else, but using this type of pattern (simple ruby objects that store data being manipulated by NounVerber classes that contain the complex business logic) has been very helpful to me for simplifying handling complex workflows like what you're doing here.
Good luck!

howto create multiple records in a single form with same values

I am looking for a clean solution to create multiple database records from a single form, that all have the same values specified in the form. Only the ID should obviously be different.
I need this function to let the user create 100+ records at once as a kind of templating.
So ideally in the form the user can type a number for the count of records she/he would like to create with the filled in values.
Use an iterator. Example:
def create_many
count = params[:count].to_i
# count within reasonable limits, check if object will validate
if (1..100) === count && Object.new(params[:object]).valid?
count.times { Object.create(params[:object]) } # <= the iterator
redirect_to my_custom_view # <= custom 'show' view
else
render :text => "Couldn't do it." # <= failure message
end
end
This example expects two parameters, :object which contains resource attributes, and :count which specifies how many records to create.
You need a custom show view to handle getting and displaying all of the newly created records.

Resources