Get unique objects on has_and_belongs_to_many - activerecord

I have 3 entities: Feeds, Episodes, Series. Feeds has_and_belongs_to_many Episodes. Episodes belongs_to Series.
What I would like is all the series for a feed, and the number of episodes, in that series, in that feed.

You can produce the complete list of all series for a given set of episodes by first finding the IDs of all series...
# uniq insures that any duplicates are removed
ids = feed.episodes.map(&:series_id).uniq
And then finding the series themselves:
series = Series.where(id: ids)
You can find the number of episodes for each series with a simple count:
series.each do |s|
puts "Series #{s.name} has #{s.episodes.count} episodes"
end

meagar’s answer led me to the following solution:
ids = #feed.episodes.pluck(:series_id)
#series = Series.find(ids)
#episode_counts = Hash.new(0)
ids.each do |id|
#episode_counts[id] += 1
end

Related

Ordering of relation based on associated table in rails4

I have 2 models as below:
class Student < ActiveRecord::Base
has_many :marks
end
class Mark < ActiveRecord::Base
belongs_to :student
end
point is a field in marks table. For each student there are many entries in the marks table.
I need to get a list of students based on the order of highest total points.
I tried as below:
#students = Student.all.collect{|p| [p,p.marks.pluck(:point).sum]}
#students.sort_by { |h| h[1] }.reverse!
But it will return 2 items in each array, one the object and next the total points.
Is there a better solution please.
Thanks,
Jissy
This should do the trick:
Student.joins(:marks).select('students.*, SUM(marks.point) AS total_point').group(:id).order('total_point DESC')
You can do it like this.
Student.joins(:marks).select('id, sum(marks.point) as total_marks').group('students.id').order('total_marks desc')
If you are still unable to run it please modify it or let me know.In place of id in select you can select any column(s).

calculations based on matching keys' values in Ruby hashes

I'm making artist recommendation program that will match a hash showing artists that a user has seen live and how many times, against a hash showing artists that a given artist has shared a bill with and how many times. The match score is calculated based on these numbers. If a user has seen some artist x amount of times and a given artist has played with this artist at least once, like this:
user = {"artist7" => 3, "artist8" => 1}
artist1 = {"artist6" => 7, "artist7" => 7}
match = 0
user.each do |k, v|
if artist1[k]
match += (1 - ((user[k] - artist1[k])/(user[k] + artist1[k])).abs)
end
end
I have tried this out in irb and the value of match does not change.
All your inputs are integers, so ruby uses integer division. It looks like that's likely to produce 1, and 1 - 1 is zero. Add some to_f to your equation to use float division instead, e.g.:
match += (1 - ((user[k] - artist1[k]).to_f/(user[k] + artist1[k])).abs)

Ruby 1.8, Getting all Hash values related to a specific key

I have the following code which takes a CSV file and feed it into a hash. Here I want to list all the names of songs for a specific genre. Like, all the "Western" songs. This code only gives me one song for a specific genre. Does anyone have suggestions on how I can get it to show all the Western songs (for example)
def self.musicGenre()
puts "Ballad,Blues,Bluegrass,Popular,Western"
require 'csv'
#musicGenreHash={} #create the hash for musicName, blank
CSV.foreach('musiclist.txt')do |row|
name,genre,composer,location = row
#musicGenreHash[genre]=name
end
def self.genre(songGenreChoice)
#musicGenreHash[songGenreChoice]
end
puts "Please type in the genre of your choice:"
songGenreChoice=gets.chomp
puts "Thesearethesongsavailable in the#{songGenreChoice} genre:"+genre("#{songGenreChoice}").inspect
end #musicGenre
You are storing only one value against a genre. You need to modify your code so that values in the hash are array, and you collect the names of all songs belonging to particular genre in that array.
Change
#musicGenreHash[genre]=name
to
#musicGenreHash[genre] = (#musicGenreHash[genre] || []) << name

How to re-order an array of objects based on a list of ids

I have an object Task as you can see below:
class Task
include HTTParty
attr_accessor :id, :name, :assignee_status, :order
def initialize(id, name, assignee_status)
self.id = id
self.name = name
self.status = status
self.order = order
end
end
So, when I load a list of tasks, I give them a specific order, like this:
i = 0
#tasks.each do |at|
at.order = i
i += 100
end
This list is, then, sent over json to a client app which shows is to the user and allows for drag'n'drop. After some task is re-ordered, the front-end app sends a list back to the server with all the task ids in the new order.
Example:
23,45,74,22,11,98,23
What I want to do is the re-order the array of objects based on the ids that I found. What I thought about doing was to set all order to 0 and then search each object individually on the matrix and set their priority accordingly. But this just feels wrong... feels to computing intensive.
Is there a clever way to do something like:
ArrayOfTaskObjects.orderbyAttribute(id) ??
Maybe the easy way is to iterate the params with a index, something like:
params['task_ids'].split(",").each_with_index do |task_id, idx|
task = lookup_the_task_based_on_id
task.order = idx
end
def sort_by_id(array)
r = array.inject({}) { |hash, element| hash.merge!(element.id => element) }
temp_array = r.sort_by {|k,_| k}
temp_array.flatten!.grep(temp_array.last.class)
end
What's happening:
The sort_by_id method takes in an array of objects - this being your Task array.
In the first line of code a hash is created which stores each Task id as the key and the Task object as the value.
The second line of code sorts the hash based on the keys (note that the r.sort_by method returns a two dimensional array, ex. [[23, Task], [44, Task], [54, Task]]).
And finally the third line of code flattens your two dimensional array into a one dimensional array and greps removes all id's from the two dimensional array leaving the array of Tasks in order.

Dynamically Create Arrays in Ruby

Is there a way to dynamically create arrays in Ruby? For example, let's say I wanted to loop through an array of books as input by a user:
books = gets.chomp
The user inputs:
"The Great Gatsby, Crime and Punishment, Dracula, Fahrenheit 451,
Pride and Prejudice, Sense and Sensibility, Slaughterhouse-Five,
The Adventures of Huckleberry Finn"
I turn this into an array:
books_array = books.split(", ")
Now, for each book the user input, I'd like to Ruby to create an array. Pseudo-code to do that:
x = 0
books_array.count.times do
x += 1
puts "Please input weekly sales of #{books_array[x]} separated by a comma."
weekly_sales = gets.chomp.split(",")
end
Obviously this doesn't work. It would just re-define weekly_sales over and over again. Is there a way to achieve what I'm after, and with each loop of the .times method create a new array?
weekly_sales = {}
puts 'Please enter a list of books'
book_list = gets.chomp
books = book_list.split(',')
books.each do |book|
puts "Please input weekly sales of #{book} separated by a comma."
weekly_sales[book] = gets.chomp.split(',')
end
In ruby, there is a concept of a hash, which is a key/value pair. In this case, weekly_sales is the hash, we are using the book name as the key, and the array as the value.
A small change I made to your code is instead of doing books.count.times to define the loop and then dereference array elements with the counter, each is a much nicer way to iterate through a collection.
The "push" command will append items to the end of an array.
Ruby Docs->Array->push
result = "The Great Gatsby, Crime and Punishment, Dracula, Fahrenheit 451,
Pride and Prejudice, Sense and Sensibility, Slaughterhouse-Five,
The Adventures of Huckleberry Finn".split(/,\s*/).map do |b|
puts "Please input weekly sales of #{b} separated by a comma."
gets.chomp.split(',') # .map { |e| e.to_i }
end
p result
Remove the comment if you would like the input strings converted to numbers
One way or another you need a more powerful data structure.
Your post gravitates toward the idea that weekly_sales would be an array paralleling the books array. The drawback of this approach is that you have to maintain the parallelism of these two arrays yourself.
A somewhat better solution is to use the book title as a key to hash of arrays, as several answers have suggested. For example: weekly_sales['Fahrenheit 451'] would hold an array of sales data for that book. This approach hinges on the uniqueness of the book titles and has other drawbacks.
A more robust approach, which you might want to consider, is to bundle together each book's info into one package.
At the simplest end of the spectrum would be a list of hashes. Each book would be a self-contained unit along these lines:
books = [
{
'title' => 'Fahrenheit 451',
'sales' => [1,2,3],
},
{
'title' => 'Slaughterhouse-Five',
'sales' => [123,456],
},
]
puts books[1]['title']
At the other end of the spectrum would be to create a proper Book class.
And an intermediate approach would be to use a Struct (or an OpenStruct), which occupies a middle ground between hashes and full-blown objects. For example:
# Define the attributes that a Book will have.
Book = Struct.new(:title, :weekly_sales)
books = []
# Simulate some user input.
books_raw_input = "Fahrenheit 451,Slaughterhouse-Five\n"
sales_raw_input = ['1,2,3', '44,55,66,77']
books_raw_input.chomp.split(',').each do |t|
ws = sales_raw_input.shift.split(",")
# Create a new Book.
books.push Book.new(t, ws)
end
# Now each book is a handy bundle of information.
books.each do |b|
puts b.title
puts b.weekly_sales.join(', ')
end
Are you happy to end up with an array of arrays? In which this might be useful:
book_sales = books_array.collect do |book|
puts "Please input weekly sales of #{books_array[0]} separated by a comma."
gets.chomp.split(",").collect{ |s| s.to_i }
end
Looking at it, you might prefer a hash, keyed by book. Something like this:
book_sales = books_array.inject({}) do |hash, book|
puts "Please input weekly sales of #{books_array[0]} separated by a comma."
weekly_sales = gets.chomp.split(",").collect{ |s| s.to_i }
hash[book] = weekly_sales
end
This solution assumes that there will never be a duplicate book title. I figure that is pretty safe, yes?
input = "A list of words"
hash = {}
input.split(/\s+/).collect { |word| hash[word] = [] }
# Now do whatever with each entry
hash.each do |word,ary|
ary << ...
end

Resources