Rails: Pros and Cons of multiple models - ruby-on-rails

I'm running a Rails 4 app and have a question about how to structure it. My application has users. These users have many fields that can be grouped into categories, such as Personal Info, Work Info, Home, etc.
My question deals with whether I should make separate models for each of these subgroups so users have many has_one associations, or instead just name the fields in the following fashion: personal_info_name, personal_info_address, work_info_address, etc.
Here are some of my thoughts for grouping into models:
Pros:
organization
readibility
Cons:
takes more database space
more models means more files/overhead
Is there a "Rails-way" to do this/what are some other pros/cons for having multiple models?
P.S.
I've read a bit about the "fat model, skinny controller" ideas, but am not sure I entirely understand them (if they pertain to the question).

You should still use proper has_one relations, but utilize ActiveRecord delegates to create shortcut methods:
class User < ActiveRecord::Base
has_one :personal_info, ...
has_one :work_info, ...
delegate :personal_info_name, to: 'personal_info.name'
delegate :personal_info_address, to: 'personal_info.address'
delegate :workd_info_address, to: 'work_info.address'
end
Of course, assuming you are using Active Record as an ORM. Otherwise, you could go the manual route:
class User
attr_accessor :personal_info, :work_info
def personal_info_name
personal_info.name unless personal_info.nil?
end
def personal_info_address
personal_info.address unless personal_info.nil?
end
def work_info_address
work_info.address unless work_info_address.nil?
end
end

OK this is just one of many ways of doing it. I usually create a Profile model, which belongs to a User. One user can either have one Profile, or, if this app is going to grow and allow one User to manage multiple properties, it could have many Profiles later on. Inside these Profiles you can use PGSQL HStore (tutorial here) to store many small preferences (tel1, tel2, address, address_work, etc.) to keep your database from cluttering. Good luck!

Related

Designing multi-tenant app in Rails

I'm implementing a multi-tenant app with Rails. My approach is not to use the postgres inbuilt multi-tenant feature and add a column to record the subdomain. That is where the question is :)
Let's get this example
class Organisation < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :organisation
end
I'm thinking about two approaches here:
Approach 1
add a subdomain column only to organisations
pros - The way how relational databases should work \0/
cons - When I have more complex queries , that will make my code slow
Approach 2
add a subdomain column to both organisations and users
pros - This will make queries faster
cons - I'm going against the relational databases
So the question is, what sort of a method I should follow between above two, or are there a different approach that I didn't think about?
We run a multi-tenant Rails app with slightly fewer than 500 table-backed classes, and if I had to guess I'd say around 400 of them relate to client data.
Client-specific attributes are held in the Client model, but we add the client_id to every client table with a not null constraint on the database. Only a minority of them are indexed, though because they are generally only accessed through a parent record.
We do not have to worry about setting the client id because the model will generally have:
class Child
after_initialize do
self.client ||= parent.client
end
end
We add the client_id to many tables because we have a lot of code that does:
#books = current_user.client.books
... so in those cases we'll have an association directly from Client to the model, and client_id is indexed.
We add the client_id to all tables, though, because we very often, for operational or unusual reasons, want to be able to find all of the relevant records for a client ...
MarketingText.where(client: Client.snowbooks).group(:type).count
... and having to go through a parent record is just inconvenient.
Also, because we made the decision to do this on all client-specific tables, we do not have to make the decision on each one.
So to get to your question, what I would do is add the subdomain to the Organisation only. However, I would add the organisation_id column to every table holding Organisation-specific data.
If you have a lot of clients but you are going to be generally familiar with their subdomain, then I would write a meta-program method on the Organisation that lets you use:
Organisation.some_subdomain
... to get the required organisation, then find the child records (in any table) with an association directly from the Organisation model ...
Organisation.some_subdomain.users
Organisation.some_subdomain.prices
Organisation.some_subdomain.whatevers
my opinion will go Approach number one, couple reasons for this
using relational database provided with activerecord + scopes will make writing software easier, also if you have more objects under organization later for example transactions, items (beside users),
I have a project with multi tenant capabilities and below is sample of design in my project
class Company < ApplicationRecord
has_many :users
# transaction
has_many :transactions
has_many :journals , :through => :transactions
# item
has_many :items
# other has_many ...
end
and in controller you can use eager loading to minimize query (includes / joins)
#company.includes(:users).scope_filter_here.search(params[:q])
approach number 1 is more user friendly compared with approach number 2 as it's more simple to user writing your url address, the less url to type is better (personal opinion).

Allowing admin and users to sign in using same form (rails)

I've created 2 tables, one for users and one for admins.
I created 2 tables as they both collect different information, but I want to be able to allow a sign in using an email address and password from both the admin and user tables via the same form.
Is this possible? I've looked around and people seem to have created 1 users table and added an admin boolean, but I wanted to avoid this and I didn't want to collect unnecessary data if I didn't need to.
Any help and assistance about how to best go around this would be great.
If you are implementing something from scratch, then it is simply a matter of coding it. I think this approach has some inherent flaws and I would avoid it.
If you want to have some segregation on the model side of things, I suggest you use STI. That way there is some shared behaviour/attributes and the distinctions can be coded separately, so you have your protection.
If you have plenty of distinct attributes, I would suggest separating them from your user/admin and creating an "admin_profile" model that belongs_to :admin and a "user_profile" that belongs_to :user.
And to make coding "transparent", you can create accessors in your admin model class to get/set the profile attributes seamlessly. Say you have an is_cool attribute on the admin_profile model, but you'd like to access it as
imadmin.is_cool
You can have in your admin.rb model
has_one :admin_profile
def is_cool
self.admin_profile.is_cool
end
be careful cause the has_one relationship may return nil if there is no profile associated with the admin/user.

Rails: has_many association with a table in another database and without foreign key

Here is my situation. I have model called Account. An account can have one or more contracts. The problem is that i'm dealing with a legacy application and each account's contracts are stored in a different database.
Example:
Account 1's contract are in account1_db.contracts.
Account 2's contract are in account2_db.contracts.
The database name is a field stored in accounts table.
How can i make rails association work with this?
This is a legacy PHP application and i simply can't change it to store everything in one table. I need to make it work somehow.
I tried this, but it didn't worked:
has_many :contracts, :conditions => [lambda{ Contract.set_table_name(self.database + '.contracts'); return '1' }]
Any ideas?
Why isn't database migration an option?
You're approaching this the wrong way. You want the two systems in your integration to be loosely coupled. By trying to get the two associated, you're creating an array of interdependencies that will later come around to backstab you. The approach you are trying creates tight coupling and reduces cohesion.
But, to directly answer your question, see below. Once again, I don't recommend implementing what I say below, but it would technically be a solution.
The first thing is that rails associations work only with foreign key. In fact, all database associations work this way. There isn't a ActiveRecord method of association without foreign keys as it defies what it means to associate two objects.
So you're not going to get it done with a has_many association. Instead, I would just manually create a function on your Contract model that simulates a has_many association.
class Account
memoize :contracts
def contracts
# Load from other database in here
end
def contracts=
# Push to other database in here
end
end

Mongo Design question with Rails/Mongoid for a bill tracking app

I'm writing a quick app for a user to track their daily bills (for money tracking purposes). I want the user to be able to define their own categories that a bill can be applicable for. I'm trying however to decide the best way to model this and also validate categories as unique.
My initial thought was this:
class User
include Mongoid::Document
embeds_many :bills
field :categories, :type => Array
end
class Bill
include Mongoid::Document
embeded_in :user, :inverse_of => :bills
field :category
index :category
end
So a user can add categories, just as strings, and when they add a bill, they'll choose from their available categories for the bill.
So, a couple questions:
Does this seem like the proper design? I Don't think it's necessary to define an actual category model as it's literally just a string used to index bills on, but I'm not sure if there are other benefits to a separate model
How do I validate_uniqueness_of :categories in my user model. I don't think it works on array items like this, but I could be wrong. I don't want a user to create categories with the same name. I suppose this might be the advantage of a separate model, embedded in the User, but again it seems like more work than necessary.
Can someone tell me my best options here to validate that a user has unique categories (but users can have the same categories, i obviously don't care about that, just unique in the scope of a single user)
[Update]
The design seems proper. In a Rails specific way how would you validate the uniqueness? When adding a category pull the list and do an indexOf check to ensure it doesn't exist. If it does just bounce back an error.
I'm not a Rails guy, let me know if I'm off track or something.
I'm not sure MongoDB would be the best choice of storage engines for that. You would be better off using MySQL with a categories table.
Knocks against MongoDB:
Not ACID transactions
No single server durability
Not relational (you want relational for a bill tracking application)

How to create a view to manage associations between HABTM models? (Rails)

I am using Ruby on Rails and need to create a view that allows the creation of records through a HABTM relationship to another model. Specifically, I have the following models: Customer and ServiceOverride, and a join table customers_serviceoverrides. Using the customer view for create/update, I need to be able to create, update and delete ServiceOverrides and manage the attributes of the associated model(s) from the same view.
Visually I'd prefer to have something like a plus/minus sign to add/delete service overrides, and each serviceoverride record has two string entities which need to be displayed and editable as well. However, if I could just get the code (a kind of nested form, I'm assuming?) working, I could work out the UI aspects.
The models are pretty simple:
class ServiceOverride < ActiveRecord::Base
has_and_belongs_to_many :customers
end
class Customer < ActiveRecord::Base
has_and_belongs_to_many :serviceoverrides
end
The closest thing I've found explaining this online is on this blog but it doesn't really address what I'm trying to do (both manage the linkages to the other model, and edit attributes of that model.
Any help is appreciated. Thanks in advance.
Chris
The ascii cast at http://asciicasts.com/episodes/17-habtm-checkboxes has a simple and functional example.

Resources