Simple question
In rails I have an ApplicationHelper method
def sum_customer_yearly_revenue(customer_id, year)
sum_customer_yearly_revenue = Sale.sum(:net_amount, :conditions => ['customer_id = ? AND financial_year = ?', customer_id, year])
end
In my view I then call sum_customer_yearly_revenue(123456, 2014). How do I call the same method but with 'all' years. If I was using SQL it would be sum_customer_yearly_revenue(123456, *) but that returns an error. If it pass in "" it looks for a year that is empty. If I leave it blank it just errors aswell.
def sum_customer_yearly_revenue(customer_id, year="all")
sales = Sale.where(customer_id: customer_id)
sales = sales.where(year: year) if year!="all"
sales.sum(:net_amount)
end
This might be useful to you. In this case if you want to retrieve sum for all year there is no necessary to pass second argument.
Change the method to:
def sum_customer_yearly_revenue(customer_id, year = nil)
conditions = { :customer_id => customer_id }
conditions.merge!(:financial_year => year) if year
Sale.sum(:net_amount, :conditions => conditions)
end
And call it like this:
sum_customer_yearly_revenue(123456)
Can't in this form. Make a new helper, sum_customer_total_revenue. Or introduce way more logic in this function, but that's not as pretty.
Also, not sure those should be any kind of helpers; that kind of stuff belongs right into a model. If you need it, make sure the model passes those information to the view by including it from a controller, not directly in a helper.
(EDIT: upvoted jbmyid; that's a better syntax to do this. However... Still think it doesn't belong in a function that's called the way it's called; still think it doesn't belong in a helper.)
Related
I am pretty new to Rails and I have a feeling I'm approaching this from the wrong angle but here it goes... I have a list page that displays vehicles and i am trying to add filter functionality where the user can filter the results by vehicle_size, manufacturer and/or payment_options.
Using three select form fields the user can set the values of :vehicle_size, :manufacturer and/or :payment_options parameters and submit these values to the controller where i'm using a
#vehicles = Vehicle.order("vehicles.id ASC").where(:visible => true, :vehicle_size => params[:vehicle_size] )
kind of query. this works fine for individual params (the above returns results for the correct vehicle size) but I want to be able to pass in all 3 params without getting no results if one of the parameters is left blank..
Is there a way of doing this without going through the process of writing if statements that define different where statements depending on what params are set? This could become very tedious if I add more filter options.. perhaps some sort of inline if has_key solution to the effect of:
#vehicles = Vehicle.order("vehicles.id ASC").where(:visible => true, if(params.has_key?(:vehicle_size):vehicle_size => params[:vehicle_size], end if(params.has_key?(:manufacturer):manufacturer => params[:manufacturer] end )
You can do:
#vehicles = Vehicle.order('vehicles.id ASC')
if params[:vehicle_size].present?
#vehicles = #vehicles.where(vehicle_size: params[:vehicle_size])
end
Or, you can create scope in your model:
scope :vehicle_size, ->(vehicle_size) { where(vehicle_size: vehicle_size) if vehicle_size.present? }
Or, according to this answer, you can create class method:
def self.vehicle_size(vehicle_size)
if vehicle_size.present?
where(vehicle_size: vehicle_size)
else
scoped # `all` if you use Rails 4
end
end
You call both scope and class method in your controller with, for example:
#vehicles = Vehicle.order('vehicles.id ASC').vehicle_size(params[:vehicle_size])
You can do same thing with remaining parameters respectively.
The has_scope gem applies scope methods to your search queries, and by default it ignores when parameters are empty, it might be worth checking
I have a form that I want a date in. The date that the controller defines is #date_end. I want this date in the form, but without an AR model this seems REALLY STUPIDLY hard. I used to use this:
= date_select :date_end_, nil, :default => #date_end
But this only works if your 'name' is different from the instance var. If they are the same you get an 'interning string' error.
The only way I've found to solve this is to add a method to Object to return self
class Object
def get_self
self
end
end
Then I can do:
= date_select :date_end, :get_self
But we all know this is stupid. But there doesn't seem to be another way...
Any suggestions?
Would something like this do the trick:
= select_date Date.today, :prefix => :date_end
According to this documentation, under section "4.1 Barebones Helpers", using this particular helper will create a series of select tags for the day, month year. The names should end up looking like "date_end[day]", "date_end[month]"...etc. Let me know if that helps.
In my view page, i am using form_tag to create a form which will pass a string of ids from a hidden field to the controller code.
In my controller code, i am looping through an array of ids to update each record containing that id in the Expression table. But the code below does not seem to work.
I would really appreciate it if somebody could give me some suggestion regarding what is wrong with the code below.
def update_expression
#emi_ids_array = params[:emi_ids].split(/,/)
#sub_id = params[:sub_id]
#emi_ids_array.each do |emi_id|
#existing_exp = Expression.find(:first, :conditions => [ "EXT_EMI_ID = ? and EXT_SUB_FK = ?", emi_id, #sub_id])
#expression = #existing_exp.update_attributes(
:EXT_SUB_FK => #sub_id,
:EXT_PRESENCE => "present",
:EXT_STRENGTH => "weak",
:EXT_EMI_ID => emi_id
)
end
end
Try converting the array of ID's (and the sub_id) to integers.
Is it the finding of the object that fails, or the update? Output the #expression.errors after the update call to see if there are any validations failing.
Is there a reason for all the instance variables? You don't need the #'s if the variable doesn't go beyond that method. Also the #expression item seems superfluous, you're just duplicating the #existing_exp object, you don't need to put the return into a new object, especially if it's replaced each time the loop runs anyway.
Found a temporary solution. 'update_attributes' does not seem to work, so i opted for 'update_all' attribute
Expression.update_all({:EXT_PRESENCE => "present", :EXT_STRENGTH => "weak"},['EXT_EMI_ID = ? and EXT_SUB_FK = ?', emi_id, #sub_id])
Hopefully, it might be useful to someone else
everyone: I am also open to just straight-up refactoring what I'm finding to be pretty repetitive, but to give a baseline of how it's working....
I have for every contact a Campaign, which has_many of three types of Models: Email, Call, and Letter.
When an Email (Call or Letter) has been executed for a specific contact, I have a Contact_Email(_or_Call_or_Letter) which belongs to both the Contact and the Model (Email_or_Call_or_Letter).
Each Contact_Email for example pairing has a :date_sent attribute. So does each Contact_Call and Contact_Letter.
How do I find the latest of all of them?
Here is the code I wrote that can find the latest Email and my finding retyping similar code for Call and Letter, but then stuck on how to do a .max on all of them:
def last_email(contact)
#get campaign the contact belongs to
#campaign = Campaign.find_by_id(contact.campaign_id)
#last_email = ContactEmail.find(:last,
:conditions => "contact_id = #{contact.id}",
:order => "date_sent DESC")
#last_call = ContactCall.find(:last,
:conditions => "contact_id = #{contact.id}",
:order => "date_sent DESC")
#last_letter = ContactLetter.find(:last,
:conditions => "contact_id = #{contact.id}",
:order => "date_sent DESC")
# how do I get the latest of all of these to display?
#email_template = Email.find_by_id(#last_email.email_id)
if #last_email.nil?
return "no email sent"
else
return #last_email.date_sent.to_s(:long) + link_to('email was sent', #email_template)
end
end
Question 1: With what I have, how can I find effectively #last_event given I can find the last Email, last Call, and last Letter for every contact?
Question 2: How can I remove the repetitive code that I have to write for each Model?
Do you have has_many associations setup in Contact referring to the other models? Something like:
class Contact < ActiveRecord::Base
has_many :contact_emails
has_many :contact_calls
has_many :contact_letters
end
If so, you can then create a last_event method on the Contact model:
def latest_event
[contact_emails, contact_calls, contact_letters].map do |assoc|
assoc.first(:order => 'date_sent DESC')
end.compact.sort_by { |e| e.date_sent }.last
end
Handling nil
When using the latest_event method you will get nil if there are no associated records. There are a couple of ways you can workaround this. The first is to check for nil first with something like:
contact.latest_event && contact.latest_event.date_sent
On late versions of Rails/Ruby you can also use Object#try which will call the method if it exists:
contact.latest_event.try(:date_sent)
I prefer not to use this as it doesn't check for nil but only if the object can respond to a method. This has cause some interesting errors if you expect nil if the object is nil but are calling a method which nil itself responds to.
Finally, my preferred method for the simple case is to use the andand gem which provides Object#andand. This greatly shortens the safe case above and saves calling of latest_event multiple times:
contact.latest_event.andand.date_sent
date_sent, nil and You.
For your example usage of calling to_s(:long), you could either use && or andand:
contact.latest_event.andand.date_sent.andand.to_s(:long)
or
contact.latest_event && contact.latest_event.date_sent.to_s(:long)
The first is safer if date_sent itself may be nil. Without using andand this could be written as:
contact.latest_event &&
contact.latest_event.date_sent &&
contact.latest_event.date_sent.to_s(:long)
which is rather complex and unwieldily in my opinion. I would recommend looking into andand
For question 1:
Just do
#last_event = [#last_letter, #last_email, #last_call].sort_by{|m| m.date_sent}.first
For question 2:
Well this is more interesting. This kind of depends on how exactly do your models look, but you might want to consider Single Table Inheritance for this type of scenario.
I have a table of events (in a sqlite3 database for what it's worth) with a column called "when" that contains a timestamp detailing precisely when the event that particular row denotes is set to occur. Right now, I have
#events = Event.find(:all)
in my controller and I am using template helper methods to calculate where to place each event on my display page based on the day of the week it occurs on. For example:
<% if(event.when.wday == 6) %>
# DO SOMETHING
<% end %>
I want to abstract this logic to the controller however. My idea was to do the following:
#thursday_events = Event.find(:all, :conditions => ["when.wday=4"])
Obviously (I guess?) this didn't work. Throwing the error "SQLite3::SQLException: near "when": syntax error: SELECT * FROM "events" WHERE (when.wday=4)".
I'm assuming this is because I tried to use a helper method within a find condition but I don't know a better way to do this. Any advice? Thanks!
The conditions parameter needs to be a fragment of SQL.
:conditions => ["when.wday=4"]
is a fragment of Ruby code, so no go.
Try
# Model Event has a datetime field named 'when'
Event.find(:all, :conditions => ["strftime('%w', events.when) = 4"])
SQLLite ref: http://www.sqlite.org/lang_datefunc.html
Added:
While more closely reading your post, I think you're planning to send multiple instance variables (one per day of the week) from your controller to your view. That's a good idea--moving logic out of the view. But, don't do more dbms queries!
Each query has significant overhead. Eg:
#Do NOT do it this way (too many db queries)
#sunday_events = Event.find(:all,
:conditions => ["strftime('%w', events.when) = 0"])
#monday_events = Event.find(:all,
:conditions => ["strftime('%w', events.when) = 1"])
#thursday_events = Event.find(:all,
:conditions => ["strftime('%w', events.when) = 4"])
# ... etc
# Better: Just 1 database query--
events = Event.find(:all)
#sunday_events = events.select{|e| e.when.wday == 0}
#monday_events = events.select{|e| e.when.wday == 1}
#thursday_events = events.select{|e| e.when.wday == 4}
# ... etc
Final comment:
Current best practice thinking is to move code into the models from controllers wherever reasonable. This is called "Fat model, skinny controller" In the above example, you could have a class-level method in the model create the individual instance variables. Or perhaps better, one hash that contains 7 values, each being an array of the records. Eg
# in Event model
def Event.find_by_day
events = Event.find(:all)
result = {}
days = [:sun, :mon, :tue, :wed, :thu, :fri, :sat]
(0..6).each{|day_i| result[days[day_i]] =
events.select{|e| e.when.wday == day_i}
}
result
end
# in controller
#events = Event.find_by_day
# in view
# #events[:sun] is array of the Sunday events
# so do something with them...