Benchmarking controller + view together, many times in one go - ruby-on-rails

I am learning how to benchmark two implementations in the controller/view. They are doing th e same thing, but one is done in view and another in controller. The code is shown below. My questions are:
is it possible to measure the taken for the same action to render 100 times in one go?
is my current benchmarking correctly measuring the combination of view + controller times?
is there any better way to do this?
```
def sort_in_view
self.class.benchmark("$sort in view") do
#regions = Region.all
respond_to do |format|
format.html
end
end
end
def sort_in_controller
self.class.benchmark("$sort in controller") do
#regions = {}
Region.all.each do |r|
#regions[r] = r.countries.order_by_name
end
respond_to do |format|
format.html
end
end
end

In order to run each case many times to get a more accurate average, I used Apache Benchmark at the end.
ab -c 1 -n 100 http://example.com/regions
This will run the request 100 times (with concurrency of 1), and give you a detailed summary of the mean and percentiles. I benchmark against my local machine, and it saves time since no browser rendering is required.

see benchmaek results,just see your Rails log

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.

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!

Speed up rake task by using typhoeus

So i stumbled across this: https://github.com/typhoeus/typhoeus
I'm wondering if this is what i need to speed up my rake task
Event.all.each do |row|
begin
url = urlhere + row.first + row.second
doc = Nokogiri::HTML(open(url))
doc.css('.table__row--event').each do |tablerow|
table = tablerow.css('.table__cell__body--location').css('h4').text
next unless table == row.eventvenuename
tablerow.css('.table__cell__body--availability').each do |button|
buttonurl = button.css('a')[0]['href']
if buttonurl.include? '/checkout/external'
else
row.update(row: buttonurl)
end
end
end
rescue Faraday::ConnectionFailed
puts "connection failed"
next
end
end
I'm wondering if this would speed it up, Or because i'm doing a .each it wouldn't?
If it would could you provide an example?
Sam
If you set up Typhoeus::Hydra to run parallel requests, you might be able to speed up your code, assuming that the Kernel#open calls are what's slowing you down. Before you optimize, you might want to run benchmarks to validate this assumption.
If it is true, and parallel requests would speed it up, you would need to restructure your code to load events in batches, build a queue of parallel requests for each batch, and then handle them after they execute. Here's some sketch code.
class YourBatchProcessingClass
def initialize(batch_size: 200)
#batch_size = batch_size
#hydra = Typhoeus::Hydra.new(max_concurrency: #batch_size)
end
def perform
# Get an array of records
Event.find_in_batches(batch_size: #batch_size) do |batch|
# Store all the requests so we can access their responses later.
requests = batch.map do |record|
request = Typhoeus::Request.new(your_url_build_logic(record))
#hydra.queue request
request
end
#hydra.run # Run requests in parallel
# Process responses from each request
requests.each do |request|
your_response_processing(request.response.body)
end
end
rescue WhateverError => e
puts e.message
end
private
def your_url_build_logic(event)
# TODO
end
def your_response_processing(response_body)
# TODO
end
end
# Run the service by calling this in your Rake task definition
YourBatchProcessingClass.new.perform
Ruby can be used for pure scripting, but it functions best as an object-oriented language. Decomposing your processing work into clear methods can help clarify your code and help you catch things like Tom Lord mentioned in the comments on your question. Also, instead of wrapping your whole script in a begin..rescue block, you can use method-level rescues as in #perform above, or just wrap #hydra.run.
As a note, .all.each is a memory hog, and is thus considered a bad solution to iterating over records: .all loads all of the records into memory before iterating over them with .each. To save memory, it's better to use .find_each or .find_in_batches, depending on your use case. See: http://api.rubyonrails.org/classes/ActiveRecord/Batches.html

Update in Ruby model method not feeding through controller

I hope someone can help. I suspect this is an embarrassingly newbie question, but then, I'm embarassingly new. I've tried everywhere I can think of for an answer to this and tried every permutation I can think of.
Task:
I'm working through an example in a Ruby tutorial that manages a online shopping cart. The task is to create a button next to each item displayed in the cart, enabling you to decrement the amount of a single item by 1.
Problem:
When I click on the "Decrement" button in my view next to an item with a quantity of one, it disappears from the cart, as expected. But when I click on the button next to an item with a quantity of more than one, nothing happens.
Code
View:
<td><%= button_to("Decrement", decrement_line_item_path(line_item), method: :post ) %>
</td>
(I have set a member route in routes.rb so that it accesses the decrement action on "POST")
Controller:
def decrement
#cart = set_cart
#line_item = #cart.line_items.find_by(params[:id])
#line_item = #cart.decrement_line_item_quantity(#line_item.id)
respond_to do |format|
format.html {redirect_to store_url}
end
end
(there's a set_cart method in a concerns file that just gets or sets the cart based on a session, and I know that works).
Model:
def decrement_line_item_quantity(item_id)
item_to_decrement = line_items.find_by(id: item_id)
if item_to_decrement.quantity > 1
item_to_decrement.quantity -= 1
else
item_to_decrement.destroy
end
item_to_decrement
end
My debugger says that:
item_to_decrement.quantity decreases in the model method, as expected
#line_item.quantity is also decreased in the controller method, as expected
But the value for the line item's quantity in the actual cart appears to be unchanged in the controller method. I check #cart.line_items.find_by(params[:id]).quantity in my debugger after returning from the model method, and it hasn't changed.
This means that when I come to render my cart later, the quantity of an item with multiple units is not decreased, and so nothing happens.
Can someone tell me what I missed? This is the very first time I've used Stack Overflow, so I'm still learning. Have read the guidelines, and I hope I'm following them appropriately. Apologies for any transgressions and thanks so much in advance.
You have to save the record after you decrement the quantity, otherwise the change in the quantity will not get persisted to the database:
def decrement_line_item_quantity(item_id)
item_to_decrement = line_items.find_by(id: item_id)
if item_to_decrement.quantity > 1
item_to_decrement.quantity -= 1
item_to_decrement.save
else
item_to_decrement.destroy
end
item_to_decrement
end

nested loop in show at controller in Ruby optimization

#release = Release.find(params[:id])
#release_cycles=#release.cycles
#release_cycles=Cycle.find_by_sql("select * from cycles where release_id=#{params[:id]}")
current_page=params[:page]?Integer(params[:page]):1
#release_cycles = #release_cycles.paginate(:page=>params[:page],:per_page=>5)
release_ics=#release.ics
puts "params[releases==]==#{params[:releases]}"
releases=params[:releases].to_i
release1=(releases>0)?Release.find(params[:releases]):nil
puts "release1==#{release1}"
#non_ics=(release1!=nil)?(release1.ics):Ic.active
#non_members=[]
#non_ics.each do |non_ic|
check=1
release_ics.each do |release_ic|
if non_ic==release_ic
check=0
puts "inside ics comparison if"
end
end
if check==1
puts "inside if ! in release_only"
#non_members << non_ic
puts "#ics==#{#non_members}"
end
end
...
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #release }
end
end
The commented block of code at the end is eating up runtime like crazy (takes about 20-30 seconds to load) I think I have an idea on how to optimize this but I would like a third person thought on how to optimize the code to make it go faster
Your entire top section of the code can be replaced by 4 lines of code:
#release = Release.find(params[:id])
#release_cycles = #release.cycles.paginate(:page=> params[:page].presence || 1,
:per_page=>5)
#non_ics= params[:releases].present? ? Release.find(params[:releases]).ics :
Ic.active
#non_members = #non_ics - #release.ics
Apart from code that can be improved you are loading all the releases in to memory and paginating the result set in ruby memory space. Which can slow your process down if you have a large number of cycles for each release.
I calculated the intersection between the two arrays in the last line using Ruby. If the array size is big I would use SQL for that.

Resources