In my church website, many members are married couples. I have a csv file from the membership database, which uses a seven-digit identifier that looks like this:
00903 1
and the spouse of that person will be
00903 2
What I would like to do is to create a table of couples and their anniversaries. How would I get a row to contain
her_first_name:string his_first_name:string family_name:string.
In other words, how would I combine information from two records into one record, when the only way to identify how the two are linked is the last "digit" in a string? Is Regex involved, and if so, what does the query look like? Thanks
My approach would be to do something like this (I'm assuming that it's always the same format, where for a couple, the first 5 digits are actually the same. I'm also assuming that you need to check if each record has a spouse, not that you already know they are married. Finally, I'm assuming you have a model named Couple with an associated couples table, a model named Person for individual members which has ind_id field as the identifier, and I'm making up the row positions for each field.):
individuals = [] # an array that can hold each record until a spouse is found in the csv
csv.each do |row|
id_regexp = Regexp.new(row[0][0..4] + '.') # creates a regexp out of the first 5 characters in the id, then adds '.' to match any following characters
spouse = individuals.select { |individual| individual.ind_id =~ id_regexp }.first #searches individuals for that regexp, returns the first instance if it exists, or nil otherwise
if spouse
couple = Couple.new(wife_name: spouse.first_name, husband_name: row[1], last_name: row[2], anniversary: row[3], etc.) # if a spouse was found create a Couple record
couple.save!
individuals.delete(spouse) # remove this record from the array so there is one less record to iterate over on the next loop
else
individuals << Person.new(first_name: row[1], last_name: row[2], etc.) # build a Person and store it in the array. You could persist the Person to the db if you are saving individuals with this import as well
end
end
Now, you could add a check in there, given a spouse is found, that checks to see which is the husband and which is the wife depending on the 1 or the 2 in the ind_id[6] slot:
if spouse.ind_id[6] == '1'
couple.wife_name = spouse.first_name
couple.husband_name = row[1]
else
couple.husband_name = spouse.first_name
couple.wife_name = row[1]
end
This would obviously go in between the couple = Couple.new(... and the couple.save!.
Anyway, I had to assume a lot because of the amount of detail provided in your OP, but hopefully this gives you a template to go off of. If you don't have Person class, you could just create a hash and push that into individuals each time and compare individual[:ind_id] in the select.
Note
I used Regexp because you specified this. Could just as easily do
base_id = row[0][0..4]
spouse = individuals.select { |individual| individual.ind_id[0..4] == base_id }.first
Related
I have an "ITEMS" database made of; ITEM_ID, OWNER_ID and VALUE.
An owner can own unlimited items. An item can only have one owner.
And I have an "OWNER" database made of; ID, NAME
I want to find the NAMES of top 10 RICHEST (wealthier) people. How can I do that?
First, I need to sum the values of an owner_id; than compare that with others?
Here is what you can do :
Item.group(:owner_id) # grouping Items by owner id
.select("SUM(value) as sum") # summing values of each group
.order("sum DESC") # ordering resulting records by the sum value
.limit(10) # giving the top 10 records
It is kinda long solution but it worked for me:
toplist = []
all_owners = Owner.all
all_owners.each do |owner|
name = Owner.find(owner).name
owner_value = Item.where(owner_id: owner).sum(:value)
toplist << [owner_value,name]
end
#top10 = toplist.sort.last(10).reverse
I have this loop:
stations = Station.where(...)
stations.all.each do |s|
if s.city_id == city.id
show_stations << s
end
end
This works well, but because of looping the all the data, I think it's kinda slow. I've tried to rewrite it with using select, like this:
show_stations << stations.select { |station| station.city_id == city.id}
But the amount of saved data into show_stations is different compared to the each version and then, the data are in different format (array/object).
Is there any better/faster way to rewrite the loop version?
The fastest version of this maybe the built-in rails ActiveRecord method for finding associated objects.
So provided your Station model contains this:
class Station < ActiveRecord::Base
belongs_to :city
And your City model contains this:
class City < ActiveRecord::Base
has_many :stations
Then rails automatically generates the method city.stations which automatically fetches the stations which contain that city's id from the database. It should be pretty optimized.
If you want to make it even faster then you can add add_index :stations, :city_id to your table in a migration and it will retrieve faster. Note that this only saves time when you have a lot of stations to search through.
If you need to make it an array you can just convert it after with city.stations.to_a. And if you wanted to narrow it further, just use the select method and add the conditions that you wanted to previously add in your Station.where(...) statement.
(e.g. city.stations.to_a.select { |item| your_filter })
You should also cache the query results like
stations ||= Station.where("your where").where(:city_id => city.id)
Maybe you need to include into the where clause the city parameter:
stations = Station.where("your where").where(:city_id => city.id)
or the same
stations = Station.where("your where").where('city_id = ?', city.id)
Station appears to be an active record model. If that is the case, and you don't need all the stations, you can add the city.id filter to your where statement.
The issue you're having now is that you're adding the array returned from select as the last item of show_stations. If you want show_stations to only contain stations that match city.id then use show_stations = ... rather than show_stations << .... If you want show_stations to contain what it already contains plus the stations that match city.id then use show_stations + stations.select { |station| station.city_id == city.id }. (There are a number of other approaches for adding two arrays together.)
I have the following resource relationship:
Class Course < ActiveRecord::Base
has_many :track_courses
has_many :tracks, through: :track_courses
end
as well as a mirroring relationship inside the Track model. The TrackCourse table which connects these models has these rows:
id: primary key
track_id: represents the track
course_id: represents the course
position: the ordering of the course inside that track
I want to allow admin users to be able to update the courses in each track via ajax. I have a list on the front-end that is being passed to the controller as a hash:
front_end_list = { course_id => position }
which represents the object and its position on the front-end sortable.
I'm also looking up the list of existing courses in that track:
existing_courses = TrackCourse.where("track_id = ?", track_id).all
GOAL: Compare these two lists and syncronize the database entries according to the front-end list. Essentially, if the user inserts Course 15 into position 2 on the webpage, I need to either insert that entry into TrackCourse table (if it doesn't exist) or update its position (if it exists). And vice versa for remove.
What is the best way of doing this? Do ActiveRecord/ActiveRelation provide methods for it? Or do I have to write something myself?
UPDATE: I found a gem called acts_as_list, but it seems to be designed for ActiveRecord tables as opposed to ActiveRelation. It essentially expects position values to be unique, whereas in TrackCourse there can be multiple course with same position (in different tracks).
I figured out a solution. I'll post my code here in case it helps anyone else down the line.
I have this method in my controller that processes the ajax request from the front-end:
def sort
track_id = params[:track_id]
courses_in_list = {}
params[:course].each do |courseid|
position = params[:course].index(courseid)
courses_in_list[courseid.to_i] = position
end
existing_courses_in_track = {}
TrackCourse.where("track_id = ?", track_id).to_a.each do |track_course|
existing_courses_in_track[track_course.course_id] = track_course.position
end
if courses_in_list.length < existing_courses_in_track.length
existing_courses_in_track.each do |courseid, position|
if courses_in_list[courseid].nil?
track_course = TrackCourse.where(track_id: track_id, course_id: courseid).first
track_course.remove_from_list
track_course.destroy!
end
end
else
if existing_courses_in_track.empty?
track_course = TrackCourse.new(track_id: track_id,
course_id: courses_in_list.keys[0])
track_course.insert_at(courses_in_list.values[0])
p "first track!"
else
courses_in_list.each do |courseid, position|
track_exists = false
if !existing_courses_in_track[courseid].nil?
track_course_position = existing_courses_in_track[courseid]
track_exists = true
end
if !track_exists
TrackCourse.new(track_id: track_id, course_id: courseid).insert_at(position)
else
p "else statement"
track_course = TrackCourse.where(track_id: track_id, course_id: courseid).first
track_course.update_attribute(:position, position)
end
end
end
end
render :nothing => true
end
Essentially, I'm building two hashes, one based on the list of front-end items and their position, and one based on the database courses and their position. I then compare them. If the front-end list is shorter, that means the user removed an item, so I iterate through the backend list, find the extra item, and remove it. Then I employ a similar mechanism for adding items to the list and resorting the list. The acts_as_list gem really helps with keeping things in the correct position. However, I did have to limit its scope when I included it in my model to ensure it runs only on relationships (TrackCourses) with a specific track_id.
I have a User model with a name attribute. In my PagesController, I want to set an instance variable equal to all of the User objects, but I want to order them based on last name. I know I can do:
#sortedusers = User.order(:name => :asc)
to order the users based on their name attribute, but how do I do it based on last name? (i.e. how do I order the Users based on the last word of their name attributes?
Thanks.
Define a virtual attribute last name in your model
def last_name
name.split(' ').last
end
and then
User.order(:last_name => :asc)
I'd suggest storing the users' last name separately. Barring that, you can use sort_by, like so:
items.sort_by! { |a| a.split(' ') } or something along those lines. Note, this most likely cannot be used straight, it's merely intended to point you in the right direction, as most of my answers are.
I need to be able to compare two columns of every row in a CSV with my own database.
For example, a row looks like this:
Headers => "Zipcode", "HouseNumber"
Row 1 => "5435 ZX", "43"
First I need to find a Company in my own database, based on zipcode and housenumber, and, if this returns true, then I need to import the rest of the columns in this row. I know how to solve this.
But, I also need to be able to keep track of the imported rows, because I need to split up the total records in small chunks, so I need to add an extra field to every row once imported.
To summarize:
How can I add an extra field ("imported") to every row once imported in the following loop:
CSV.foreach('reviews.csv', :headers => true) do |row|
Review.create(row.to_hash)
end
This code snippet will create the Review if you can find the Company. You'll have to clean it up to match it to the correct attribute names for the companies table.
CSV.foreach('reviews.csv', :headers => true) do |row|
Review.create(row.to_hash) if Company.where("Zipcode = ? and HouseNumber = ?",
row.Zipcode, row.HouseNumber)
end
To add an additional column, to the following:
CSV.foreach('reviews.csv', :headers => true) do |row|
if company = Company.where("Zipcode = ? and HouseNumber = ?",
row.Zipcode, row.HouseNumber)
row[:imported] = true
Review.create(row.to_hash)
end
This assumes the imported column exists in your table.
I think we may need more information to give you a more complete answer, but the ideas here should get you close.
Seems like the simplest thing to do would be to maintain the "imported" state in a separate hash, like so:
imported = {}
CSV.read('reviews.csv').each_with_index do |row, line|
if <your condition> && !imported[line]
# create model
imported[line] = true
end
end
Then just keep that hash around until you need to read the next block, and re-run. The && !imported[line] bit will prevent reading a line twice.
If that's not what you're looking for, you'll need to clarify your question a bit. :-)