I am trying to override Ruby's <=> (spaceship) operator to sort apples and oranges so that apples come first sorted by weight, and oranges second, sorted by sweetness. Like so:
module Fruity
attr_accessor :weight, :sweetness
def <=>(other)
# use Array#<=> to compare the attributes
[self.weight, self.sweetness] <=> [other.weight, other.sweetness]
end
include Comparable
end
class Apple
include Fruity
def initialize(w)
self.weight = w
end
end
class Orange
include Fruity
def initialize(s)
self.sweetness = s
end
end
fruits = [Apple.new(2),Orange.new(4),Apple.new(6),Orange.new(9),Apple.new(1),Orange.new(22)]
p fruits
#should work?
p fruits.sort
But this does not work, can someone tell what I am doing wrong here, or a better way to do this?
Your problem is you are only initializing one of the properties on either side, the other one will still be nil. nil isn't handled in the Array#<=> method, which ends up killing the sort.
There are a few ways to handle the problem first would be something like this
[self.weight.to_i, self.sweetness.to_i] <=> [other.weight.to_i, other.sweetness.to_i]
nil.to_i gives you 0, which will let this work.
Probably late, nevertheless...
add the following monkeypatch
class Array
def to_i(default=Float::INFINITY)
self.map do |element|
element.nil? ? default : element.to_i
end
end
end
And change the body of Fruity::<=> to
[self.weight, self.sweetness].to_i <=> [other.weight, other.sweetness].to_i
Related
Here is what I have so far, it is not working:
class Couple(o,t)
one = o
two = t
end
couple1 = Couple.new(10, "Ten")
p couple1.one
p couple1.two
Not sure why it's not working?
Defining a class doesn't work like defining functions, you have to define functions on them that use the internal variables, and the the initializer that tells it what to do when calling .new
attr_accessor helps with the crud of setting up the functions and variables. The most simplest ways would be to use have a class like
class Couple
attr_accessor :one, :two
end
couple1 = Couple.new
couple1.one = 10
couple1.two = "Ten"
p couple1.one
p couple1.two
To use the new function to initialize the class with a few variables, you can define that function giving you a Class definition looking like
class Couple
attr_accessor :one, :two
def initialize(one, two)
#one = one
#two = two
end
end
couple1 = Couple.new(10, "Ten")
p couple1.one
p couple1.two
If you need to just hold a pair of items, then use Struct. It's a simple generator of classes that contain only variables and accessors, and nothing else (similar co C/C++'s Struct).
Couple = Struct.new(:one, :two)
# Or more idiomatically
class Couple < Struct.new(:one, :two)
def to_s
"one: #{self.one}, two: #{self.two}"
end
end
couple1 = Couple.new(10, 'ten')
puts couple1 # one: 10, two: ten
couple1.one = 100
puts couple1 # one: 100, two: ten
Also, one very interesting thing in Ruby is that the class data/members, both instance and class/static ones are "private" -- you can access them from outside only via accessor methods, not directly, and Ruby gives you the possibility to quicky generate these methods with the macros atrr_accessor, attr_reader, and attr_writer.
class Couple
one = 'o'
two = 't'
end
p Couple.one # NoMethodError: undefined method `one' for Couple:Class
class Couple
def initialize(one, two)
#one = one
#two = two
end
end
c = Couple.new(10, 'ten')
p c.one # undefined method `one' for #<Couple:0x936d2d4 #one=10, #two="ten">
That's why you need the accessors.
You need to use attr_reader for read or attr_accessor for read/write to access the class variables. Your class should look like this:
class Couple
attr_accessor :one, :two
def initialize(one, two)
#one = one
#two = two
end
end
Using attr_accessor will create, in this case, the methods one, one=, two, two=. If you were to use attr_reader, it would create the methods one, two.
Using the example above code, you could have:
couple = Couple.new(5, 6)
p couple.one # Outputs 5
p couple.two # Outputs 6
couple.one = 7
p couple.one # Outputs 7
There is also attr_writer, which will give you the methods one=, two=, but this isn't what you're looking for in this case. It gives you write only access to a variable.
I find very verbose and tedious to test if records coming from the database are correctly ordered.
I'm thinking using the array '==' method to compare two searches arrays. The array's elements and order must be the same so it seems a good fit. The issue is that if elements are missing the test will fail even though they are strictly ordered properly.
I wonder if there is a better way...
Rails 4
app/models/person.rb
default_scope { order(name: :asc) }
test/models/person.rb
test "people should be ordered by name" do
xavier = Person.create(name: 'xavier')
albert = Person.create(name: 'albert')
all = Person.all
assert_operator all.index(albert), :<, all.index(xavier)
end
Rails 3
app/models/person.rb
default_scope order('name ASC')
test/unit/person_test.rb
test "people should be ordered by name" do
xavier = Person.create name: 'xavier'
albert = Person.create name: 'albert'
assert Person.all.index(albert) < Person.all.index(xavier)
end
I haven't come across a built-in way to do this nicely but here's a way to check if an array of objects is sorted by a member:
class MyObject
attr_reader :a
def initialize(value)
#a = value
end
end
a = MyObject.new(2)
b = MyObject.new(3)
c = MyObject.new(4)
myobjects = [a, b, c]
class Array
def sorted_by?(method)
self.each_cons(2) do |a|
return false if a[0].send(method) > a[1].send(method)
end
true
end
end
p myobjects.sorted_by?(:a) #=> true
Then you can use it using something like:
test "people should be ordered by name by default" do
people = Person.all
assert people.sorted_by?(:age)
end
I came across what I was looking for when I asked this question. Using the each_cons method, it makes the test very neat:
assert Person.all.each_cons(2).all?{|i,j| i.name >= j.name}
I think having your record selection sorted will give you a more proper ordered result set, and in fact its always good to order your results
By that way I think you will not need the array == method
HTH
sameera
I read this article that explained how to make enumerations in Ruby, and also showed how to them enumerable like:
class Blah
def Blah.add_item(key, value)
#hash ||= {}
#hash[key] = value
end
def Blah.const_missing(key)
#hash[key]
end
def Blah.each
#hash.each {|key, value| yield(key, value)}
end
end
I have other enumerations that I need, can I create a base class somehow from this so I don't have to repeat the methods add_item, const_missng and .each for each one?
When creating a file for all my enums, I put it in /lib/enums.rb, is that a good practise?
should I be putting this class inside of a module i.e. I believe you do that for a namespace right?
You can just use Blah as your base class.
class C < Blah; end
class D < Blah; end
I think I might just throw it in with the source code of each project it's used with. Yes, DIE, DRY, and all that, but that's mostly important in line-by-line code. It's a fairly common practice to merge external software with each project.
No. It's already a class, so it's using only one name. Put the module around the code that uses Blah, the project or section of a project. That will be large and more in need of namespacing.
DigitalRoss's answer is good. I'll present an alternative. Suppose you'd like each of your enumerations to live in a module. All you need is a little Enumeration module, like so:
module Enumeration
include Enumerable
def self.included(m)
m.extend self
end
def each(&block)
constants.find_all do |name|
name =~ /^[A-Z_\d]+$/
end.map do |name|
[name, const_get(name)]
end.sort_by(&:last).each(&block)
end
end
When you need an enumeration, create a module for it, include Enumeration, and define your keys and values as constants with all-caps names.
module States
include Enumeration
INIT = 1
RUN = 2
DONE = 3
end
The module will respond to any of the methods provided by Enumerable:
p States.to_a
# => [["INIT", 1], ["RUN", 2], ["DONE", 3]]
You may find that you sometimes don't care what the values are, just that they are distinct. Let's add to Enumeration a method value that makes it easy to create constants with auto-incrementing keys:
module Enumeration
def value(name, value = next_value)
const_set(name, value)
end
def next_value
(map(&:last).max || 0) + 1
end
end
Now let's have some planets:
module Planets
include Enumeration
value :MERCURY
value :VENUS
value :EARTH
end
p Planets.to_a
# => [["MERCURY", 1], ["VENUS", 2], ["EARTH", 3]]
Of course, these enumerations are just collections of normal constants, so you can use them directly:
p Planets::MERCURY # => 1
I am trying to use a time_select to input a time into a model that will then perform some calculations.
the time_select helper prepares the params that is return so that it can be used in a multi-parameter assignment to an Active Record object.
Something like the following
Parameters: {"commit"=>"Calculate", "authenticity_token"=>"eQ/wixLHfrboPd/Ol5IkhQ4lENpt9vc4j0PcIw0Iy/M=", "calculator"=>{"time(2i)"=>"6", "time(3i)"=>"10", "time(4i)"=>"17", "time(5i)"=>"15", "time(1i)"=>"2009"}}
My question is, what is the best way to use this format in a non-active record model. Also on a side note. What is the meaning of the (5i), (4i) etc.? (Other than the obvious reason to distinguish the different time values, basically why it was named this way)
Thank you
You can create a method in the non active record model as follows
# This will return a Time object from provided hash
def parse_calculator_time(hash)
Time.parse("#{hash['time1i']}-#{hash['time2i']}-#{hash['time3i']} #{hash['time4i']}:#{hash['time5i']}")
end
You can then call the method from the controller action as follows
time_object = YourModel.parse_calculator_time(params[:calculator])
It may not be the best solution, but it is simple to use.
Cheers :)
The letter after the number stands for the type to which you wish it to be cast. In this case, integer. It could also be f for float or s for string.
I just did this myself and the easiest way that I could find was to basically copy/paste the Rails code into my base module (or abstract object).
I copied the following functions verbatim from ActiveRecord::Base
assign_multiparameter_attributes(pairs)
extract_callstack_for_multiparameter_attributes(pairs)
type_cast_attribute_value(multiparameter_name, value)
find_parameter_position(multiparameter_name)
I also have the following methods which call/use them:
def setup_parameters(params = {})
new_params = {}
multi_parameter_attributes = []
params.each do |k,v|
if k.to_s.include?("(")
multi_parameter_attributes << [ k.to_s, v ]
else
new_params[k.to_s] = v
end
end
new_params.merge(assign_multiparameter_attributes(multi_parameter_attributes))
end
# Very simplified version of the ActiveRecord::Base method that handles only dates/times
def execute_callstack_for_multiparameter_attributes(callstack)
attributes = {}
callstack.each do |name, values|
if values.empty?
send(name + '=', nil)
else
value = case values.size
when 2 then t = Time.new; Time.local(t.year, t.month, t.day, values[0], values[min], 0, 0)
when 5 then t = Time.time_with_datetime_fallback(:local, *values)
when 3 then Date.new(*values)
else nil
end
attributes[name.to_s] = value
end
end
attributes
end
If you find a better solution, please let me know :-)
What's the most elegant way to select out objects in an array that are unique with respect to one or more attributes?
These objects are stored in ActiveRecord so using AR's methods would be fine too.
Use Array#uniq with a block:
#photos = #photos.uniq { |p| p.album_id }
Add the uniq_by method to Array in your project. It works by analogy with sort_by. So uniq_by is to uniq as sort_by is to sort. Usage:
uniq_array = my_array.uniq_by {|obj| obj.id}
The implementation:
class Array
def uniq_by(&blk)
transforms = []
self.select do |el|
should_keep = !transforms.include?(t=blk[el])
transforms << t
should_keep
end
end
end
Note that it returns a new array rather than modifying your current one in place. We haven't written a uniq_by! method but it should be easy enough if you wanted to.
EDIT: Tribalvibes points out that that implementation is O(n^2). Better would be something like (untested)...
class Array
def uniq_by(&blk)
transforms = {}
select do |el|
t = blk[el]
should_keep = !transforms[t]
transforms[t] = true
should_keep
end
end
end
Do it on the database level:
YourModel.find(:all, :group => "status")
You can use this trick to select unique by several attributes elements from array:
#photos = #photos.uniq { |p| [p.album_id, p.author_id] }
I had originally suggested using the select method on Array. To wit:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
gives us [2,4,6] back.
But if you want the first such object, use detect.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} gives us 4.
I'm not sure what you're going for here, though.
I like jmah's use of a Hash to enforce uniqueness. Here's a couple more ways to skin that cat:
objs.inject({}) {|h,e| h[e.attr]=e; h}.values
That's a nice 1-liner, but I suspect this might be a little faster:
h = {}
objs.each {|e| h[e.attr]=e}
h.values
Use Array#uniq with a block:
objects.uniq {|obj| obj.attribute}
Or a more concise approach:
objects.uniq(&:attribute)
The most elegant way I have found is a spin-off using Array#uniq with a block
enumerable_collection.uniq(&:property)
…it reads better too!
If I understand your question correctly, I've tackled this problem using the quasi-hacky approach of comparing the Marshaled objects to determine if any attributes vary. The inject at the end of the following code would be an example:
class Foo
attr_accessor :foo, :bar, :baz
def initialize(foo,bar,baz)
#foo = foo
#bar = bar
#baz = baz
end
end
objs = [Foo.new(1,2,3),Foo.new(1,2,3),Foo.new(2,3,4)]
# find objects that are uniq with respect to attributes
objs.inject([]) do |uniqs,obj|
if uniqs.all? { |e| Marshal.dump(e) != Marshal.dump(obj) }
uniqs << obj
end
uniqs
end
You can use a hash, which contains only one value for each key:
Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
Rails also has a #uniq_by method.
Reference: Parameterized Array#uniq (i.e., uniq_by)
I like jmah and Head's answers. But do they preserve array order? They might in later versions of ruby since there have been some hash insertion-order-preserving requirements written into the language specification, but here's a similar solution that I like to use that preserves order regardless.
h = Set.new
objs.select{|el| h.add?(el.attr)}
ActiveSupport implementation:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Now if you can sort on the attribute values this can be done:
class A
attr_accessor :val
def initialize(v); self.val = v; end
end
objs = [1,2,6,3,7,7,8,2,8].map{|i| A.new(i)}
objs.sort_by{|a| a.val}.inject([]) do |uniqs, a|
uniqs << a if uniqs.empty? || a.val != uniqs.last.val
uniqs
end
That's for a 1-attribute unique, but the same thing can be done w/ lexicographical sort ...
If you are not married with arrays, we can also try eliminating duplicates through sets
set = Set.new
set << obj1
set << obj2
set.inspect
Note that in case of custom objects, we need to override eql? and hash methods