Slice/Map Ordered Hash - ruby-on-rails

I am writing a "Punch Clock" application for my office.. I am working on the controller for the "TIme Card" view which should list a users punches for a given week, and total DAILY then add the TOTAL for the week. I have figured out how to get the time diff between all of the punches with slice/map, my issue is that when I try to do this on the ordered hash (grouped by days) I get undefined method `created_at' for #, I know I must be missing some syntax somewhere, your help is greatly appreciated...
Here is my controller...
Note that if i call #in_out_lenghts on #punches, this works and gives me the total for the week, but #punches_days gives me an error, therefore I can not keep a running tally....
def mytimecard
#week = params[:week].to_s
if #week == "lastweek"
#punches = Punch.lastweek.where("user_id = ?", params[:user])
else
#punches = Punch.thisweek.where("user_id = ?", params[:user])
end
#punches_days = #punches.group_by { |t| t.created_at.beginning_of_day}
if #punches.count%2 == 0
#in_out_lengths = #punches_days.each_slice(2).map { |a|(a[1].created_at).round(15.minutes) - (a[0].created_at).round(15.minutes) }
#total = ((#in_out_lengths.inject(:+))/60/60)
else
#total = "Can Not Calculate, Odd Number of Punches"
end
respond_to do |format|
format.html # timecard.html.erb
format.json { render :json => #punches }
end
end

group_by will return a hash of days and punches.
{ :day_1 => [ :punch1, :punch2], :day_2 => [ :punch3, :punch4, :punch5 ] }
doing an each_slice and a map will result in some sort of array, but probably not what you meant.
You may have meant to count the number of punches and call something like this
Punch.lastweek.where("user_id = ?", params[:user]).group('date(created_at)')
This would have resulted in the date => punches_count format, at least with mysql.

Related

Too many checks for empty params. How to optimize queries to ActiveRecord in Rails5?

I'm doing checks for empty parameters before do the query.
There is only 1 check for params[:car_model_id]. I can imagine if I will add more checks for other params, then there will be a mess of if-else statements. It doesn't look nice and I think it can be optimized. But how? Here is the code of controller:
class CarsController < ApplicationController
def search
if params[:car_model_id].empty?
#cars = Car.where(
used: ActiveRecord::Type::Boolean.new.cast(params[:used]),
year: params[:year_from]..params[:year_to],
price: params[:price_from]..params[:price_to],
condition: params[:condition]
)
else
#cars = Car.where(
used: ActiveRecord::Type::Boolean.new.cast(params[:used]),
car_model_id: params[:car_model_id],
year: params[:year_from]..params[:year_to],
price: params[:price_from]..params[:price_to],
condition: params[:condition]
)
end
if #cars
render json: #cars
else
render json: #cars.errors, status: :unprocessable_entity
end
end
end
The trick would be to remove the blank values, do a little bit of pre-processing (and possibly validation) of the data, and then pass the params to the where clause.
To help with the processing of the date ranges, you can create a method that checks both dates are provided and are converted to a range:
def convert_to_range(start_date, end_date)
if start_date && end_date
price_from = Date.parse(price_from)
price_to = Date.parse(price_to)
price_from..price_to
end
rescue ArgumentError => e
# If you're code reaches here then the user has invalid date and you
# need to work out how to handle this.
end
Then your controller action could look something like this:
# select only the params that are need
car_params = params.slice(:car_model_id, :used, :year_from, :year_to, :price_from, :price_to, :condition)
# do some processing of the data
year_from = car_params.delete(:year_from).presence
year_to = car_params.delete(:year_to).presence
car_params[:price] = convert_to_range(year_from, year_to)
price_from = car_params.delete(:price_from).presence
price_to = car_params.delete(:price_to).presence
car_params[:price] = convert_to_range(price_from, price_to)
# select only params that are present
car_params = car_params.select {|k, v| v.present? }
# search for the cars
#cars = Car.where(car_params)
Also, I'm pretty sure that the used value will automatically get cast to boolean for you when its provided to the where.
Also, #cars is an ActiveRecord::Relation which does not have an errors method. Perhaps you mean to give different results based on whether there are any cars returned?
E.g: #cars.any? (or #cars.load.any? if you don't want to execute two queries to fetch the cars and check if cars exist)
Edit:
As mentioned by mu is too short you can also clean up your code by chaining where conditions and scopes. Scopes help to move functionality out of the controller and into the model which increases re-usability of functionality.
E.g.
class Car > ActiveRecord::Base
scope :year_between, ->(from, to) { where(year: from..to) }
scope :price_between, ->(from, to) { where(price: from..to) }
scope :used, ->(value = true) { where(used: used) }
end
Then in your controller:
# initial condition is all cars
cars = Cars.all
# refine results with params provided by user
cars = cars.where(car_model_id: params[:car_model_id]) if params[:car_model_id].present?
cars = cars.year_between(params[:year_from], params[:year_to])
cars = cars.price_between(params[:price_from], params[:price_to])
cars = cars.used(params[:used])
cars = cars.where(condition: params[:condition]) if params[:condition].present?
#cars = cars

Update database using a loop on Ruby On Rails

I'm trying to do a for each loop to update my database. I was using this piece of code to make the update:
def update
respond_to do |format|
#t_id = params[:t_id]
#t_order = params[:order]
#t_relation = TRelation.where('t_id' => #t_id)
#i = 0;
#t_order.each do |p|
#t_relation = TRelation.where('t_id = ? and
video_id = ?', #t_id, p[1][#i])
#i = #i + 1
#t_relation[0].t_order = #i
#t_relation[0].save
end
format.json { render :nothing => true, :status => 200, :content_type => 'text/html' }
end
end
It does not loop through; it goes through it one time and stops. I don't understand what's happening.
This is the content of params[:order]
params[:order] = {ActionController::Parameters} ActionController::Parameters (1 element)
'0' = Array (3 elements)
[0] = "7"
[1] = "5"
[2] = "3"
And if I make a #timeline_order.inspect I get this:
{"0"=>["7", "5", "3"]}
How can I loop through it? I have no idea
It iterates once because params[:order] only has one element. What you want to do is iterate on #t_order["0"], which has 3 elements.
Also, you should avoid all that logic within the respond_to. You can (should) define the variables outside of it.

Sort! seems to ignore custom attribute

I have this method to place 'The' at the end of a string if it is at the beginning of the string in Novel.class:
def sort_name
display_name = self.name
if display_name.match(/^the/i)
arr = display_name.split(/^the/i)
display_name = "#{arr[1]}, The"
end
display_name
I have this index method in NovelController:
def index
#novels = Novel.all
#novels.to_a.sort! { |a,b| a.sort_name.downcase <=> b.sort_name.downcase }
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #novels }
end
end
In the view I just display the sort_name. The sort_name is being displayed but the novels are still ordered by name. Does anybody see a flaw? Thanks.
The problem here is that sorted array is discarded.
#novels.to_a
This returns a temporary array (which isn't saved anywhere). That temp array is then sorted in-place and forgotten, because you don't have any references to it.
Solution: save it into a variable.
#novels = Novel.all.sort { |a,b| a.sort_name.downcase <=> b.sort_name.downcase }
Also, you have a bug in your sort_name code. It returns values like these:
# for name "The Yellow God"
display_name # => " Yellow God, The"

Nested ActiveRecords: Find many childrens of many parents

In my Rails 3.2 app a Connector has_many Incidents.
To get all incidents of a certain connector I can do this:
(In console)
c = Connector.find(1) # c.class is Connector(id: integer, name: string, ...
i = c.incidents.all # all good, lists incidents of c
But how can I get all incidents of many connectors?
c = Connector.find(1,2) # works fine, but c.class is Array
i = c.incidents.all #=> NoMethodError: undefined method `incidents' for #<Array:0x4cc15e0>
Should be easy! But I don't get it!
Here’s the complete code in my statistics_controller.rb
class StatisticsController < ApplicationController
def index
#connectors = Connector.scoped
if params['connector_tokens']
logger.debug "Following tokens are given: #{params['connector_tokens']}"
#connectors = #connectors.find_all_by_name(params[:connector_tokens].split(','))
end
#start_at = params[:start_at] || 4.weeks.ago.beginning_of_week
#end_at = params[:end_at] || Time.now
##time_line_data = Incident.time_line_data( #start_at, #end_at, 10) #=> That works, but doesn’t limit the result to given connectors
#time_line_data = #connectors.incidents.time_line_data( #start_at, #end_at, 10) #=> undefined method `incidents' for #<ActiveRecord::Relation:0x3f643c8>
respond_to do |format|
format.html # index.html.haml
end
end
end
Edit with reference to first 3 answers below:
Great! With code below I get an array with all incidents of given connectors.
c = Connector.find(1,2)
i = c.map(&:incidents.all).flatten
But idealy I'd like to get an Active Records object instead of the array, because I'd like to call where() on it as you can see in methode time_line_data below.
I could reach my goal with the array, but I would need to change the whole strategy...
This is my time_line_data() in Incidents Model models/incidents.rb
def self.time_line_data(start_at = 8.weeks.ago, end_at = Time.now, lim = 10)
total = {}
rickshaw = []
arr = []
inc = where(created_at: start_at.to_time.beginning_of_day..end_at.to_time.end_of_day)
# create a hash, number of incidents per day, with day as key
inc.each do |i|
if total[i.created_at.to_date].to_i > 0
total[i.created_at.to_date] += 1
else
total[i.created_at.to_date] = 1
end
end
# create a hash with all days in given timeframe, number of incidents per day, date as key and 0 as value if no incident is in database for this day
(start_at.to_date..end_at.to_date).each do |date|
js_timestamp = date.to_time.to_i
if total[date].to_i > 0
arr.push([js_timestamp, total[date]])
rickshaw.push({x: js_timestamp, y: total[date]})
else
arr.push([js_timestamp, 0])
rickshaw.push({x: js_timestamp, y: 0})
end
end
{ :start_at => start_at,
:end_at => end_at,
:series => rickshaw #arr
}
end
As you only seem to be interested in the time line data you can further expand the map examples given before e.g.:
#time_line_data = #connectors.map do |connector|
connector.incidents.map do |incident|
incident.time_line_data(#start_at, #end_at, 10)
end
end
This will map/collect all the return values of the time_line_data method call on all the incidents in the collection of connectors.
Ref:- map
c = Connector.find(1,2)
i = c.map(&:incidents.all).flatten

Ruby: compare hash to hash value trouble

I have such part of "ghost look like" code (but it so must be, as db is huge and have many tables):
def search_group
#search_trees = SearchTree.all
#designation = Designation.find(:all, :conditions => { :DES_ID => #search_trees.map(&:STR_DES_ID)})
#text = DesText.find(:all, :conditions => { :TEX_ID => #designation.map(&:DES_TEX_ID)})
#search_result = #text.find_all{|item| item.TEX_TEXT.include?(params[:search_group_text])}
#designation_back = #designation.find_all{|item| item.DES_TEX_ID == #search_result.TEX_ID}
#search_trees_back = #search_trees.find_all{|item| item.STR_DES_ID == #designation_back.DES_ID}
respond_to do |format|
format.html
end
end
I try to compare
#designation_back = #designation.find_all{|item| item.DES_TEX_ID == #search_result.TEX_ID}
but i get errors, something bad...undefined method `TEX_ID'. As i think, it's via i compare hash and hash in bad way... How can i do this?
#search_result = #text.find_all{|item| item.TEX_TEXT.include?(params[:search_group_text])}
#designation_back = #designation.find_all{|item| item.DES_TEX_ID == #search_result.TEX_ID}
it's because #search_result is an array and not an object where you can call that method on.
#search_results is an array. If you know it is returning just one result, you can do #search_results[0].Tex_Id, otherwise have to loop through for each value of #search_results.
try 'pry' gem to debug what results you are getting from each assignment.

Resources