Sorting An Array of Object by Different Attributes - ruby

I have an array of objects containing three different models. Two of them have a common attribute of category, and a third one that we just added doesn't. I'm wanting to sort the array alphabetically using the category name for the first two and the object's name for the third. Since they are all strings, this seems possible, but I don't know how to make it work.
My controller:
def index
#opportunities = Opportunity.non_historical_active.order("title")
#recommendations = Recommendation.active
#fast_content = FastContent.where(:approved => true)
#skills = (#recommendations + #opportunities + #fast_content)
Array displayed in my view:
<% Array(#skills.sort_by{ |skill| skill.opportunity_category.name}).each_with_index do |opp, i| %>
This array worked before we added the #fast_content variable to #skills.

Assuming Opportunity and Recommendation should be sorted by opportunity_category.name and FastContent should be sorted by name, this would work:
#skills.sort_by { |skill|
case skill
when Opportunity, Recommendation
skill.opportunity_category.name
when FastContent
skill.name
end
}
Another option is to add a common method to all three classes:
class Opportunity < ActiveRecord::Base
def sort_name
opportunity_category.name
end
end
class Recommendation < ActiveRecord::Base
def sort_name
opportunity_category.name
end
end
class FastContent < ActiveRecord::Base
def sort_name
name
end
end
And use that instead:
#skills.sort_by(&:sort_name)

Related

Based on user input I would like to find by name for a object within a list I have in my CLI

I am making a CLI it has 100 objects each has a name and I would like to create an option to search my list of 100 objects to find the object by name. What would be the best implementation to use here.
To start with I am assuming in Ruby I can use .find ? My current WIP is below. Any help is appreciated.
class PokeDEXCLI::Pokemon
attr_accessor :name, :id, :height, :weight
##all = []
def initialize(attr_hash)
attr_hash.each do |key, value|
self.send("#{key}=", value) if self.respond_to?("#{key}=")
end
self.save
end
def save
##all << self
end
def self.all
##all
end
end
My thought was I could search by input to find by doing something like this first?
def self.find_by_name(input)
puts " Would you like to search by pokemon name? Please type in your query."
input = gets.chomp
if ##all.include? input
(this is where I am unsure how to compare input to the :name attribute)
end
So I believe I will use the below snippet. My other question is can I add a default argument to name so it will return nil if there is no match?
class Pokemon
def self.find_by_name(name = nil)
##all.select { |pokemon| pokemon.name.include?(name) }
end
end
The Ruby approach here is to use a Hash:
OBJECTS = {
shoe: 'A shoe',
rose: 'A red flower',
dog: 'A yappy dog'
cat: 'Some orange blur'
}
This doesn't have to be a constant as it is here, you could easily have a variable, but if the data never changes constants are more efficient in terms of impact on performance.
Where you can reference it like this:
OBJECTS[:shoe]
Or based on input:
OBJECTS[gets.chomp.to_sym]
If collection accepts objects with already existing names, then use select method to return collection of matched names
class Pokemon
def self.find_by_name(name)
##all.select { |pokemon| pokemon.name == name }
end
end
If you want to find objects which name contains given string, use same approach with different condition
class Pokemon
def self.find_by_name(name)
##all.select { |pokemon| pokemon.name.include?(name) }
end
end
If collection accepts only unique names, then collection could be implemented by using Hash
class Pokemon
##all = {}
def self.find_by_name(name)
##all[name]
end
def save
##all[name] == self
end
end
this class method could help you if your 'Pokemon' class have attr_accessor 'name'
def self.find_by_name(search_string)
result = []
ObjectSpace.each_object(self) { |pokemon| result << pokemon if pokemon.name.include?(search_string) }
result
end
it will return an array of pokemons with name, including the search string

How to call methods from two separate models in Ruby Volt

I have one model (Group) associated with my GroupController, but it has many instances of another model (People) in it. I want to be able to create new instances of People from my GroupController and add it to an array. What is the best way to go about doing this? The following is an excerpt from the GroupController:
class Group < Volt::ModelController
field :people_in_group
def add_people(name)
people_in_group = []
person = People.new(name)
people_in_group << person
end
end
The function breaks when I try to create a new person. Any advice?
Is this supposed to be a model? If so it should inherit from Volt::Model not Volt::ModelController
Thanks!
Try something like this:
class Person < Volt::Model
field :name, String
def initialize(a_name)
#name = a_name
end
end
class Group < Volt::Model
has_many :people
def add_people(name)
people << Person.new(name)
end
end

How can I create instances of a ruby class from a hash array?

I have a module FDParser that reads a csv file and returns a nice array of hashes that each look like this:
{
:name_of_investment => "Zenith Birla",
:type => "half-yearly interest",
:folio_no => "52357",
:principal_amount => "150000",
:date_of_commencement => "14/05/2010",
:period => "3 years",
:rate_of_interest => "11.25"
}
Now I have an Investment class that accepts the above hash as input and transforms each attribute according to what I need.
class Investment
attr_reader :name_of_investment, :type, :folio_no,
:principal_amount, :date_of_commencement,
:period, :rate_of_interest
def initialize(hash_data)
#name = hash_data[:name_of_investment]
#type = hash_data[:type]
#folio_no = hash_data[:folio_no]
#initial_deposit = hash_data[:principal_amount]
#started_on =hash_data[:date_of_commencement]
#term = hash_data[:period]
#rate_of_interest = hash_data[:rate_of_interest]
end
def type
#-- custom transformation here
end
end
I also have a Porfolio class with which I wish to manage a collection of investment objects. Here is what the Portfolio class looks like:
class Portfolio
include Enumerable
attr_reader :investments
def initialize(investments)
#investments = investments
end
def each &block
#investments.each do |investment|
if block_given?
block.call investment
else
yield investment
end
end
end
end
Now what I want is to loop over the investment_data yielded by the module and dynamically create instances of the investment class and then send those instances as input to the Portfolio class.
So far I tried:
FDParser.investment_data.each_with_index do |data, index|
"inv#{index+1}" = Investment.new(data)
end
But obviously this doesn't work because I get a string instead of an object instance. What is the right way to send a collection of instances to a enumerable collection class that can then manage them?
I'm not sure what "send as input to the Portfolio class" means; classes themselves don't accept "input". But if you're just trying to add Investment objects to the #investments instance variable inside an instance of Portfolio, try this:
portfolio = Portfolio.new([])
FDParser.investment_data.each do |data|
portfolio.investments << Investment.new(data)
end
Note that the array literal [] and the return value of portfolio.investments point to the self-same Array object here. This means you could equivalently do this, which arguably is a little clearer:
investments = []
FDParser.investment_data.each do |data|
investments << Investment.new(data)
end
Portfolio.new(investments)
And if you want to play a little code golf, it shrinks further if you use map.
investments = FDParser.investment_data.map {|data| Investment.new(data) }
Portfolio.new(investments)
I think this is a little harder to read than the previous option, though.

How to limit an array's size during push and pop

I want to be able to store a users last 5 comments so I've created an array and am able to add comments to the array using:
comments << message
I only want the last 5 comments. Is there a simple way (setting the arrays size so the 6th one falls off) to achieve this without writing a method or storing every single comment and then only showing the last 5?
You can create your own datastructure that wraps the functionality of an Array in ruby quite easily:
class CommentQueue
include Enumerable
def initialize(num = 5)
#size = num
#queue = Array.new
end
def each(&blk)
#queue.each(&blk)
end
def pop
#queue.pop
end
def push(value)
#queue.shift if #queue.size >= #size
#queue.push(value)
end
def to_a
#queue.to_a
end
def <<(value)
push(value)
end
end
Done.
If I'm not mistaken, you are using Rails. The "Rails way" is to use an association:
class User < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
end
Now you can get the last 5 comments with:
#current_user.comments.last(5)
If you want to / have to store your comments in an array, you could replace your assignment with something like:
comments.push(message).slice!(0...-5)
The above adds message to the array and removes everything but the last 5 elements. It also works with multiple items:
comments.push(message1, message2).slice!(0...-5)
In Ruby arrays are dynamic, which means they grow bigger the more data you put into it, and Ruby does not provide a built in for what you want. So if you want a ring-type data structure, you have to implement it yourself.
A quick and dirty of a ring-array implementation could go something like:
class Ring
def initialize(s)
#r = []
#r.fill(nil, 0, 5)
#s = s
end
def push(e)
#r.shift
#r.push e
end
end
if comments.size < 5 then
comments << new_comment
else
oldest = comments.sort{|e| e.date}.last
if oldest.date < new_comment.date then # comment is newer?
comments[comments.index(oldest)] = new_comment #swapping
end
end

Is there any data structure in Ruby which is similar to Pascal records?

Is there any data structure in Ruby which is similar to Pascal records?
I would like to define a record type which has about 15-20 fields, all of them are strings.
I tried to define a class for this purpose, but I realized that I have to define getter and setter methods:
class Data
def description
#description
end
def size
#size
end
def address
#address
end
.
. all other (about 15 variables...)
.
def description=( value )
#description = value
end
def size=(value)
#size=value
end
def address=(value)
#address=value
end
.
. more setter method (about 15 times...)
.
end
To define all the 15-20 fields and getters/setters this way is quite annoying. Is there any shorter way to do it? For example:
class Data
desc, size, address=""
end
Or something similar, and then I would be able to create a new instance of the Data class:
d=Data.new
and then set the instance variables:
d.desc="bla"
d.size="50.66"
d.address="Bla street 20."
I already have a method, that can parse an XML file with the XMLSimple gem, and I would like to create a record (or class) from the parsed data and return it back with the "return" keyword. Then I want to access the fields of the record simply: d.size, d.address, d.description and so on. For example:
def data_import(fname="1.xml")
data = XmlSimple.xml_in(fname,{'SuppressEmpty' => ""})
d=Data.new()
d.desc=data['desc'][0]
d.size=data['size'][0]
d.address=data['address'][0]
d. ... =
d. ... = (about 15-20 times)
d. ... =
return d
end
my XML (1.xml):
<entity>
<desc>bla</desc>
<size>50.66</size>
<address>Bla street 20.</address>
.
. (15-20 more fields)
.
</entity>
In the Pascal language (and as far as I know in C++) there were a data structure called "record" for this purpose.
You cand use a Hash as a bucket for your attributes/properties or you can make use of Ruby's metaprogramming abilities for generating getters and setters using accessors like this:
attr_accessor :desc, :size, :address
You can also collect the attributes in an array and use splatting like this to expand the array in a list needed by the previously mentioned method:
attributes = [:desc, :size, :address]
attr_accessor *attributes
Yes, there is, it uses the C/C++ name instead of record: Struct. Struct is a class factory, if you call Struct.new, it will return a class which has the desired fields:
Data = Struct.new(:description, :size, :address, …)
If you need to modify the behavior, you can pass a block to Struct.new:
Data = Struct.new(:description, :size, :address, …) do
def my_custom_method; end
end
or use inheritance:
class Data < Struct.new(:description, :size, :address, …)
def my_custom_method; end
end
Then:
d = Data.new
d.description = …
d.size = …
d.address = …
or alternatively:
d = Data.new(…, …, …)

Resources