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

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.

Related

Rails - Not hitting while loop when creating classes

I have a create method in Rails where I am trying to create multiple objects in a while loop. For some reason it doesn't seem to be hitting the while loop so no objects are being created. The code is below:
def create
#user = User.find(params[:participant][:user_id])
#activity = Activity.find(params[:activity_id])
weeks = #activity.weeks
i = 1
while i <= weeks do
puts "Test"
participant = Participant.new
participant.user_id = #user.id
participant.activity_id = #activity.id
participant.attended = false
participant.paid = false
participant.week = i
participant.save
i = i+1
end
redirect_to user_activities_path(#user, :id => #activity.id)
end
The form I am using to submit is working fine as I can see from the console, and the redirect_to method at the end is working, so it just seems to be missing the loop. If it helps, the value of weeks is 10. Any help would be greatly appreciated.
If multiple Test have been output, try participant.save!, i think the participant save might fail, like some column not valid, so no objects are being created.
Please check if activity record is get fetched. I think your 3rd statement should be as follow.
#activity = Activity.find(params[:participant][:activity_id])

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!

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

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.

Retrieving only unique records with multiple requests

I have this "heavy_rotation" filter I'm working on. Basically it grabs tracks from our database based on certain parameters (a mixture of listens_count, staff_pick, purchase_count, to name a few)
An xhr request is made to the filter_tracks controller action. In there I have a flag to check if it's "heavy_rotation". I will likely move this to the model (cos this controller is getting fat)... Anyway, how can I ensure (in a efficient way) to not have it pull the same records? I've considered an offset, but than I have to keep track of the offset for every query. Or maybe store track.id's to compare against for each query? Any ideas? I'm having trouble thinking of an elegant way to do this.
Maybe it should be noted that a limit of 14 is set via Javascript, and when a user hits "view more" to paginate, it sends another request to filter_tracks.
Any help appreciated! Thanks!
def filter_tracks
params[:limit] ||= 50
params[:offset] ||= 0
params[:order] ||= 'heavy_rotation'
# heavy rotation filter flag
heavy_rotation ||= (params[:order] == 'heavy_rotation')
#result_offset = params[:offset]
#tracks = Track.ready.with_artist
params[:order] = "tracks.#{params[:order]}" unless heavy_rotation
if params[:order]
order = params[:order]
order.match(/artist.*/){|m|
params[:order] = params[:order].sub /tracks\./, ''
}
order.match(/title.*/){|m|
params[:order] = params[:order].sub /tracks.(title)(.*)/i, 'LOWER(\1)\2'
}
end
searched = params[:q] && params[:q][:search].present?
#tracks = parse_params(params[:q], #tracks)
#tracks = #tracks.offset(params[:offset])
#result_count = #tracks.count
#tracks = #tracks.order(params[:order], 'tracks.updated_at DESC').limit(params[:limit]) unless heavy_rotation
# structure heavy rotation results
if heavy_rotation
puts "*" * 300
week_ago = Time.now - 7.days
two_weeks_ago = Time.now - 14.days
three_months_ago = Time.now - 3.months
# mix in top licensed tracks within last 3 months
t = Track.top_licensed
tracks_top_licensed = t.where(
"tracks.updated_at >= :top",
top: three_months_ago).limit(5)
# mix top listened to tracks within last two weeks
tracks_top_listens = #tracks.order('tracks.listens_count DESC').where(
"tracks.updated_at >= :top",
top: two_weeks_ago)
.limit(3)
# mix top downloaded tracks within last two weeks
tracks_top_downloaded = #tracks.order("tracks.downloads_count DESC").where(
"tracks.updated_at >= :top",
top: two_weeks_ago)
.limit(2)
# mix in 25% of staff picks added within 3 months
tracks_staff_picks = Track.ready.staff_picks.
includes(:artist).order("tracks.created_at DESC").where(
"tracks.updated_at >= :top",
top: three_months_ago)
.limit(4)
#tracks = tracks_top_licensed + tracks_top_listens + tracks_top_downloaded + tracks_staff_picks
end
render partial: "shared/results"
end
I think seeking an "elegant" solution is going to yield many diverse opinions, so I'll offer one approach and my reasoning. In my design decision, I feel that in this case it's optimal and elegant to enforce uniqueness on query intersections by filtering the returned record objects instead of trying to restrict the query to only yield unique results. As for getting contiguous results for pagination, on the other hand, I would store offsets from each query and use it as the starting point for the next query using instance variables or sessions, depending on how the data needs to be persisted.
Here's a gist to my refactored version of your code with a solution implemented and comments explaining why I chose to use certain logic or data structures: https://gist.github.com/femmestem/2b539abe92e9813c02da
#filter_tracks holds a hash map #tracks_offset which the other methods can access and update; each of the query methods holds the responsibility of adding its own offset key to #tracks_offset.
#filter_tracks also holds a collection of track id's for tracks that already appear in the results.
If you need persistence, make #tracks_offset and #track_ids sessions/cookies instead of instance variables. The logic should be the same. If you use sessions to store the offsets and id's from results, remember to clear them when your user is done interacting with this feature.
See below. Note, I refactored your #filter_tracks method to separate the responsibilities into 9 different methods: #filter_tracks, #heavy_rotation, #order_by_params, #heavy_rotation?, #validate_and_return_top_results, and #tracks_top_licensed... #tracks_top_<whatever>. This will make my notes easier to follow and your code more maintainable.
def filter_tracks
# Does this need to be so high when JavaScript limits display to 14?
#limit ||= 50
#tracks_offset ||= {}
#tracks_offset[:default] ||= 0
#result_track_ids ||= []
#order ||= params[:order] || 'heavy_rotation'
tracks = Track.ready.with_artist
tracks = parse_params(params[:q], tracks)
#result_count = tracks.count
# Checks for heavy_rotation filter flag
if heavy_rotation? #order
#tracks = heavy_rotation
else
#tracks = order_by_params
end
render partial: "shared/results"
end
All #heavy_rotation does is call the various query methods. This makes it easy to add, modify, or delete any one of the query methods as criteria changes without affecting any other method.
def heavy_rotation
week_ago = Time.now - 7.days
two_weeks_ago = Time.now - 14.days
three_months_ago = Time.now - 3.months
tracks_top_licensed(date_range: three_months_ago, max_results: 5) +
tracks_top_listens(date_range: two_weeks_ago, max_results: 3) +
tracks_top_downloaded(date_range: two_weeks_ago, max_results: 2) +
tracks_staff_picks(date_range: three_months_ago, max_results: 4)
end
Here's what one of the query methods looks like. They're all basically the same, but with custom SQL/ORM queries. You'll notice that I'm not setting the :limit parameter to the number of results that I want the query method to return. This would create a problem if one of the records returned is duplicated by another query method, like if the same track was returned by staff_picks and top_downloaded. Then I would have to make an additional query to get another record. That's not a wrong decision, just one I didn't decide to do.
def tracks_top_licensed(args = {})
args = #default.merge args
max = args[:max_results]
date_range = args[:date_range]
# Adds own offset key to #filter_tracks hash map => #tracks_offset
#tracks_offset[:top_licensed] ||= 0
unfiltered_results = Track.top_licensed
.where("tracks.updated_at >= :date_range", date_range: date_range)
.limit(#limit)
.offset(#tracks_offset[:top_licensed])
top_tracks = validate_and_return_top_results(unfiltered_results, max)
# Add offset of your most recent query to the cumulative offset
# so triggering 'view more'/pagination returns contiguous results
#tracks_offset[:top_licensed] += top_tracks[:offset]
top_tracks[:top_results]
end
In each query method, I'm cleaning the record objects through a custom method #validate_and_return_top_results. My validator checks through the record objects for duplicates against the #track_ids collection in its ancestor method #filter_tracks. It then returns the number of records specified by its caller.
def validate_and_return_top_results(collection, max = 1)
top_results = []
i = 0 # offset incrementer
until top_results.count >= max do
# Checks if track has already appeared in the results
unless #result_track_ids.include? collection[i].id
# this will be returned to the caller
top_results << collection[i]
# this is the point of reference to validate your query method results
#result_track_ids << collection[i].id
end
i += 1
end
{ top_results: top_results, offset: i }
end

A loop in a loop to fill an array?

I am building a time registration program. Users can work on a project, and I want to display in a chart how many hours each user worked on a project, let's say, each month. The chart plugin works like this:
first_serie = OpenFlashChartLazy::Serie.new(
[["2008-1",100],["2008-2",120],["2008-3",130]],
{:title=>"name_of_user1",:start_date=>Time.mktime(2008,1,1),:items=>8})
This adds a new line in the graph.
My question is how can I loop through all my users and for each fill a new series with data from the database?
I have no idea how you generate all the data for Serie.new, but you can get started using this:
#series = []
users = User.find(:all)
users.each do |user|
#series << OpenFlashChartLazy::Serie.new(blah, blah, blah)
end
This will add all of the added Serie objects to an array.
As a follow up to Pesto would be nicer to use inject.
#series = User.all.inject([]) do |mem, user|
mem << OpenFlashChartLazy::Serie.new(user.foo, user.bar, user.foobarbob)
end
Same code, just doesnt have a #series = []

Resources