Rails 3.2.8 - Multiple database access within one single Rails App - sql-server

I'm building a webapp that has its own database called 'products_db'. But my app will have to call reviews which is located in the database 'reviews_db', which is a legacy db being used by another system that I can't do anything because client wants it that way.
So, luckily both db are located in the same SQL Server (MSSQL). I've already have te 'activerecord-sqlserver-adapter' working but I need to figure out a way to access 'reviews_db' from my webapp.
The reviews_db doesn't follow any Rails convention because its a legacy system.
So, my class Product:
class Product < ActiveRecord::Base
attr_accessible :name, :description, :price
has_many :reviews
end
And my class Review:
class Review < ActiveRecord::Base
# THIS CLASS DOESN'T FOLLOW RAILS CONVENTION
# HOW DO I SET AND MANAGE LEGACY PRIMARY KEY?
# HOW DO I CONNECT THIS MODEL TO THE OTHER DATABASE?
# HOW DO I CONNECT THIS MODEL TO THE RIGHT TABLE NAME?
attr_accessible :rv_tbl_title, :rv_tbl_id, :rv_tbl_text, :rv_tbl_author, :rv_tbl_ref_prod
has_one :Product, foreign_key: :rv_tbl_author
end
Is there a gem for it? What's the solution to use in the Review class questions?

I'm not sure if this first part is necessary or not, but in your database.yml file, make a new connection by adding something like this to the end:
review:
adapter: sqlserver
database: reviews_db
.... put your other configuration info here
Then in your review model review.rb:
class Review < ActiveRecord::Base
establish_connection :review
self.table_name = "review_table"
self.primary_key = "review_id"
end
Change the table name to the correct table, and the primary key to the correct column name.
Then create a new table/model for the sole purpose of having a local reference to a review. So you could call it ReviewReference
class ReviewReference < ActiveRecord::Base
belongs_to :review
has_one :product
end
And change your Product model to
class Product < ActiveRecord::Base
has_many :reviews, class_name: "ReviewReference"
end
This should get you a long way toward your goal. You might have to end up doing a lot of
#reviews = Review.where("some_column = ?", some_value)
or
#reviews = Review.find_by_sql("Some SQL Here") if you're doing more complex queries.
Sorry my answer isn't more concrete, I've only done this once. Props to Chad Fowler's Rails Recipes book for the concept.

Related

Rails - include? with object or object id

I have an object called Company which has a method called owners that returns an array of User objects. I want to filter those companies by checking if the current user is in the owner's array. first I did something like this (which works):
Company.select { |c| c.owners.include?(current_user) }
However, I figured a more efficient check would be comparing only the ids instead of whole objects:
Company.select { |c| c.owners.map(&:id)include?(current_user.id) }
Can anyone help me understand if there is a difference between the two options?
There's really no difference between the two. Both are very inefficient and won't work if your Company table is large. They load all the Company records into memory, and furthermore call c.owners which fires an additional query on each of the records. This is called an N+1 query. You can address the N+1 part by using Company.all.includes(:owners), but you'd still have the issue of loading everything into memory.
It's hard to give you exact code since you haven't shared your model definition (specifically, how the Company#owners association is defined). But I'll assume you have a CompanysOwners join table. In which case I would recommend the following code:
companies = CompanysOwners.where(owner: current_user).includes(:company).map(&:company)
This only fires off a single query, and doesn't load more records into memory than are needed. You could get rid of the includes(:company) part and it would still work, but be slower.
If your Company#owners association is defined differently than this, feel free to leave a comment and I can show you how to modify this for your needs.
By the way, to address your original question more directly ... under the hood, c.owners.include?(current_user) uses == to compare the records. And ActiveRecord::Core#== is comparing the IDs under the hood. You can see the source code here for proof of that: https://api.rubyonrails.org/v6.1.3.1/classes/ActiveRecord/Core.html#method-i-3D-3D. So they really are doing the same thing.
I have an object called Company which has a method called owners that returns an array of User objects.
Well that's your problem right there.
This should really be handled on the model layer by setting up an indirect association:
class User < ApplicationRecord
has_many :company_ownerships,
foreign_key: :owner_id,
inverse_of: :owner
has_many :companies, through: :company_ownerships
end
class CompanyOwnership < ApplicationRecord
belongs_to :owner, class_name: 'User'
belongs_to :company
end
class Company < ApplicationRecord
has_many :company_ownerships
has_many :owners, through: :company_ownerships
end
This will let you get the companies owned by a user by simply calling:
current_user.companies
This is vastly more efficient as it does a single query to fetch the associated records instead of loading the entire table into memory. This will return an ActiveRecord::Relation object and not an array which allows you to add additional scopes if needed:
current_user.companies.where(bankrupt: false)
Its also superior from a code design standpoint as the buisness logic is encapsulated in your models instead of leaking the implementation details all over the place.
It will also let you include/preload/eager_load to avoid n+1 queries:
#users = User.include(:companies)
.all
#users.each do |user|
# this loads all the companies in one single query instead
# of one query per user
user.companies.each do |company|
puts company.name
end
end
If you ever for some reason need to check if two records are related you want to use a join and where clause:
#companies = Company.joins(:owners)
.where(users: { id: current_user.id })

Rails new migrations not updating old data

I have a model called Article. This model has attribute called duration which is a string. The duration can be morning, afternoon, evening and night.
For example, article object looks like this:
#<Article id: 1, created_at: "2015-09-22 08:00:08", updated_at: "2015-09-22 08:00:08", duration: "morning">
Since the duration has similar properties, I create a base class called duration and the inherited classes as morning, afternoon, evening and night.
In a duration there can be many articles. But a single article can have only one duration. So, I have has_many and belongs_to association:
app/model/duration.rb
class Duration < ActiveRecord::Base
has_many :articles
end
app/model/article.rb
class Article < ActiveRecord::Base
belongs_to :duration
end
Other inherited classes are:
app/model/duration/morning.rb
class Morning < Duration
end
and so on for afternoon.rb, evening.rb and night.rb.
I already have the migration to create durations table. To add the field type to duration, I have a migration called add_type_to_duration.rb
AddTypeToDuration < ActiveRecord::Migration
def change
add_column :durations, :type, :string
end
end
I have another migration file called add_duration_ref_to_articles.rb to add reference
class AddDurationRefToArticles < ActiveRecord::Migration
def change
add_reference :articles, :duration, index:true
end
end
I have another migration to create the new durations in add_initial_durations.rb
class AddInitialDurations < ActiveRecord::Migration
def change
Morning.create
Afternoon.create
Evening.create
Night.create
end
end
Now I want to update the old data to adapt according to new migrations. So, I have another migration called update_articles_to_have_duration.rb
class UpdateArticlesToHaveDuration < ActiveRecord::Migration
def change
morning = Duration.find_by_type("Morning")
Article.where(duration: "morning").find_each do |article|
article.update!(duration_id: morning.id)
end
end
end
Now when I run the migrations all the articles, that had duration = morning, now have duration_id = nil. However, when I run the last migration again with rake db:migrate:redo step:1, then articles have correct duration_id. I think somehow the migration is not run properly. However, I don't get any error while running them. Can anyone please let me know what am I doing wrong here?
Thanks for the help.
As you've said that duration_id is set properly when running the migration second time, the only reason why it's not working on the first go might be that the migrations are not running sequentially as you've shown.
Migration files have timestamps on them and they run oldest first when you do rake db:migrate.
See the timestamps of the migration file to ensure that they're ordered as you want them.
❯ rake db:migrate:status
database: ~/blog/db/development.sqlite3
Status Migration ID Migration Name
--------------------------------------------------
up 20150907015616 Create articles
down 20150907031746 Create comments
down 20150909034614 Devise create users
You can use above command to see the Migration Id and copy it to run the migrations one after another to verify my assumption with following command:
❯ bin/rake db:migrate VERSION=20150907015616 VERSION=20150907031746 VERSION=20150909034614
You'll have to re-order(better to delete and recreate) the migration files or transpose its contents if the migration files do not follow the chronological order of timestamps.

Rails 4: multi-level includes and conditions

I've read the Rails API docs on this and have googled already, but the examples I've come across don't work. I'm sure this is a duplicate question, but none of the current ones work so I'm asking it again.
This is my model setup, 2 levels deep:
class Company < ActiveRecord::Base
has_many :offices
end
class Office < ActiveRecord::Base
belongs_to :office
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :office
end
I need to find all the companies that have associated contacts with a given user_id value.
user_id = 1
Company.includes(offices: :contacts).where("contacts.user_id = ?", user_id)
I keep getting an error: TinyTds::Error: The multi-part identifier "contacts.user_id" could not be bound.
The only table with a user_id is contacts. Unfortunately I have to use MS SQL Server as the DB, hence the reason I'm using TinyTds.
I'm assuming my Rails AR syntax is incorrect, but the API docs are rather confusing to me on multi-level queries like this.

NDB Datastore: Data Modeling for a Job website

What is the best way to model data for a job website which has the following elements:
Two types of user accounts: JobSeekers and Employers
Employers can create JobPost entities.
Each JobSeeker can create a Resume entity, and many JobApplication entities.
JobSeekers can create a JobApplication entity which is related to a JobPost entity.
A JobPost entity may receive many JobApplication entities.
A JobSeeker may only create one JobApplication entity per JobPost entity.
A Resume contains one or more instances of Education, Experience, using ndb.StructuredProperty(repeated = True).
Each Education contains the following ndb.StringProperty fields: institution, certification, area_of_study
While each Experience contains: workplace, job_title.
Here is a skeleton model that meets your requirements:
class Employer(ndb.Model):
user = ndb.UserProperty()
class JobPost(ndb.Model):
employer = ndb.KeyProperty(kind=Employer)
class JobSeeker(ndb.Model):
user = ndb.UserProperty()
def apply(self, job_post):
if JobApplication.query(JobApplication.job_seeker == self.key,
JobApplication.job_post == job_post).count(1) == 1:
raise Exception("Already applied for this position")
...
class Resume(ndb.Model):
job_seeker = ndb.KeyProperty(JobSeeker)
education = ndb.JsonProperty()
experience = ndb.JsonProperty()
class JobApplication(ndb.Model):
job_seeker = ndb.KeyProperty(JobSeeker)
job_post = ndb.KeyProperty(JobPost)
Notes:
Employer and JobSeeker have the built-in UserProperty to identify and allow them to login.
Resume uses JsonProperty for education and experience to allow for more fields in the future. You can assign a Python dictionary to this field, for example
resume.education = {'institution': 'name', 'certification': 'certificate', 'area_of_study': 'major', 'year_graduated': 2013, ...}
(I have personally found StructuredProperty to be more pain than gain, and I avoid it now.)
Limiting a JobSeeker to only one JobApplication can be done with the method apply() which checks the JobApplication table for existing applications.

Rails 3 Retrieving All Great Grandchild Records As ActiveRecord Collection

So I'm trying to perform queries on a models great granchildren. The relationship is as so...
Tournament > Competitions > Matches > Players
and the Tournament model:
class Tournament < ActiveRecord::Base
has_many :competitions, :dependent => :destroy
has_many :matches, :through => :competitions
has_many :players, :through => :matches
end
Currently what I have for retrieving all the great great grandchildren records is:
#results = #tournament.competitions.collect{|b| b.matches.collect{|c| c.players.with_win_count}}.flatten
And this works, however the problem is that it returns an array. What I'm trying to do is build a dynamic query based on user input, and the player table is essentially a result pool for all matches and the user has the option to filter only what he wants to see. What I have in the end is a fairly potentially complex (depending on user input) query and the additional where clauses cannot be performed on an array. To give you a better idea of how this is meant to work, here is my code...
def results
#tournament = Tournament.find(params[:id])
#results = #tournament.all_great_grandchildren
#results.where(params[:player_condition1]) if params[:player_condition1]
#results.where(params[:player_condition2]) if params[:player_condition2]
#results.where(params[:player_condition3]) if params[:player_condition3]
if params[:match_condition]
#Join to match table so we can query the match fields
#results.join(:match)
#results.where(params[:match_condition])
end
....
#results.order(params[:order]) if params[:order]
end
Is there a way to find all of the great grandchildren (player) records for any given tournament without an array so I can continue to condition the records?
I think you should just call #tournament.players will work out of the box if you have setup with the above associations.
Chamnap has got it. The reason we define “has_many” and “belongs_to” associations in models is to give us easy access to the association models.
So when you have a Tournament object called tournament, you can do
competitions = tournament.competitions
matches = tournament.matches
players = tounaments.players
You can also go in the reverse direction, starting with a player object. Ex: matches = player.matches
With Active Record you shouldn't have to do manual joins or raw SQL model calls when dealing with associations. Have a quick read of the Actice Records Associations guide:
http://guides.rubyonrails.org/association_basics.html

Resources