Migrating legacy data to new model in rails - ruby-on-rails

I'm getting started with Rails again and am running into a conundrum I find intimidating at the moment. I'm somewhat of a noob when it comes to working with databases, so please forgive me if this is fairly basic.
I have an older Rails app with a data model I no longer wish to conform to. The model should be deprecated in favor of a lighter, less complex one.
The older app is also very monolithic, so I'm trying to break it up into smaller service components.
So this leads me to my question, since it's generally frowned upon to use multiple databases from a single model… what would be the best method for translating data stored in the old model to my new model, one service at a time?
For example, let us suppose I have a user model in both the old and the new. In the old model, the user has many columns, not all of which should make it to the new model.
An example of this could be a change from a user being limited to a single address in the old model to being able to assign a one to many relationship where addresses are split off in their own model and simply referenced using a foreign key or something.
EDIT 1:
The goal ultimately is to siphon the data from the legacy model's database into the new model's database as easily as possible, one dataset at a time.
EDIT 2:
Originally posted from my mobile. Here are a couple examples which may help with suggestions.
OLD MODEL
create_table "brands", force: :cascade do |t|
t.string "name"
t.string "url"
t.string "logo"
t.boolean "verified"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "hidden", default: false
t.string "facebook_url"
t.string "twitter_handle"
t.string "pinterest_handle"
t.string "google_plus_url"
t.string "address_street1"
t.string "address_street2"
t.string "address_street3"
t.string "address_city"
t.string "address_state"
t.string "address_zip"
t.string "address_country"
t.string "email"
t.string "phone"
t.string "story_title"
t.text "story_text"
t.string "story_photo"
end
NEW MODEL
create_table "companies", force: :cascade do |t|
t.string "companyName", null: false
t.string "companyURL", null: false
t.boolean "companyIsActive", default: true, null: false
t.boolean "companyDataIsVerified", default: false, null: false
t.string "companyLogoFileURL"
t.datetime "companyFoundedOnDate"
t.integer "companyHQLocationID"
t.integer "companyParentCompanyID"
t.integer "companyFirstSuggestedByID"
t.string "companyFacebookURL"
t.string "companyGooglePlusURL"
t.string "companyInstagramURL"
t.string "companyPinterestURL"
t.string "companyTwitterURL"
t.string "companyStoryTitle"
t.text "companyStoryContent"
t.string "companyStoryImageFileURL"
t.boolean "companyIsHiddenFromIndex", default: false, null: false
t.integer "companyDataScraperID"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
So, basically... I want to be able to take data from the old model, say a brands "name" column and siphon its related values to the new model so the value ends up in the companies "companyName" column of a totally different postgresql instance.

Having done this multiple times, I can tell you that the easiest thing to do is to create a simple rake task that iterates the first collection and creates items in the new collection.
There is no need to use anything like DataMapper. You already have ActiveRecord and can simply define which DB connection to use for each model.
In your config/database.yml:
brand_database:
adapter: postgresql
host: brand_host
username: brand_user
password: brand_pass
database: brand_db
company_database:
adapter: postgresql
host: company_host
username: company_user
password: company_pass
database: company_db
In your models:
class Brand < ActiveRecord::Base
establish_connection :brand_database
end
class Company < ActiveRecord::Base
establish_connection :company_database
end
In your new rake task (lib/tasks/db.rake):
# lib/tasks/db.rake
namespace :db do
desc "Migrate brand records to company records"
task :migrate_brands_to_companies, [] => :environment do
Brand.find_each do |brand|
Company.find_or_initialize_by(companyName: brand.name) do |company|
puts "\n\tCreating Company record for #{brand.name}"
company.companyURL = brand.url
company.companyLogoFileURL = brand.logo
company.companyTwitterURL = "https://twitter.com/#{brand.twitter_handle}"
company.companyIsHiddenFromIndex = brand.hidden
company.created_at = brand.created_at
company.updated_at = brand.updated_at
company.save!
end
end
end
end
Finally, run the rake task:
$ rake db:migrate_brands_to_companies
I need to say this: Rails is built using a solid convention. Failing to adhere to that convention will cause problems and additional expense, everytime. I have seen this many many times. Every single time I have seen someone deviate from that convention, they run into far more trouble than they would have ever expected. They break a lot of the "Rails magic".

Taking a TDD approach would certainly help you cover more ground.
Look at DataMapper, which you can use in a Rake task or completely separate Ruby-script. This way, you can iterate over the app data (from Active Record) and pass it to the new Postgres DB via DataMapper.
You can connect to the new DB like this,
DataMapper.setup(:default, 'postgres://user:password#hostname/database')

Related

ActiveRecord search for similar entries to show page selection (Rails 5)

I have a show page generated from a very large database, queried by #listing = Listing.find(params[:listing_id]) in the controller. At the bottom of the page I also have links to three other listings; for now they are queried by #listings = Listing.last(3), but I am trying to pick three similar listings - by community, or by a price range.
example - Listing.where(:price => 500000..600000).limit(2)
However...I am at a loss as to how to do this in comparison to the active page listing. How would I pick listings from the same community, or within a certain price range of the same record, for instance? If someone could direct me as to how to make this connection I would appreciate it (I'm fairly junior, but also am really learning to enjoy ActiveRecord, and want to expand what I can do with it).
Before anyone asks, here's my schema:
create_table "listings", force: :cascade do |t|
t.string "mls"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "community"
t.string "description"
t.integer "price"
t.string "status"
t.string "address"
end
It looks like you need something like this:
price_range = (#listing.price - 100)..(#listing.price + 100)
Listing.where(price: price_range, community: #listing.community).limit(3)
You were on the right track!

Heroku PG::UndefinedColumn: ERROR does not exist

This runs fine on the rails server but doesn't work on Heroku and says something went wrong etc. This basically happens when I search for b_pages via BPage_name:
Schema:
create_table "b_pages", force: :cascade do |t|
t.string "Bpage_name"
t.string "first_post"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "profile_img_file_name"
t.string "profile_img_content_type"
t.integer "profile_img_file_size"
t.datetime "profile_img_updated_at"
t.string "banner_img_file_name"
t.string "banner_img_content_type"
t.integer "banner_img_file_size"
t.datetime "banner_img_updated_at"
t.integer "user_id"
t.string "status"
t.text "bio"
t.string "relationship"
t.text "whitelist"
t.text "blacklist"
end
b_page.rb:
def self.search(query)
where("Bpage_name like ?", "%#{query.downcase}%")
end
Identifiers (such as table and column names) are case insensitive in SQL. However, if you double quote them when you create the table then they are case sensitive. Standard SQL folds unquoted identifiers to upper case but PostgreSQL folds them to lower case, hence the complain about bpage_name being an unknown column when you say:
where("Bpage_name like ?", "%#{query.downcase}%")
ActiveRecord double quotes all the identifiers when it creates tables so if you have any column names that are case sensitive (such as Bpage_name) then you have to double quote them everywhere you use them in SQL snippets:
where('"Bpage_name" like ?', "%#{query.downcase}%")
# -----^----------^
The recommended practice with PostgreSQL (and Rails/ActiveRecord for that matter) is to use lower case column names with underscores to separate words. Saying this:
t.string "bpage_name"
in your migration would be more consistent with both conventions and would avoid your quoting problem completely.
When you say something like:
where(:Bpage_name => 'pancakes')
ActiveRecord will add the quotes itself and send:
where "Bpage_name" = 'pancakes'
to the database. The quoting and case sensitivity issue only arises when you use bits of SQL; of course, you almost always end up using SQL snippets with ActiveRecord so the naming convention is still important if you don't want to litter your code with a bunch of noisy double quotes.

Can I add multiple entries to one column attribute in my rails database?

I'm building a job board in the rails framework. I'm setting up my job scaffold and running into an issue.
The employer will be able to set the job_title, job_location, salary, description, required_education, required_major required_experience, and so forth. In some job postings, you'll see employers put in their description, "Minimum Education Requirement is a BS in Computer Science or related field." I'm trying to add numerous fields in my db under the required_major column so the employer can add the related degrees. For example, if the employer will accept a Bachelors degree in Computer Science, Software Engineering, Computer Engineering, or Information Technology; I want to be able to add multiple majors for the required_major field. Would I be able to create a subfield of this column so it would have all the degrees listed or should I have numerous columns (ex. required_major1 required_major2, etc.).
Here is my current schema for the jobs table:
ActiveRecord::Schema.define(version: 20160103030240) do
create_table "jobs", force: :cascade do |t|
t.integer "company_id"
t.string "job_title"
t.string "job_location"
t.decimal "salary", precision: 8, scale: 2
t.text "description"
t.string "required_education"
t.string "required_major"
t.string "required_experience"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Right now, I'm just going to start with a single degree field and expand on it as I build. Any input on this would be appreciated.

Data migrations for older version of friendly id

I'm migrating a very old app from friendly_id 3.2 to 5.1.
I have a user model which currently has a cached_slug field. I created a new field called just slug. Initially, I thought I could just copy the data over from cached_slug to slug, but I noticed that there's also a whole other table called slugs which looks like this:
create_table "slugs", force: :cascade do |t|
t.string "name", limit: 255
t.integer "sluggable_id"
t.integer "sequence", default: 1, null: false
t.string "sluggable_type", limit: 40
t.string "scope", limit: 255
t.datetime "created_at"
end
Following the directions in the FriendlyID README for the Rails Quickstart, I ran rails generate friendly_id which created this table:
create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope"
t.datetime "created_at"
end
So now I'm all confused about what I need to do to complete migration. I tried creating a new user with the console and the friendly_id_slugs table is still empty, so I'm not sure when or what it's used for.
I have two questions:
1) What is this other table used for
2) What do I need to do to migrate my old slugs to the new table/field so it will continue to work moving forward?
Thanks!
If you don't mind losing the FriendlyId's History records (i.e: the old slugs),
drop the old slug table
in the rails console, run <ModelName>.find_each(&:save) for all friendly_id models.
Step 2 should regenerate new slugs.
Warning: Test before doing this on production servers!
Update: You can also perform step 2 in a migration file
class RegenerateSlugs < ActiveRecord::Migration
def change
# <ModelName>.find_each(&:save)
User.find_each(&:save)
Article.find_each(&:save)
end
end

Can you recommend a plugin or gem that generates a facebook-style message inbox using Rails 3?

I want a way for users to send and receive messages and to see them in an inbox similar to how it is done in Facebook: it shows the subject and knows whether the particular message was received or sent, and then clicking shows the entire thread.
I have been trying to use a single Message record with a UserMessage -- one for the sender, the other for the recipient -- but not exactly sure how to, for example, show all the messages for a User whether a recipient or sender.
Ideally, someone has already done this in a plugin or gem I can repurpose.
This is not a very difficult task for you to handle. We want to be able to send messages between users. Let's say we have a User model that looks something like (for the sake of simplicity)
create_table "users", :force => true do |t|
t.string "username", :null => false
t.string "hashed_password", :null => false
t.datetime "created_at"
t.datetime "updated_at"
t.string "salt", :null => false
end
and a Message model that looks like (also for the sake of simplicity)
create_table "messages", :force => true do |t|
t.string "subject", :null => false
t.integer "sender_id"
t.integer "recipient_id"
t.datetime "created_at"
t.datetime "updated_at"
end
and a Msgcontent model that looks like
create_table "msgcontents", :force => true do |t|
t.string "body", :null => false
t.integer "message_id"
t.integer "sender_id"
t.datetime "created_at"
t.datetime "updated_at"
end
Now that you have those models set up, in your controller you might have something like.
def show_all_messages
#sent_messages = Message.find_all_by_sender_id(params[:user_id])
#received_messages = Message.find_all_by_recipient_id(params[:user_id])
end
def show_message_detail
#thread = Msgcontent.find_all_by_message_id(params[:message_id])
end
Where params[:user_id] is the current user signed in and params[:message_id] is the message that you want to show in detail. You can also combine the 2 variables into one variable, but you might want to separate it, it's up to you. This way, in your view you can appropriately display all the messages that have been sent and received by a user. I'm not going to write a whole view worth of code, but basically there's the show_all_messages that gets all messages that the user is a part of, and the show_message_detail will get the whole thread of the message. I will leave this part to you :)
If you want to be able to tell whether a user has seen the message or not, you can add 2 fields to your Message model, which would then result into
create_table "messages", :force => true do |t|
t.string "subject", :null => false
t.integer "sender_id"
t.integer "recipient_id"
t.datetime "sender_lastviewed", :null => false
t.datetime "recipient_lastviewed", :null => false
t.datetime "created_at"
t.datetime "updated_at"
end
Now you will know the last date that the sender or recipient has looked at your message. In your show_all_messages you would need to update your Message model to reflect the two columns you've added to your table. Then to able to get the messages after the _lastviewed you need to use conditions in your code, which can be found here
If you really want to find a gem, you can probably can with some diligent googling, however there are some very basic Rails (and database) concepts here that is essential for you to understand if you want to rely on your own knowledge for future development.
has_mailbox gem
is there to send messages between users, I'm testing it on my sample porject right now :)
and dont forget to add will_paginate gem in gem file because for now ha_mailbox gem shows error incase will_paginate is not in gem file. I think it is dependent on it

Resources