I need to find the average of multiple columns based on a params search across several tables in my VPC controller, but i'm having trouble displaying the average results in the view and also the search is very slow to read from the database. Wondering the best method to do below would be. Would it be best to do the averages in the model? (not sure how this is done though)
Models
class Vpc < ActiveRecord::Base
has_many :results
end
class Result < ActiveRecord::Base
attr_accessible :trial_id, :variety_id, :year, :lint, :turnout, :length_decimal, :length_imperial, :strength, :uniformity, :micronaire, :manual_class
belongs_to :trial, :primary_key => 'trial_id'
belongs_to :variety, :primary_key => 'variety_id'
belongs_to :vpc
has_many :sites, :through => :trial
has_many :growers, :through => :trial
has_many :regions, :through => :sites
end
Controller
class VpcController < ApplicationController
add_breadcrumb "Home", :root_url
add_breadcrumb "Variety Performance Comparison", :vpc_index_path
def index
all = Result.select(:variety_id)
#variety = Variety.where(:variety_id => all).order('variety_name DESC')
#years = Result.select('DISTINCT year')
#regions = Region.all
#irrigations = Trial.select('DISTINCT irrigated').order('irrigated ASC')
end
def search
if params[:variety_one] != params[:variety_two]
#comparison = Result.group('trials.trial_id').having('COUNT(*) = 2').where(variety_id: [params[:variety_one], params[:variety_two]]).
joins(:trial).where('trials.irrigated' => params[:irrigated], 'year' => params[:year]).joins(:regions).where('sites.region_id' => params[:regions])
#vone = #comparison.where('variety_id = ?', params[:variety_one]).select('avg(lint) AS lintone')
#vtwo = #comparison.where('variety_id = ?', params[:variety_two]).select('avg(lint) as linttwo')
#count = #comparison.count('DISTINCT results.trial_id')
#years = #comparison.where('results.year' => params[:year]).select('DISTINCT results.year')
#region = #comparison.where('sites.region_id' => params[:regions]).joins(:regions).group('regions.region_id').select("DISTINCT regions.name")
else
redirect_to vpc_index_url, notice: "Can't compare the same variety"
end
#variety_one = Variety.where('variety_id = ?', params[:variety_one]).group('variety_name')
#variety_two = Variety.where('variety_id = ?', params[:variety_two]).group('variety_name')
add_breadcrumb "Results"
end
end
View Results
<h2>VPC</h2>
<p>We found <%= #count.count %> trials that matched your options, spanning <%= #years.length %> years (<%= #years.map{|y| y.year}.join(", ") %>) and <%= #region.length %> regions (<%= #region.map{|r| r.trial.site.region.name}.join(", ") %>).</p>
<table class="table">
<th></th>
<% #variety_one.each do |v| %>
<th><%= v.variety_name %></th>
<% end %>
<% #variety_two.each do |v| %>
<th><%= v.variety_name %></th>
<% end %>
<th>Difference</th>
<tr>
<td>Yield (bales/ha)</td>
<td><%= "%.2f" % (#vone.lintone/227) unless #vone.blank? %></td>
<td><%= "%.2f" % (#vtwo.lintwo/227) unless #vtwo.blank? %></td>
<td><%= "%.2f" % ((#vone.lintone/227) - (#vtwo.lintwo/227)) unless #lintone.blank? %></td>
</tr>
</tr>
</table>
<hr>
<div class="accordion" id="accordion2">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseOne">
<b>Overview of results</b> <span class="pull-right"><i class="icon-chevron-down"></i></span>
</a>
</div>
<div id="collapseOne" class="accordion-body collapse">
<div class="accordion-inner">
<table class="table">
<th>Year</th>
<th>Site</th>
<th>Region</th>
<th>Grower</th>
<% #comparison.each do |v| %>
<tr>
<td><%= link_to v.trial.year, trial_trials_path(trial_id: v.trial_id) %></td>
<td><%= link_to v.trial.site.site_name, trial_trials_path(trial_id: v.trial_id) unless v.trial.site.blank? %></td>
<td><%= link_to v.trial.site.region.name, trial_trials_path(trial_id: v.trial_id) unless v.trial.site.blank? %></td>
<td><%= link_to v.trial.grower.full_name, trial_trials_path(trial_id: v.trial_id) unless v.trial.grower.blank? %></td>
</tr>
<% end %>
</table>
</div>
</div>
</div>
SQL Error
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AS lintone) AS count_avg_lint_as_lintone, avg(lint) AS lintone, trials.trial_id ' at line 1: SELECT COUNT(avg(lint) AS lintone) AS count_avg_lint_as_lintone, avg(lint) AS lintone, trials.trial_id AS trials_trial_id FROM `results` INNER JOIN `trials` ON `trials`.`trial_id` = `results`.`trial_id` INNER JOIN `trials` `trials_results_join` ON `trials_results_join`.`trial_id` = `results`.`trial_id` INNER JOIN `sites` ON `sites`.`site_id` = `trials_results_join`.`site_id` INNER JOIN `regions` ON `regions`.`region_id` = `sites`.`region_id` WHERE `results`.`variety_id` IN (2300, 2255) AND `trials`.`irrigated` IN (0, 1, 2) AND `results`.`year` IN (2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013) AND `sites`.`region_id` IN (1, 2, 3, 4, 5, 6, 7, 8) AND (variety_id = '2300') GROUP BY trials.trial_id HAVING COUNT(*) = 2
Looking at your massive controller, I would suggest the following:
#comparison = Result.group('trials.trial_id').having('COUNT(*) = 2').where(variety_id: [params[:variety_one], params[:variety_two]]).
joins(:trial).where('trials.irrigated' => params[:irrigated], 'year' => params[:year]).joins(:regions).where('sites.region_id' => params[:regions])
#count = #comparison.count('DISTINCT results.trial_id')
#years = #comparison.where('results.year' => params[:year]).select('DISTINCT results.year')
#region = #comparison.where('sites.region_id' => params[:regions]).joins(:regions).group('regions.region_id').select("DISTINCT regions.name")
Keep that chunk the same but create a migration to add indexes to increase the performance.
You should not call average so many times but instead do a select on the values you want
#vone = #comparison.where('variety_id = ?', params[:variety_one]).select(avg(lint) as lint, avg...
#vtwo = #comparison.where('variety_id = ?', params[:variety_two]).select(avg(lint) as lint, avg...)
Now in your views you can just get the needed attributes.
After that, have a look at the logs to see if there are any N+1 you can reduce.
Related
I am using filterrific gem to add filters in my app. I have parents table and children table. On the children_list page which displays list of all the children with their firstname and their parent's firstname. The issue I am facing is in the search query I want to add the parent.firstname search as well for filterrific. I tried adding a join as below:-
num_or_conds = 2
joins(child: :parent).where(
terms.map { |term|
"(LOWER(children.firstname) LIKE ?) OR (LOWER(parents.firstname) LIKE ?) "
But this didnt do the job. Any idea how this can be achieved.
parent.rb
has_many :children
child.rb
belongs_to :parent
filterrific(
available_filters: [
:search_query,
:with_clinic_date
]
)
scope :search_query, lambda { |query|
return nil if query.blank?
terms = query.downcase.split(/\s+/)
terms = terms.map { |e|
(e.gsub('*', '%') + '%').gsub(/%+/, '%')
}
num_or_conds = 2
where(
terms.map { |term|
"(LOWER(children.firstname) LIKE ?) OR (LOWER(parents.firstname) LIKE ?)"
}.join(' AND '),
*terms.map { |e| [e] * num_or_conds }.flatten
)
}
scope :with_clinic_date, lambda { |ref_date|
where('children.clinic_date = ?', ref_date)
}
end
_children.html.erb
<h1>Children</h1>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Parent First Name</th>
<th>Child firstname</th>
</tr>
</thead>
<tbody>
<% #children.each do |child| %>
<tr>
<td><%=child.parent.firstname %></td>
<td><%=child.firstname %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
children-list.html.erb
<%= form_for_filterrific #filterrific do |f| %>
<div class="row">
<div class="col-sm-3">
Search
<%= f.text_field(
:search_query,
class: 'filterrific-periodically-observed form-control'
) %>
</div>
<div class="col-sm-3">
Request Date
<%= f.date_field(:with_clinic_date, class: 'js-datepicker form-control') %>
</div>
<div class="col-sm-3">
<br>
<%= link_to(
'Reset filters',
reset_filterrific_url,
) %>
</div>
</div>
<%= render_filterrific_spinner %>
<% end %>
<%= render( partial: 'children/children_list') %>
children.js.erb
<% js = escape_javascript(
render(partial: 'children/children_list')
) %>
$("#filterrific_results").html("<%= js %>");
AFAIK, you can't filter two separate classes on the same page. It will use the last defined filterrific instance. When I ran into this problem, I used remote forms with custom action/routes
# routes.rb
resources :parent do
get :filter_parents
resources: children do
get :filter_children
end
end
And then the controllers..
# parents_controller.rb
def index
parents_filter # this would be a helper method running your filter queries
end
def filter_parents
parents_filter # this would be a helper method running your filter queries
end
The children's controller would look similar, just different named helper method/custom action.
And then use a partial for the table. Target the table's container, and use a filter_parents.js.erb and filter_childrens.js.erb file
$('#parents-table').html('<%= escape_javascript render 'path/to/partial'%>')
// same in childrens.js.erb, just target the appropriate table
Im currently working in an Rails 5 application where you can search for a first name or last name and records of the customers of that account would be displayed. However I am getting a Nil object return from search algorithm.
customers_controller:
class CustomersController < ApplicationController
def index
if params[:keywords].present?
#keywords = params[:keywords]
customer_search_term = CustomerSearchTerm.new(#keywords)
#customer = Customer.where(
customer_search_term.where_clause,
customer_search_term.where_args).
order(customer_search_term.order)
else
#customers = []
end
end
end
As you can see if there is no records found is suppose to return an empty array but is returning a Nil object.
customers/index.html.erb
[![<header>
<h1 class="h2">Customer Search</h1>
</header>
<section class="search-form">
<%= form_for :customers, method: :get do |f| %>
<div class="input-group input-group-lg">
<%= label_tag :keywords, nil, class: "sr-only" %>
<%= text_field_tag :keywords, nil,
placeholder: "First Name, Last Name or Email Address",
class: "form-control input-lg" %>
<span class="input-group-btn">
<%= submit_tag "Find Customers", class: "btn btn-primary btn-lg" %>
</span>
</div>
<% end %>
</section>
<section class="search-results">
<header>
<h1 class="h3">Results</h1>
</header>
<table class="table table-striped">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Joined</th>
</tr>
</thead>
<tbody>
<% #customers.each do |customer| %>
<tr>
<td><%= customer.first_name %></td>
<td><%= customer.last_name %></td>
<td><%= customer.email %></td>
<td><%= l customer.created_at.to_date %></td>
</tr>
<% end %>
</tbody>
</table>
</section>][1]][1]
The first thing you should understand is that instance variables return nil if they haven't been set. If you say #fake_var == nil it will be true if you never defined #fake_var before this. You can contrast this with regular local variables, which will raise a NoMethodError if you try and use them before they're defined. For example, puts(fake_var) will raise a NoMethodError for fake_var.
Now look at your template. No matter what it will loop over #customers. If #customers has not been set, you'll see a NoMethodError because you can't call each on nil.
Finally, look at your controller action:
def index
if params[:keywords].present?
#keywords = params[:keywords]
customer_search_term = CustomerSearchTerm.new(#keywords)
#customer = Customer.where(
customer_search_term.where_clause,
customer_search_term.where_args).
order(customer_search_term.order)
else
#customers = []
end
end
Specifically the case when params[:keywords].present?. You never set #customers in this case so it will be nil when the template tries to access it.
I think if you simply replaced #customer = with #customers = it would solve your problem.
you can force it to return array using #to_a which converts nil to empty array
def index
return [] unless params[:keywords]
#keywords = params[:keywords]
customer_search_term = CustomerSearchTerm.new(#keywords)
#customer = Customer.where(
customer_search_term.where_clause,
customer_search_term.where_args).
order(customer_search_term.order
).to_a
end
https://apidock.com/ruby/Array/to_a
I am trying to update a Rails 2.3 application to a newer Rails version(4/5).
I have there a method that prints a html table using a list as input, and the caller can customize the display of the rows. I also searched some existing gems that do something similar, but they don't have all the requirements I need. So I have to make this work. The code is
def model_table_2(collection, headers, options = {}, &proc)
options.reverse_merge!({
:id => nil,
:class => nil,
:style => nil,
:placeholder => 'Empty',
:caption => nil,
:summary => nil,
:footer => nil
})
placeholder_unless !collection.empty?, options[:placeholder] do
html_opt = options.slice(:id, :class, :style, :summary)
content_tag(:table, html_opt) do
table_sections = []
table_sections << content_tag(:caption, options[:caption]).to_s if options[:caption]
table_sections << content_tag(:thead,
content_tag(:tr,
headers.collect { |h|
concat(content_tag(:th, h))
}
)
)
if options[:footer]
table_sections << content_tag(:tfoot,
content_tag(:tr, content_tag(:th, concat(options[:footer]), :colspan => headers.size))
)
end
table_sections << content_tag(:tbody,
collection.each_with_index.collect do |row, row_index|
concat(
proc.call(row, cycle('odd', 'even'), row_index)
)
end.join
)
table_sections.join
end
end
end
def placeholder(message = t('general.empty'), options = {}, &proc)
# set default options
o = { :class => 'placeholder', :tag => 'p' }.merge(options)
# wrap the results of the supplied block, or just print out the message
if proc
t = o.delete(:tag)
concat tag(t, o, true), proc.binding
yield
concat "</#{t}>", proc.binding
else
content_tag o.delete(:tag), message, o
end
end
def placeholder_unless(condition, *args, &proc)
condition ? proc.call : concat(placeholder(args), proc.binding)
end
In the view file I call it like this:
<% table_cols = ["No.", "Name"] %>
<% obj_list = [{active: true, name: 'First'}, {active: true, name: 'Second'}, {active: false, name: 'Last'}, nil] %>
<%= model_table_2(obj_list, table_cols, {:class=>'table table-bordered', :caption=>'Model Table Test', :footer=>'The Footer'}) do |record, klass, row_index| -%>
<% if !record.nil? then %>
<% content_tag :tr, :class => klass + (record[:active] ? '' : ' text-muted') do -%>
<td><%= row_index+1 -%></td>
<td><%= record[:name] %></td>
<% end %>
<% else %>
<% content_tag :tr, :class => klass do -%>
<td style="text-align:center;">*</td>
<td>render form</td>
<% end %>
<% end %>
<% end %>
But the output is not how I would expect:
<table class="table table-bordered">
<th>No.</th>
<th>Name</th>
The Footer
<tr class="even">
<td>1</td>
<td>First</td>
</tr>
<tr class="odd">
<td>2</td>
<td>Second</td>
</tr>
<tr class="even text-muted">
<td>3</td>
<td>Last</td>
</tr>
<tr class="odd">
<td>*</td>
<td>render form</td>
</tr>
</table>
As you can see, some of the tags are missing, like caption, thead, tbody, tfoot. I guess it's because the content_tag calls are nested. I tried before without the table_sections array, but it didn't work either.
Also, I have an error when the list is empty, and the code goes to the placeholder... methods.
It's a weird quirk of content_tag but if you nest them you need to use a concat on each return in the inner tags. Otherwise, you just get the last returned string and the inner tags just disappear into the ether. Sadly, in my experience, I've found complex nesting isn't worth the effort of moving into a helper method.
Perhaps, a better approach would be to DRY up the html with a decorator pattern, rails partials, or using something like the cells gem.
I'm trying to submit multiple entries into the database from an array. So, I create multiple new entries and store them in an array. In the view, I have a form of checkboxes for the user to check which ones he/she wants to add to the database. Upon clicking submit, I would like to add each of these forms to the database all in on shot. Here is the code:
Controller:
class EventsBoysController < ApplicationController
def new
#season = find_season
#meet = find_meet(#season)
#athletes_boys = current_coach.athletes.where(boy: true)
#events_boys = []
#athletes_boys.each do |athlete|
#events_boys << #meet.events_boys.new(:athlete_id => athlete.id)
end
#events = ["400 IH", "100", "1600", "400", "110 HH", "800", "3200", "200"]
end
def create
debugger
#season = find_season
#meet = find_meet(#season)
#events_boys = #meet.events_boys.create(events_boy_permit)
# debugger
if #events_boys.save
redirect_to #events_boys, notice: 'Season was successfully created.'
else
render action: "new"
end
end
private
def find_season
Season.find(params[:season_id])
end
def find_meet season
season.meets.find(params[:meet_id])
end
def events_boy_permit
params.require(:events_boy).permit(:season_id, :meet_id, :athlete_id, :boys_400_m_im, :boys_1600_m, :boys_400_m, :boys_110_m_hh, :boys_800_m, :boys_3200_m, :boys_200_m, :boys_100_m, :time_400_m_im, :time_1600_m, :time_400_m, :time_110_m_hh, :time_800_m, :time_3200_m, :time_200_m, :time_100_m, :place_400_m_im, :place_1600_m, :place_400_m, :place_110_m_hh, :place_800_m, :place_3200_m, :place_200_m, :place_100_m)
end
end
View:
<h1><%= "Create new events for the boys for #{#meet.name}" %></h1>
<table id="events-table">
<tr>
<th></th>
<% #events.each do |event| %>
<th><%= event %></th>
<% end %>
</tr>
<% #events_boys.each do |event| %>
<tr>
<td><%= Athlete.find_by_id(event.athlete_id).name %></td>
<%= form_for [#season, #meet, event], :html => { :mulitpart => true } do |f| %>
<td><%= f.check_box :boys_400_m_im %></td>
<td><%= f.check_box :boys_100_m %></td>
<td><%= f.check_box :boys_1600_m %></td>
<td><%= f.check_box :boys_400_m %></td>
<td><%= f.check_box :boys_110_m_hh %></td>
<td><%= f.check_box :boys_800_m %></td>
<td><%= f.check_box :boys_3200_m %></td>
<td><%= f.check_box :boys_200_m %></td>
<td><%= f.submit "Submit", :class => 'btn' %></td>
<% end %>
</tr>
<% end %>
</table>
<script type="text/javascript">
var athletes = new Array();
var arrayInAthletes = new Array();
element = document.getElementById("events-table");
var athletesArray = element.children[0].children
for (i=1; i < athletesArray.length; i++) {
var rowArray = athletesArray[i].children;
arrayInAthletes = [];
arrayInAthletes[0] = rowArray[0].innerText;
var sum = 0;
for (j=1; j < rowArray.length; j++) {
var checkBox = rowArray[j].children[0];
var checkedValue = $('#'+checkBox.id+':checked').val();
if (checkedValue === "1") {
arrayInAthletes[sum+1] = rowArray[j].children[0].id;
sum += 1;
}
}
athletes[i-1] = arrayInAthletes;
}
</script>
I think what you are looking for is something like the code below (needs to be adapted to your example).
Contact.create([
{first_name: 'Ryan', last_name: 'Smith', email: 'ryan#smith.com'},
{first_name: 'John', last_name: 'Doe', email: 'john#doe.com'}
])
All you need to do is put your data into an array of hashes. Each hash is an object you want to save. This will do a batch insert, which is what I think you want.
You will need to pull the values for checked/unchecked from your params since that is where they are passed to your controller. Cycle through that hash and perform your logic on that.
Here's my controller
class ActivitiesController < ApplicationController
def exercises
if current_user.userprofile.present? #chef whether there is a userprofile object
#weeknum = current_user.userprofile.week
#dayly_activity = Activity.where(:week => 1, :day => 'Monday').first
end #end check userprofile
end
def updatexercises
respond_to do | format |
#dayly_activity = Activity.where(:week => 1, :day => 'Monday').first
#dayly_activity.update_attributes(params[:#dayly_activity])
#dayly_activity.save
format.html { render action: "exercises" }
end
end
end
And my template
<h1>WEEKLY EXERCICES</h1>
Day : <%= #dayly_activity.day %>
<%= form_for(#dayly_activity, :url => { :action => "updatexercises" }) do | f | %>
<table>
<tr>
<td>Jogging:</td>
<td>
<% list = (0..20).to_a %>
<%= f.select :jog, list %>
x 0.1 km
</td>
</tr>
<tr>
<td>Bicycling:</td>
<td>
<% list = (0..10).to_a %>
<%= f.select :bicycl, list %>
km
</td>
</tr>
<tr>
<td>Push ups:</td>
<td>
<% list = (0..20).to_a %>
<%= f.select :pushups, list %>
x 10 times
</td>
</tr>
<tr>
<td colspan = "2"><%= f.submit %></td>
</tr>
</table>
<% end %>
When I click the button, the Daily+activity object is not being saved. Am I missing some thing
EDIT
I've tried to hard code this way and it saving to the database.
#dayly_activity.jog = 17
#dayly_activity.pushups = 13
#dayly_activity.save
Obviously, the problem must be with the update_attributes
You need to use params[:dayly_activity] (drop the # sign).
Also, I would put these two lines :
#dayly_activity = Activity.where(:week => 1, :day => 'Monday').first
#dayly_activity.update_attributes(params[:dayly_activity])
Outside of your respond_to block (put them on top of it).
You can also drop the #dayly_activity.save, update_attributes do it automatically and will returns true/false if it works/fails.
You have error in [:#dayly_activity]
And in that code
#dayly_activity.update_attributes(params[:#dayly_activity])
#dayly_activity.save
save is useless. update_attributes saving the record.
It better to check result of update_attributes. So you can catch validation errors.
For example
if #dayly_activity.update_attributes(params[:dayly_activity])
redirect_to dayli_activity_path, :notice => "Updated"
else
render :edit
end