ruby on rails concatenating two active records objects - ruby-on-rails

I have two complex rails (AR) queries coming from two different methods that I sometimes want to concatenate. The data structure returned in each object is the same I just want to append one onto another.
Here's a simplified example (not my actual code):
#peep1 = Person.find(1)
#peep2 = Person.find(2)
Thought something like this would work:
#peeps = #peep1 << #peep2
or this
#peeps = #peep1 + #peep2
The above is just a simplified example - joining the queries etc won't work in my case.
Edit:
Maybe concatenating is the wrong term.
Here's the output I'd like:
Say #peep1 has:
first_name: Bob
last_name: Smith
and #peep2 has:
first_name: Joe
last_name: Johnson
I want these to be combined into a third object. So if I iterate through #peeps it will contain the data from both previous objects:
#peeps has:
first_name: Bob
last_name: Smith
first_name: Joe
last_name: Johnson
Thanks!

To be frank, nothing that you are describing makes any sense :)
#peep1 and #peep2 each represent a single object -- a single row in the database.
There is no sense in which they can be meaningfully combined.
You can make an array of both of them.
#all_peeps = [#peep1, #peep2]
And then iterate over that.
#all_peeps.each do |peep|
print peep.first_name
end

This worked for me:
> #loop_feed = #job.bids.all
> #bidadd = []
> #loop_feed.each do |loop_feed|
> compare_id = loop_feed.user_id
> #user_search.each do |user|
> if compare_id == user.id
> #bidadd = [#bidadd, loop_feed].flatten
> end
> end
> end

Related

Ruby - calculations with gets.chomp in a loop

I'm very new to Ruby. I've been trying to find a way to do calculations with user inputs. There are two things that I wanted to do:
Output which friend has the most dinosaurs or jellyfish.
Output which friend has the most dinosaurs and jellyfish combined.
I only have this loop requesting for user inputs so far:
f = "yes"
while f == "yes"
print "Enter your name: "
n = gets.chomp.to_f
print "Enter the number of dinosaurs you have: "
d = gets.chomp.to_f
print "Enter the number of jellyfish you have: "
j = gets.chomp.to_f
print "Another friend? (yes/no)"
f = gets.chomp
end
Any help is appreciated. Thank you so much!
UPDATES:
Thank you so much for you all's help. I've realized that I was not specific enough. I'll try to clarify that here.
The output that I want looks something like this (things in ~ ~ are user inputs):
Enter your name: ~ Bob~
Enter the number of dinosaur you have: ~3~
Enter the number of jellyfish you have: ~6~
Another friend? (yes/no) ~yes~
Enter your name: ~ Sally~
Enter the number of dinosaur you have: ~2~
Enter the number of jellyfish you have: ~8~
Another friend? (yes/no) ~no~
Friend who has the most dinosaurs: Bob
Friend who has the most jellyfish: Sally
Friend who has the most dinosaurs and jellyfish combined: Sally
So far, the codes that I wrote only gets me to "Another friend? (yes/no)" but I'm not sure how to ask Ruby to output the last three lines that I want. Can you folks shed some light on this, please?
Thank you very much!
MORE UPDATES:
Thanks for all of your help! Got it figured out with Hash and Array. Thank you!
The answer to your question is three-fold.
Collecting user input
gets returns a line from stdin, including the newline. If the user enters Bobenter then gets returns "Bob\n".
To strip the newline you use String#chomp:
"Bob\n".chomp #=> "Bob"
To convert an input like "3\n" to an integer 3, you can use String#to_i:
"3\n".to_i #=> 3
Note that to_i ignores extraneous characters (including newline), so you can avoid chomp.
Storing data
You probably want to store a user's data in one place. In an object-oriented programming language like Ruby, such "place" is often a class. It can be as simple as:
class User
attr_accessor :name, :dinosaurs, :jellyfish
end
attr_accessor creates getters and setters, so you can read and write a user's attributes via:
user = User.new
user.name = 'Bob'
user.name #=> "Bob"
Since you don't want just one user, you need another place to store the users.
An Array would work just fine:
users = []
user = User.new
user.name = 'Bob'
user.dinosaurs = 3
users << user
users
#=> [#<User #name="Bob", #dinosaurs=3>]
Adding a second user:
user = User.new # <- same variable name, but new object
user.name = 'Sally'
user.dinosaurs = 2
users << user
users
#=> [#<User #name="Bob", #dinosaurs=3>, #<User #name="Sally", #dinosaurs=2>]
Retrieving the users (or their attributes) from the array:
users[0] #=> #<User #name="Bob">
users[0].name #=> "Bob"
users[1].name #=> "Sally"
Calculating with data
Array includes many useful methods from the Enumerable mixin.
To get an element with a maximum value there's max_by – it passes each element (i.e. user) to a block and the block has to return the value you are interested in (e.g. dinosaurs). max_by then returns the user with the highest dinosaurs value:
users.max_by { |u| u.dinosaurs }
#=> #<User #name="Bob">
You can also calculate the value:
users.max_by { |u| u.dinosaurs + u.jellyfish }
But that would currently result in an exception, because we did not set jellyfish above.
Right now you're trying to turn the name into a float (number) by calling to_f on it. It's already a string when it's coming in from the user, and should probably stay a string.
Also, you're currently overwriting each of your variables in every iteration of your loop. So if Bob fills this out, and wants to add a friend, Sally, all of Bob's information is overwritten by Sally's.
Instead, you'll need to create a Hash or Array, then add each user to it one by one from your loop.
continue = "yes"
users = {}
while continue == "yes"
print "Enter your name: "
name = gets.chomp.to_f
print "Enter the number of dinosaurs you have: "
dinosaursCount = gets.chomp.to_f
print "Enter the number of jellyfish you have: "
jellyfishCount = gets.chomp.to_f
users[name] = {dinosaurs: dinosaursCount, jellyfish: jellyfishCount}
print "Another friend? (yes/no)"
continue = gets.chomp
end
Now if Bob and Sally have both been added through the command line, you can get their data by doing:
users.Bob #{dinosaurs: 10, jellyfish: 10}
users.Bob.dinosaurs #10
users.Bob.jellyfish #10

Ruby on Rails Tutorial: Defining a hash with symbol keys

Regarding the exercises in Michael Hartl's RoR Tutorial in lesson 4.3.3 (Hashes & Symbols):
"Define a hash with symbol keys corresponding to name, email, and a “password digest”, and values equal to your name, your email address, and a random string of 16 lower-case letters."
I am hoping to get some input and/or alternative & 'better' solutions to this (or at least some criticism regarding my solution).
def my_hash
a = ('a'..'z').to_a.shuffle[0..15].join
b = { name: "John", email: "johndoe#gmail.com", password: a }
return b
end
puts my_hash
(Yes I realize this is a very simple exercise and apologize if it has been asked before.)
There are many 2 improvements could be made:
Use Array#sample to get random letters (it has an advantage: the letter might in fact repeat in the password, while shuffle[0..15] will return 16 distinct letters);
Avoid redundant local variables and especially return.
Here you go:
def my_hash
{
name: "John",
email: "johndoe#gmail.com",
password: ('a'..'z').to_a.sample(16).join
}
end
puts my_hash
Bonus:
I accidentaly found the third glitch in the original code. It should probably be:
def my_hash
{
name: "Brandon",
email: "brandon.elder#gmail.com",
password: ('a'..'z').to_a.sample(16).join
}
end
:)

Split one data row into multiple based on # of instances in one of the cells

Right now, when a user creates a Request object, the output looks like this:
<Request id: 1, email: "abc#yahoo.com", items: ["one item", "two item"], created_at: "2014-04-24 05:14:24", edit_id: "gwe3EX4q2EUVk7FQCRUJug">
I am trying to convert this so that if items > 1, the output is split into two Request instances like so:
<Request id: 1, email: "abc#yahoo.com", items: "one item", created_at: "2014-04-24 05:14:24", edit_id: "gwe3EX4q2EUVk7FQCRUJug">
<Request id: 2, email: "abc#yahoo.com", items: "two item", created_at: "2014-04-24 05:14:24", edit_id: "gwe3EX4q2EUVk7FQCRUJug">
What's complicating this further is that also I want the Request.id to increment as per usual, but NOT the created_at and edit_id, both of which are automatically generated when a Request.create is called.
How can I do this? The code snippet of the method I've worked on so far (certainly not working, I'm stumped)...
self.items.each_with_index do |item, index|
index = Request.save(:email => self.email, :items => item, :created_at => self.created_at, :edit_id => self.edit_id)
end
Thanks!
FYI the code for the edit_id method:
def Request.new_edit_id
SecureRandom.urlsafe_base64
end
def create_edit_id
self.edit_id = Request.new_edit_id
end
UPDATE:
Also, the clone or dup methods that create shallow copies don't work in this case. The first answer on this question: How do I copy a hash in Ruby? works for a simple array, but for a complex hash like what I have, changing the cloned copy will actually change the original as well.
I'm going to explore a deep copy and see if I can get that to work!
In the process of figuring this out, I learned that it's bad practice to store array data like this in an object. I should just be making another data table and relating the two.
That said, if any bit of this code is helpful, here's what did it:
#requestrecord.items = ["water filter", "tent"]
num_of_requests = #requestrecord.items.count
i = 0
cloned_request = Hash.new
while i < num_of_requests do
cloned_request[i] = Marshal.load(Marshal.dump(#requestrecord)) #creates a cloned_request[0] = #requestrecord and cloned_request[1] = #requestrecord
cloned_request[i].items = #requestrecord.items.slice(i) #disaggregates the items into the cloned_requests; cloned_request[0].items = "water filter", cloned_request[1].items = "tent"
i += 1
end
Note the Marshal.load(Marshal.dump(#requestrecord)) creates a deep copy and therefore works where clone and dup would not. (They would create references to the original hash. If the original hash changed, so would they. Moreover, I found out that in complex hashes like what I was building, changing a cloned hash actually changes the original as well!)

How do I incorporate the index in variable name when looping with each_with_index?

How would I incorporate the index in a variable name so that I could access the different group objects?
This is my db/seeds.rb file:
u = User.create( email: "yeah#foo.com", password: "yeah", password_confirmation: "yeah")
groups_list = ["Math Club", "Science Class", "Economics Class"]
groups_list.each_with_index do |name, index|
"g#{index}" = u.groups.create(name: name)
end
When you start needing dynamically defined local variables you have a code smell and you know you should reconsider what you're doing.
It looks like you would be better served with a map call, converting the groups_list from an array of strings into an array of Group objects belonging to u:
groups_list.map { |name| u.groups.create name: name }
#=> [<Group name="Math Club">, …]
Then you can iterate through the groups or pass them into another method as an array, no trickery involved.

Rails Replace Attributes in Arrays

I have a list of incorrect cities name in Philippines:
>> a = City.find_all_by_country_id(4)
=> [#<City id: 91, name: "Alaminos", country_id: 4, created_at: "2009-11-12 04:06:14", updated_at: "2009-11-12 04:06:14">, #<City id: 92, name: "Angeles", country_id: 4, created_at: "2009-11-12 04:06:14", ...
And I wanted to replace all the names with the correct one:
=> b = ["Abra", "Agusan del Norte", "Agusan del Sur", ...
I wanted to use the replace method because I wanted to update the existing city id, inserting/truncating them only if necessary.
But I still can't figure this one out, since a is an array of arrays (correct me if I am wrong) while b is just a simple, down-to-earth array.
a should be an array of City models. For example if you wanted to change the city name of the city id 91 (the first record) to "Abra" (the first element in the array) you would just do a[0].name = b[0]. I'm a little unclear on what exactly you're trying to do, but hopefully this will get you over the syntax part of the problem.
Have a look at at Array#zip, and ActiveRecord::Base#update_attribute. As Andy Gaskell points out, a is an array of City objects. So zip can be called on a, and update_attribute can be called on any element of a.
The short simple way of doing what you want is this:
a.zip(b){|array| array[0].update_attribute(:name, array[1])}
Zip will turn multiple arrays into an array of arrays. Where each index of the new array is an array composed of elements in the source arrays of the same index.
a.zip(b) = c #=> ∀i: c[i] = [a[i],b[i]]
If you pass a block to zip, Ruby will yield each array in c to the block. It's a handy shortcut for a.zip(b).collect(&block).
In the code above, array[0] = a[i], and array[1] = b[i], each iteration supplies a different value of i. update_attributes will update the record in the database bypassing validations and callbacks.
Caveats:
If b has more elements then a you will get No Method Errors.
If a has more elements than b, the extra elements will have their name set to "".
Again, Update_attributes bypasses validations and saves the updated record automatically. If this is not too your liking you can replace the innards of the block with:
array[0].name = array[1]; array[0].save
I decided to use a migration file instead, so this is the code:
class AddProvinces < ActiveRecord::Migration
def self.up
philippines = Country.find_by_name('Philippines')
old_cities = philippines.cities
new_cities = (['Abra', 'Agusan del Norte', 'And all the cities...'])
old_cities.each do |c|
unless new_cities.blank?
City.update(c.id, :name => new_cities.first)
new_cities.delete(new_cities.first)
else
City.delete(c.id)
end
end
unless new_cities.blank?
new_cities.each do |c|
City.create(:name => c, :country_id => 'philippines.id')
end
end
end
def self.down
end
end

Resources