How to understand this code? - ruby-on-rails

def event_calendar_options
{
:year => #year,
:month => #month,
:event_strips => #event_strips,
:month_name_text => I18n.localize(#shown_month, :format => "%B %Y"),
:previous_month_text => "<< " + month_link(#shown_month.prev_month),
:next_month_text => month_link(#shown_month.next_month) + " >>"
}
end
def event_calendar
calendar event_calendar_options do |args|
event = args[:event]
%(#{h(event.name)})
end
end
Here the whole event_calendar_options is enclosed by {}, so does it simply return the hash table?
Secondly, why event_calendar_options, a module method, can be passed as a parameter to calendar?

Assuming they're part of the same class (e.g. controller or model), event_calendar_options can be called by event_calendar. This is common practice within classes. Even if they're not, they can be declared as public and called by other classes as well (which is less common).
And, yes, event_calendar_options returns the hash table. In ruby, the final statement will be returned unless a return statement is supplied. In this case, it will return a hash table that will be iterated over by the event_calendar method.

Related

Ruby condition for inserting unique items into an array

I know that if you have an array and reference it as array.uniq it will return without any of the duplicates.
However in this case it is an array of objects (is that proper ruby speak?). I want each call to go into the #calls array unless the call.from is the same as a call_formatted object already present in the array.
How can I conditionally place these objects in the array if no other objects in the array have the same call.from value?
calls_raw.each do |call|
call_formatted = {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
#calls << call_formatted
end
array.uniq { |item| item[:from] }
Use #map to build your array for you and call #uniq on it...
calls_raw.map do |call|
{
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.uniq{|call| call[:from]}
The above approach will first build an array of calls larger than it may ultimately need to be, and the final call to #uniq will make the list unique.
Or, to avoid adding all the duplicates in the array, you could build it with a Hash as such:
calls_raw.each_with_object do |call, h|
h[call.from] ||= {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.values
The Hash approach will use the first occurrence of call.from as it is being set with ||=. To use the last occurrence of call.from then use a straightforward assignment with =.
It's also been suggested to just use a Set instead of an Array.
To take that approach you're going to have to implement #eql? and #hash on the class we're populating the set with.
class CallRaw
attr_accessor :from
def initialize(from)
self.from = from
end
def eql?(o)
# Base equality on 'from'
o.from == self.from
end
def hash
# Use the hash of 'from' for our hash
self.from.hash
end
end
require 'set'
s = Set.new
=> <Set: {}>
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 #from="Chewbaca">}>
# We expect now, that adding another will not grow our set any larger
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 #from="Chewbaca">}>
# Great, it's not getting any bigger
s << CallRaw.new("Chewbaca")
s << CallRaw.new("Chewbaca")
=> <Set: {#<CallRaw:0x00000002211888 #from="Chewbaca">}>
Awesome - the Set works!!!
Now, it is interesting to note that having implemented #eql? and #hash, we can now use Array#uniq without having to pass in a block.
a = Array.new
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">]
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">, <CallRaw:0x000000021c2bc0 #from="Chewbaca">]
a.uniq
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">]
Now, I'm just wondering if there is a badge that StackOverflow awards for having too much coffee before setting out to answer a question?
Unless there's some reason it has to be an array, I'd store the data in a Hash, keyed by the from value.
Then it's easy and fast to look up an entry by the from value. You can choose to insert a new value only if there's no value already with the same key, or insert the new value and let it replace the old entry with that key.
Example:
calls = Hash.new
def add(call)
if not calls[call.from]
calls[call.from] = call
end
end

Rails - How to define setter/getter dynamically for list of methods inside a class

I have a Notifications module which have classes like 1)car 2)bike 3)Aeroplane. I have a serialized column in UserFeature model.And I have a module 'Notifications' which has list of 11 classes in it.
Notifications
1)car
2)bike
3)Aeroplane
The hash structure of the column notifications in UserFeature model must be
{:car => {:mirror => :true, :door => :true}
:bike => {:x=> :true, :x => :true}
:Aeroplane => {:p => :true, :q => :true}
}
I can access user_object.Notifications
But so as to access user_object.car and also user_object.mirror I need to write getter/setter methods { Defining getter/setter dynamically because I dont want to write getter/setter for every method and also I am unsure about the number of methods I have -> which in future may extend }
Notifications.constants.each do |notification_class|
class_methods = "Notifications::#{notification_class}".constantize.methods(false)
class_methods.each do |method|
method_name = method[0..-4].split('(')[0]
setter_getter_name = "#{notification_class.to_s.underscore}_#{method_name}"
define_method("#{setter_getter_name}=") do |value|
self.notifications = GlobalUtils.form_hash(self.notifications, "#{notification_class}".to_sym, "#{method_name}".to_sym)
self[:notifications]["#{notification_class}".to_sym][ "#{method_name}".to_sym] = value
end
define_method("#{setter_getter_name}") do
self.notifications.fetch("#{notification_class_name}".to_sym, {}).fetch("#{method_name}".to_sym)
end
end
end
But still when i try to access user_object.mirror,
undefined method for #<UserFeature000043645345>
What I am doing wrong?
I need to do this using getter/setter method only
An OpenStruct is a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values. This is accomplished by using Ruby’s metaprogramming to define methods on the class itself.
example:
require 'ostruct'
hash = { "country" => "Australia", :population => 20_000_000 }
data = OpenStruct.new(hash)
p data # -> <OpenStruct country="Australia" population=20000000>
Use Ruby OpenStruct class. It will fulfill your requirements without defining such bunch of code.
Edit1, example:
require 'ostruct'
class Aeroplane < OpenStruct; end
a = Aeroplane.new(:p => :true, :q => :true)
a.p # => true

what is activerecord.values.columnattribute

I got an events helper module that somebody coded in a rails application. I am working on a form that can allow someone to create a new event.
here is a part of the form
=form.input :sponsorship_type, collection: get_event_labels(:event_types), as: :select_other
=form.input :society_name
it used to be
=form.input :event_type, collection: get_event_labels(:sponsorship_types), as: :select_other
=form.input :society_name
per the client request I had to drop the event_type column from the events table and added this instead
t.string "sponsorship_type"
the old schema has this
t.string "event_type"
this is the module
module EventsHelper
LABEL_MAP = {
institutions: [::INSTITUTIONS, 'activerecord.values.institutions.name'],
event_types: [::EVENT_TYPES, 'activerecord.values.event_types'],
industries: [::INDUSTRIES, 'activerecord.values.industries'],
referrers: [::REFERRERS, 'activerecord.values.referrers'],
regions: [::REGIONS, 'activerecord.values.regions'],
cities: [::CITIES, 'activerecord.values.cities']
}.freeze
def get_event_labels(type)
if Geokit::Geocoders::IpGeocoder.geocode(remote_ip).country_code == 'TW' and type == :event_types
return {
'活動/班' => 'Activities/Classes',
'食品和飲料' => 'Food&Beverage',
'優惠券' => 'Coupons',
'現金' => 'Cash',
'器材' => 'Equipment',
'獎品' => 'Prizes'
}
end
Hash[
LABEL_MAP[type][0].map do |constant|
[I18n.t("#{LABEL_MAP[type][1]}.#{constant}"),
constant]
end
]
end
def remote_ip
request.remote_ip
end
end
what is this? [::EVENT_TYPES, 'activerecord.values.event_types']
i tried just changing all the event_types to sponsorship_type. and then I am getting a
': uninitialized constant SPONSORSHIP_TYPES (NameError)
Its probably because activerecord.values.sponsorship_types have no values. How do I access it and put in values?
what is this?
::EVENT_TYPES
my end goal is to return the hash
return {
'活動/班' => 'Activities/Classes',
'食品和飲料' => 'Food&Beverage',
'優惠券' => 'Coupons',
'現金' => 'Cash',
'器材' => 'Equipment',
'獎品' => 'Prizes'
}
as selection option for the user on the form.
EVENT_TYPES is a constant. It must be defined somewhere in that application, perhaps in the controller or somewhere in the config folder. Find it and define your SPONSORSHIP_TYPES in the same way.
activerecord.values.event_types looks like a localization key. Look into your localization files in config/locales/... for some yaml hash with this structure. Add a new node sponsorship_types in the same way.

Another way to output helper instead of eval

(#products + #collections + #users + #questions).map do |r|
#results << {
:label => ["Product", "Collection", "User"].include?(r.class.name) ? r.name : r.question,
:category => r.class.name,
:href => eval("#{r.class.name.downcase}_path(r)")
}
end
I am currently looking if there is a way not to use eval on the string to convert it to a helper.
Note: This code is currently in the controller. Rails 2.3.11
Use polymorphic_path
#results = (#products + #collections + #users + #questions).map do |r|
{
:label => ["Product", "Collection", "User"].include?(r.class.name) ? r.name : r.question,
:category => r.class.name,
:href => polymorphic_path(r)
}
end
You can try something like this:
:href => self.send("#{r.class.name.downcase}_path".to_sym, r)
Since I'm not totally sure of the context here I'm not 100% confident this will work, but if this is a method you're trying to reference, then self is the most likely target for it.
There's 3 ways you can dynamically call methods with benchmarks shown here. I'l summarize the article below:
One way to invoke a method dynamically in ruby is to send a message to the object :
p s.send(:length) #=> 6
p s.send(:include?,"hi") #=> true
A second way is instantiate a method object and then call it:
method_object = s.method(:length)
p method_object.call #=> 6
method_object = s.method(:include?)
p method_object.call('hi') #=> true
And the third way is to use the eval method:
eval "s.length" #=> 6
eval "s.include? 'hi'" #=>true
According to the benchmarks results the SLOWEST is eval so I'd use send instead.
#######################################
##### The results
#######################################
#Rehearsal ----------------------------------------
#call 0.050000 0.020000 0.070000 ( 0.077915)
#send 0.080000 0.000000 0.080000 ( 0.086071)
#eval 0.360000 0.040000 0.400000 ( 0.405647)
#------------------------------- total: 0.550000sec
# user system total real
#call 0.050000 0.020000 0.070000 ( 0.072041)
#send 0.070000 0.000000 0.070000 ( 0.077674)
#eval 0.370000 0.020000 0.390000 ( 0.399442)
There are a few things you should be doing different, some of which have been alluded to or explicitly stated in other answers, but I think this does a better job:
#results = (#products + #collections + #users + #questions).map do |r|
{
:label => r.try(:question) || r.name,
:category => r.class.model_name.human,
:href => send(:"#{r.class.model_name.underscore}_path", r)
}
end
You don't need to build your result array manually — you're already using map, just assign the result to #results.
There's no need to do all the manual work of determining whether or not you should be posting the question or the name. You could give all these models a to_label method, which would be pretty easy, but assuming that questions don't have names using try and a binary or is just a dead simple way around the problem.
Single word model names convert pretty easily to display titles, but when you end up with a class like BedSheet you're going to want it to display as "Bed Sheet". Might as well do it right to start.
The other side of that same problem is converting the class name to the path/url helper method: you want bed_sheet_path, not bedsheet_path.
Using send over eval, as explained in other answers. No need to use an explicit to_sym though since Ruby supports using double-quotes to create a symbol outright.
Another quick note, I don't know if Rails 2.x was the same, but in Rails 3 you don't need to use the path helper to link to an instance of a model since most html helper methods will convert automatically (e.g. link_to 'A Product', #product).
Voila.
You could use a look-up table:
class_procs = {
Product => {
:path => lambda { |r| product_path(r) },
:label => lambda { |r| r.name }
},
Collection => {
:path => lambda { |r| collection_path(r) },
:label => lambda { |r| r.name }
}
User => {
:path => lambda { |r| user_path(r) },
:label => lambda { |r| r.name }
},
Question => {
:path => lambda { |r| question_path(r) },
:label => lambda { |r| r.question }
}
}
(#products + #collections + #users + #questions).map do |r|
procs = class_procs[r.class]
#results << {
:label => procs[:label].call(r),
:category => r.class.name,
:href => procs[:path].call(r)
}
end
And if your needs get more complicated then you could easily convert the per-class Hashes to individual classes to keep the inner Hashes from getting too big and complicated.

FasterCSV Parsing issue?

G'day guys, I'm currently using fastercsv to construct ActiveRecord elements and I can't for the life of me see this bug (tired), but for some reason when it creates, if in the rake file i output the column I want to save as the element value, it puts out correctly, as either a Trade or a Quote
but when I try to save it into the activerecord, it won't work.
FasterCSV.foreach("input.csv", :headers => true) do |row|
d = DateTime.parse(row[1]+" "+row[2])
offset = Rational(row[3].to_i,24)
o = d.new_offset(offset)
t = Trade.create(
:name => row[0],
:type => row[4],
:time => o,
:price => row[6].to_f,
:volume => row[7].to_i,
:bidprice => row[10].to_f,
:bidsize => row[11].to_i,
:askprice => row[14].to_f,
:asksize => row[15].to_i
)
end
Ideas?
Name and Type are both strings, every other value works except for type. Have I missed something really simple?
Ruby's Object class has a type method. You need to t[:type] = row[4] to avoid that method.
-Tim

Resources