How to access first item in hash value array - ruby-on-rails

I have a hash whose value is an array of song lyrics (line1, line2, etc..)
Code:
class Song
def initialize(lyrics)
#lyrics = lyrics
end
def get_song_name()
puts #lyrics.keys
end
def get_first_line()
puts #lyrics.values[0]
end
end
wasted = Song.new({"Wasted" => ["I like us better when we're wasted",
"It makes it easier to see"]})
real_world = Song.new("Real World" => ["Straight up what do you want to learn about here", "if i was someone else would this all fall apart"])
wasted.get_song_name()
wasted.get_first_line()
#=>I like us better when we're wasted
#=>It makes it easuer to see
So when I called wasted.get_first_line, I want it to get the first item in the array of the value. I tried doing #lyrics.values[0], but it returns both lines of the song instead of the first one.
How do I accomplish this?

You need to understand that in the above code #lyrics is a Hash. Here is what you are doing and what it translates to:
#lyrics
# => {"Wasted"=>["I like us better when we're wasted", "It makes it easier to see"]}
#lyrics.values
# => [["I like us better when we're wasted", "It makes it easier to see"]]
#lyrics.values[0]
# => ["I like us better when we're wasted", "It makes it easier to see"]
#lyrics.values[0][0]
# => "I like us better when we're wasted"
Therefore to access the first line, you need to get the first element of the values array. i.e.
#lyrics.values[0][0]
or
#lyrics.values.first.first

Lets use this hash for example:
x = {foo: [:bar, :baz]}
x.values # => [[:bar, :baz]]
x.values.first # => [:bar, :baz]
x.values.first.first # => :bar
In other words, #lyrics.values[0] will return the first value in the #lyrics hash, which is the array of two songs. You still have to get the first song out of that array.

This is not the answer to original question, but if I were you, I would modify the class like below. It will be more apt to store song name and lines of lyrics as individual attributes, instead of merging them as a hash - which kind of defies the whole purpose of having Song class.
class Song
attr_accessor :song_name, :lyrics
def initialize(song_name, lyrics)
#song_name = song_name
#lyrics = lyrics
end
end
Please note that you may not need get_first_line method. You could always use Array#first to have same effect:
real_world = Song.new("Real World", ["Line 1", "Line 2"])
puts real_world.lyrics.first # Prints "Line 1"
You can also access lyrics lines using array index
puts real_world.lyrics[1] # Prints "Line 2"

Related

Ruby/Rails: Best way to loop through a csv and set flag when new person is found

I feel like this is programming 101 stuff, but I am going to swallow my pride and ask for help. I have a CSV that I am processing. Here is a sample...
person_id, name, start_date
1111, busta, 1/1/14
1111, busta, 1/4/14
1111, busta, 1/7/14
2222, mista, 1/3/14
2222, mista, 1/1/14
2222, mista, 1/11/14
...and here is a sample of the code I am using to process the rows...
def self.import(file)
student_start_dates = Hash.new {|hsh, key| hsh[key] = [] }
CSV.foreach(file.tempfile, :headers => true) do |row|
student_start_dates[row["person_id"]] << row["start_date"]
#need something in the loop that says hey...when I find a new person_id send this array to the process method
end
end
def self.process(student)
#process something like 1111 => ["1/1/14", "1/4/14", "1/7/14"]
end
So as you can see from the data each student has multiple start dates associated with them. I am trying to build an array of start_dates for each student. When I find a new person_id, then need to 'do some stuff' with my start_date array. My question is what is the best way to add logic that looks for a change in the person_id as I loop through each row in my csv? I know I could set some sort of flag that gets set when the person_id changes, then based on the state of that flag process my start_date array, and reset the flag. However, I'm tried implementing that without much luck. Or when it did it felt 'dirty'. Just hoping a fresh set of eyes will give me some ideas on cleaner code.
A big part of my issue is the best way to set a flag that says "..when you find a new student (new person_id) then call the process method to find the earliest start date.
If I understand this correctly, you're trying to get a resulting hash that would look something like {1111 => ["1/1/14", "1/4/14", "1/7/14"], 2222 => [...], ...}
If so you could use the built in CSV parser and just construct the hash as you loop over each row.
# Create the hash, the default value will be an array
student_start_dates = Hash.new {|hsh, key| hsh[key] = [] }
CSV.foreach(file_name, :headers => true) do |row|
student_start_dates[row["person_id"]] << row["start_date"]
end

Rails association access

I wish I described this better, but it's the best I know how. I have two classes Cars and Colors. Each can have many of each other through a association class CarColors. The association is set up correctly I'm positive of this but I can't seem to get this to work:
#carlist = Cars.includes(:Colors).all
#carlist.colors
ERROR
#carlist[0].colors
WORKS
My question is how can I iterate over the #carlist without declaring a index as in the successful example? Below is a few things I have tried which also fail:
#carlist.each do |c|
c.colors
end
#carlist.each_with_index do |c,i|
c[i].colors
end
Your first example fails because Car.includes(:colors).all returns an array of cars, not a single car, so the following will fail, because #colors is not defined for the array
#cars = Car.includes(:colors).all
#cars.colors #=> NoMethodError, color is not defined for Array
The following will work, because the iterator will have an instance of car
#cars.each do |car|
puts car.colors # => Will print an array of color objects
end
each_with_index will work as well, but it is a bit different, as the first object
is the same as the each loop car object, the second object is the index
#cars.each_with_index do |car, index|
puts car.colors # => Will print an array of color objects
puts #cars[index].colors # => Will print an array of color objects
puts car == #cars[index] # => will print true
end

Rails / Strings: How to output a different parts of a string?

I am trying to split a string, and output the different parts, whats the best practice for rails 3 ?
String: "book_page_title"
Seperator: "_"
I want to have book, page and title as seperate variables, so that
I can perform actions on them..
Any help is appreciated.
Also, I am having trouble finding good reference sites, with examples like PHP have, and suggestions ?
To split:
book,page,title = string.split('_')
And to recombine:
string = [book,page,title].join('_')
use
split('_')
method it gives array.
Try ruby+string+doc in google, you will get http://ruby-doc.org/core/classes/String.html as the first result, and you can see a number of string functions in this link. You can see split there.
splitted_array = "book_page_title".split("_")
=> ["book", "page", "title"]
splitted_array.each do |string|
#..do manipulations here
end
"book_page_title".split("_") will return you array of strings. So you can access every element via [].
splitted = "book_page_title".split("_") # ["book", "page", "title"]
puts splitted[0] # gives "book"
puts splitted[1] # gives "page"
puts splitted[2] # gives "title"
a = "book_page_title".split("_")
a.each do |i|
instance_variable_set("##{i}", "value")
end
#book #=> *value*
#page #=> *value*
#title #=> *value*

How can I reduce repetition in this Ruby on Rails code?

This is a snippet of code from an update method in my application. The method is POSTed an array of user id's in params[:assigned_ users_ list_ id]
The idea is to synchronise the DB associations entries with the ones that were just submitted, by removing the right ones (those that exist in the DB but not the list) and adding the right ones (vise-versa).
#list_assigned_users = User.find(:all, :conditions => { :id => params[:assigned_users_list_id]})
#assigned_users_to_remove = #task.assigned_users - #list_assigned_users
#assigned_users_to_add = #list_assigned_users - #task.assigned_users
#assigned_users_to_add.each do |user|
unless #task.assigned_users.include?(user)
#task.assigned_users << user
end
end
#assigned_users_to_remove.each do |user|
if #task.assigned_users.include?(user)
#task.assigned_users.delete user
end
end
It works - great!
My first questions is, are those 'if' and 'unless' statements totally redundant, or is it prudent to leave them in place?
My next question is, I want to repeat this exact code immediately after this, but with 'subscribed' in place of 'assigned'... To achieve this I just did a find & replace in my text editor, leaving me with almost this code in my app twice. That's hardly in keeping with the DRY principal!
Just to be clear, every instance of the letters 'assigned' becomes 'subscribed'. It is passed params[:subscribed_ users_ list_ id], and uses #task.subscribed_ users.delete user etc...
How can I repeat this code without repeating it?
Thanks as usual
You don't need if and unless statements.
As for the repetition you can make array of hashes representing what you need.
Like this:
[
{ :where_clause => params[:assigned_users_list_id], :user_list => #task.assigned_users} ,
{ :where_clause => params[:subscribed_users_list_id], :user_list => #task.subscribed_users}
] each do |list|
#list_users = User.find(:all, :conditions => { :id => list[:where_clause] })
#users_to_remove = list[:user_list] - #list_users
#users_to_add = #list_users - list[:user_list]
#users_to_add.each do |user|
list[:user_list] << user
end
#users_to_remove.each do |user|
list[:user_list].delete user
end
end
My variable names are not the happiest choice so you can change them to improve readability.
I seem to be missing something here, but aren't you just doing this?
#task.assigned_users = User.find(params[:assigned_users_list_id])

Verifying if an object is in an array of objects in Rails

I'm doing this:
#snippets = Snippet.find :all, :conditions => { :user_id => session[:user_id] }
#snippets.each do |snippet|
snippet.tags.each do |tag|
#tags.push tag
end
end
But if a snippets has the same tag two time, it'll push the object twice.
I want to do something like if #tags.in_object(tag)[...]
Would it be possible? Thanks!
I think there are 2 ways to go about it to get a faster result.
1) Add a condition to your find statement ( in MySQL DISTINCT ). This will return only unique result. DBs in general do much better jobs than regular code at getting results.
2) Instead if testing each time with include, why don't you do uniq after you populate your array.
here is example code
ar = []
data = []
#get some radom sample data
100.times do
data << ((rand*10).to_i)
end
# populate your result array
# 3 ways to do it.
# 1) you can modify your original array with
data.uniq!
# 2) you can populate another array with your unique data
# this doesn't modify your original array
ar.flatten << data.uniq
# 3) you can run a loop if you want to do some sort of additional processing
data.each do |i|
i = i.to_s + "some text" # do whatever you need here
ar << i
end
Depending on the situation you may use either.
But running include on each item in the loop is not the fastest thing IMHO
Good luck
Another way would be to simply concat the #tags and snippet.tags arrays and then strip it of duplicates.
#snippets.each do |snippet|
#tags.concat(snippet.tags)
end
#tags.uniq!
I'm assuming #tags is an Array instance.
Array#include? tests if an object is already included in an array. This uses the == operator, which in ActiveRecord tests for the same instance or another instance of the same type having the same id.
Alternatively, you may be able to use a Set instead of an Array. This will guarantee that no duplicates get added, but is unordered.
You can probably add a group to the query:
Snippet.find :all, :conditions => { :user_id => session[:user_id] }, :group => "tag.name"
Group will depend on how your tag data works, of course.
Or use uniq:
#tags << snippet.tags.uniq

Resources