I know I can use something like User.sort {|a, b| a.attribute <=> b.attribute} or User.find and order, but is it a way like comparable interface in Java, so every time I called sort on User object it will do sort on the predefined attributes.
Thanks,
You can do that by defining the <=> method for your objects. That way, you should be able to just say: collection.sort for non-destructive sort, or collection.sort! for in-place sorting:
So, example here:
class A
def <=>(other)
# put sorting logic here
end
end
And a more complete one:
class A
attr_accessor :val
def initialize
#val = 0
end
def <=>(other)
return #val <=> other.val
end
end
a = A.new
b = A.new
a.val = 5
b.val = 1
ar = [a,b]
ar.sort!
ar.each do |x|
puts x.val
end
This will output 1 and 5.
Related
I am facing issues while using sort or sort_by in my presenter and controller. It says undefined method 'sort_by' .
Does it belong to any particular class? I have looked it up online but I am not able to find anything concrete.
Can any one shed light on this?
Here is my presenter code-
def sortDocument()
#list.sort do |a, b|
(b.published_date <=> a.published_date) ||
a.display_name <=> b.display_name
#list
end
and alternatively
def sortDocument()
#list.sort_by!{ |m| m.published_date }
end
EDIT:
Error message:
undefined method `sort_by!' for #<DocumentsBureauServices::DocumentList:0x007ff8250f8a28>
presenter-
class DocumentPresenter
def initialize(list)
#list = list
#list = sort_document()
end
def sortDocument()
#list.sort do |a, b|
(b.published_date <=> a.published_date) ||
a.display_name <=> b.display_name
#list
end
end
and alternatively
def sortDocument()
#list.sort_by!{ |m| m.published_date }
end
Sorts enum using a set of keys generated by mapping the values in enum through the given block...to use sort_by ...
%w{ apple pear fig }.sort_by {|word| word.length}
#=> ["fig", "pear", "apple"]
It looks like the DocumentsBureauServices::DocumentList doesn't include the enumerable module and doesn't have access to the sort_by method.
If indeed DocumentList is a decorator around an enumerable object then you could use a delegator. ActiveSupport has a method dedicated to delegating: http://apidock.com/rails/Module/delegate
In your case, you could use it like this:
class DocumentsBureauServices::DocumentList
delegate :sort_by!, to: :my_enumerable
def initialize(enumerable)
#my_enumerable = enumerable
end
end
And then you could call sort_by! on your list object like this:
#list = DocumentsBureauServices::DocumentList.new(Document.all)
#list.sort_by! {|m| m.published_date}
#=> [#Document1, #Document2, etc]
If you want to know more about creating your own presenters, might I suggest the excellent RailsCast about it http://railscasts.com/episodes/287-presenters-from-scratch
I personally use the Draper library for presenters: https://github.com/drapergem/draper which makes presenting ActiveRecord objects a breeze. There's a RailsCast about it here: http://railscasts.com/episodes/286-draper
Currently, I have a series of gift cards that have different statuses. Due to complex logic, these statuses are not persisted on the object. My goal is to order these gift cards by their status. This is my current implementation:
#gift_cards.sort! do |a, b|
comp = gc_sort_order[a.status] <=> gc_sort_order[b.status]
comp.zero?? (b.expiration_date <=> a.expiration_date) : comp
end
GiftCard#status retuns either expired redeeemd or active. Their 'sort weight' is determined by gc_sort_order defined thusly:
def gc_sort_order
{
active: 1,
redeemed: 2,
expired: 3
}
end
However, this seems very clunky, and I'd like to refactor it but haven't come across a better solution as of yet. Any input would be appreciated.
class Reverser
attr_reader :v
def initialize(v)
#v = v
end
def self.[](v)
new(v)
end
def <=>(other)
other.v <=> #v
end
end
# status ASC, expiration_date DESC
#gift_cards.sort_by! { |gc| [ gc_sort_order[gc.status],
Reverser[gc.expiration_date] ] }
Try this:
#gift_cards.sort_by! { |gc| [-gc_sort_order[gc.status], gc.expiration_date] }.reverse
for one of my views I want to include a search field with jqueries typeahead function.
The array should contain all the attribute values of a client.
The array for the query is generated the following way:
#clients = []
Client.each do |client|
#clients << client.attributes.values.join(' ')
end
Is this approach performant enough for a dataset of about 3000 entries?
Or is there a better and faster solution?
Thanks in advance.
Cheers,
Patrick
Update
One user mentioned to implement it like this:
#clients = Client.map do |client|
client.attributes.values.join(' ')
end
This is another way to do it. But a benchmark reveals that this is no improvement in performance.
This leaves me with the question: Maybe there is a more performant way, but speaking about a maxium of 3000 records - does it really matter?
You could use .map:
#clients = Client.map do |client|
client.attributes.values.join(' ')
end
Even if ActiveRecord models would implement the map method (which they don't i believe), the two solutions suggested by the OP and #xdazz are time- and memory-complexity-wise equivalent. This can be observed with this simple benchmark:
require 'fruity'
# Dummy client class
class Client < Struct.new(:first_name, :last_name, :position, :company)
class << self
include Enumerable
def each(&block)
5000.times do
yield Client.new('Firstname', 'Lastname', 'CEO', 'Company Inc.')
end
end
end
alias_method :attributes, :to_h
end
compare do
schnika do
clients = []
Client.each do |client|
clients << client.attributes.values.join(' ')
end
nil
end
xdazz do
clients = Client.map do |client|
client.attributes.values.join(' ')
end
nil
end
end
Which will output
schnika is similar to xdazz
Also, when you look at the implementation of map (synonymous to collect), it becomes clear that really nothing else happens than in the OP's method:
static VALUE
rb_ary_collect(VALUE ary)
{
long i;
VALUE collect;
RETURN_ENUMERATOR(ary, 0, 0);
collect = rb_ary_new2(RARRAY_LEN(ary));
for (i = 0; i < RARRAY_LEN(ary); i++) {
rb_ary_push(collect, rb_yield(RARRAY_PTR(ary)[i]));
}
return collect;
}
This translates to:
class Array
def collect
collect = []
self.each do |el|
collect << yield(el)
end
collect
end
end
You probably don't need to retrieve all the attributes (for example 'updated_at'), so the following may be faster:
#clients = Client.select([:name, :email, :id]).map do |client|
client.attributes.values.join(' ')
end
Added the id in case you need to link to the client.
I have custom array methods like
class Array
def decreasing?
for i in (0...self.size)
return false if self[i] > self[i+1]
end
true
end
def increasing?
for i in (0...self.size)
return false if self[i] < self[i+1]
end
true
end
end
And
module Enumerable
def sorted?
each_cons(2).all? { |a, b| (a <=> b) <= 0 }
end
end
Currently I have them in a model file randomly. Where is a better place to put these codes in Rails?
I would put it in an initializer (in config/initializers) called array_extensions.rb and enumerable_extensions.rb.
I think it can be under /lib directory.
Question: Is there a concise way in Ruby (and/or Rails) to merge two objects together?
Specifically, I'm trying to figure out something akin to jQuery's $.extend() method, whereas the first object you pass in will have its properties overridden by the second object.
I'm working with a tableless model in Rails 3.2+. When a form submission occurs, the parameters from the submission are used to dynamically populate a User object. That user object is persisted between page requests using Ruby's PStore class, marshalling objects to flat files which can be easily retrieved in the future.
Relevant code:
module Itc
class User
include ActiveModel::Validations
include ActiveModel::Conversion
include ActionView::Helpers
extend ActiveModel::Naming
def set_properties( properties = {} )
properties.each { |k, v|
class_eval("attr_reader :#{k.to_sym}")
self.instance_variable_set("##{k}", v)
} unless properties.nil?
end
end
end
Creation of a user object occurs like this:
user = Itc.User.new( params[:user] )
user.save()
The save() method above is not ActiveRecord's save method, but a method I wrote to do persistence via PStore.
If I have a user object loaded, and I have a form submission, I'd like to do something like this:
merged = existingUserObject.merge(User.new(params[:user])
and have the outcome of merged be a user object, with only properties that were changed in the form submission be updated.
If anyone has any ideas about a better way to do this in general, I'm all ears.
Is Hash#merge not what you're looking for? http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-merge. Seems like you could just go
merged = existingUserObject.merge(params[:user])
I don't think you need to create an entirely new User object since presumably that's what existingUserObject is and you just want to overwrite some properties.
Do it by piggybacking on hash's behaviors. Create a class that takes a hash for the parameter of the new() method, and then a to_h method that takes an object and generates a hash from the current state of the instance:
class Foo
def initialize(params={})
#a = params[:a]
#b = params[:b]
end
def to_h
{
a: #a,
b: #b
}
end
end
instance_a = Foo.new(a: 1, b:2)
instance_b = Foo.new(a: 1, b:3)
instance_c = Foo.new(instance_a.to_h.merge(instance_b.to_h))
Dumping it into IRB:
irb(main):001:0> class Foo
irb(main):002:1> def initialize(params={})
irb(main):003:2> #a = params[:a]
irb(main):004:2> #b = params[:b]
irb(main):005:2> end
irb(main):006:1>
irb(main):007:1* def to_h
irb(main):008:2> {
irb(main):009:3* a: #a,
irb(main):010:3* b: #b
irb(main):011:3> }
irb(main):012:2> end
irb(main):013:1> end
nil
irb(main):014:0>
irb(main):015:0* instance_a = Foo.new(a: 1, b:2)
#<Foo:0x1009cfd00
#a = 1,
#b = 2
>
irb(main):016:0> instance_b = Foo.new(a: 1, b:3)
#<Foo:0x1009ead08
#a = 1,
#b = 3
>
irb(main):017:0>
irb(main):018:0* instance_c = Foo.new(instance_a.to_h.merge(instance_b.to_h))
#<Foo:0x100a06c60
#a = 1,
#b = 3
>
This is how I achieved similar thing with 1 of my model
# merge other_config.attrs into self.attrs, only for nil attrs
def merge!(other_object)
return if other_object.nil? || other_object.class != self.class
self.assign_attributes(self.attributes.slice ('id').merge(other_object.attributes.slice!('id')){|key, oldval, newval|
oldval.nil? ? newval: oldval
})
end