Correct Model Data Structure? (My 1st Rails App) - ruby-on-rails

I'm about to build my first Ruby on Rails application (and first anything beyond xhtml and css), and I'm looking for some helpful feedback on my model structure.
I've included a mockup to help visual the application.
The only models I'm sure I need so far are:
1. a movie model (to serve as the main model, named movie so URL's will look like, "sitename.com/movies/1-Willy-Wonka") and
2. a user model
Other than those, I don't know what to make the others; like 'filming locations', 'actors', and most importantly, the attributes like 'willy wonka' and 'johnny depp'.
Should I make all of those models, even the attributes? If so, is it possible, and recommended, to let people create models as attributes?
I try to see the app as data created by forms and stored in databases, and with that pov I can't decide how to connect attributes (character <=> actor) to sections (actors, filming locations, artwork, etc.) inside a movie listing; while allowing the attributes themselves to be hyperlinks to related attributes/movies/even sections: clicking on 'steven speilberg' would bring you to a page with movies he's acted in, films he's directed, produced, written, etc.
Thoughts?

I would recommend getting your data model clear first. You can then start with one rails model per database table. This is not always the case, but it's a reasonable place to start.
Let's focus on the movies and actors:
Some assumptions:
movies can have a many-to-many relationship to the other entities
you want to store the character information as part of the relationship between an actor and a movie
You might then model your relationships like this:
# movies.rb
class Movie < ActiveRecord::Base
has_many :roles
has_many :actors, :through => :roles
end
# actor.rb
class Actor < ActiveRecord::Base
has_many :roles
has_many :movies, :through => :roles
end
Normally, you can rely on Rails' magic to handle the join model without creating one yourself. In this case we want to store the character information as attributes of the join model, so we create it explicitly.
# role.rb
class Role < ActiveRecord::Base
belongs_to :movie
belongs_to :actor
end

Start with models Movie, Actor, FilmLocation, and Character. Movie and Actor have a many-to-many relationship(A Movie has many actors, an actor worked on many movies). FileLocation and Movie also are many-to-many. Character(Willy Wonka) and Actor are many-to-many as well.

If this is your first anything as far as web development then I suggest you start with writing a simple web app that just lists movies and allows your to add, edit and delete them. Just store the title, a synopsis and maybe the URL of the movie poster/dvd cover. Then work on adding Actors and associating them with the Movie.
Creating an "IMDB-like" site is not a trivial project. You're going to have a lot complex relationships beyond just associating an actor with a movie. Actors have Roles in Movies. And you might want to get even more abstract than that and say that a Person has Job in a Movie which would allow you to also track things like the Directors, Producers, Key Grips, Casting Directors.
Not only should you work on your data model, but work on a plan about what you want the site to consist of and what order you should create those features in (based on need) and take small steps to get to that final goal.

Further elaboration. The migrations for the models listed above might look like this:
class CreateMovies < ActiveRecord::Migration
def self.up
create_table 'movies' do |t|
t.string 'title', :null => false
t.timestamps
end
end
def self.down
drop_table 'movies'
end
end
class CreateActors < ActiveRecord::Migration
def self.up
create_table 'actors' do |t|
t.string 'first_name', 'last_name', :null => false
t.timestamps
end
end
def self.down
drop_table 'actors'
end
end
The movie_id and actor_id fields below correspond to the belongs_to associations in the Role model above and are the foreign keys that allow the roles table to join actors and movies. As I have proposed modeling it, the character_name is a property of this relationship, and is thus an attribute of the role. It's your call whether you allow NULL in the character_name. I'm leery of NULL, but in this case I've allowed it as one could make the argument that in practice you often wish to store the fact that an actor is in a given movie but don't know or care about the character name.
class CreateRoles < ActiveRecord::Migration
def self.up
create_table 'roles' do |t|
t.integer 'movie_id', 'actor_id', :null => false
t.string 'character_name'
t.timestamps
end
end
def self.down
drop_table 'roles'
end
end

It might be worthwhile to read up on database normalization: http://en.wikipedia.org/wiki/Database_normalization

In general, yes. I like my models to be as granular as possible. This makes things much clearer for someone who isn't familiar with the application and makes it easier to reuse code.
It's helpful to have a firm grasp of ActiveRecord associations before writing a complex application. Make sure you know all of the associations by heart and what they actually generate in terms of SQL tables. Sometimes it can seem like magic, and magic isn't necessarily a good thing. If you know what's behind it things fall in to place.
Also, don't be afraid to scrap everything and start over while experimenting. It's Ruby, so it won't take long to get back to where you were.

Related

Modeling user table for single database multiple application

Say we have 3 different applications - serviceapp, subscriptionapp, ecomapp, all written in ruby on rails and uses the same database in backend and tables in the backend. So the user table for all these three applications are same. If a user is part of serviceapp using the same email and credentials he can login into subscriptionapp or ecomapp and vice versa.
The reason behind choosing same user table and other table for all the application is puerly business perspective - same single crm and ticketing system for sales and cdm team to track everything. Devise is being used in all three applications along with LDAP so login and signup works fine without any issue.
Problem:
Till now users' last_login_at is a single column so we really can't tell which app he last logged in at. But now we have to start logging these details separately, like when did he last login at serviceapp, ecomapp, subscription app separetly.
Also we are starting to use a new crm of one particular app - subscriptionapp and for the clients(users) of that particular app we have to store extra information like unq_id from crm and so on.
My intial thought is to add these columns in the user table itself. But in the future we might add few extra information to user table which are app specific. Hence adding it to the main user table won't be a good idea for this. How shall I proceed in this case? I though of creating three different tables like subscriptionapp_client, ecomapp_client, serviceapp_client had associating them with the user table like user has_one ***_client.
If the association is present like if user.subscriptionapp_client.present? he is a client of that app and we can store the last login at, crm_uniq_id and all in there in that table itself.
Is there anyother good approach that might fit the problem here? I am reading about MTI but it looks like it won't solve the problem.
Single table inheritance with JSON.
class CreateClientAccount < ActiveRecord::Migration[5.2]
def change
create_table :client_accounts do |t|
t.references :user
t.string :uid # the unique id on the client application
t.string :type
t.integer :sign_in_count
t.datetime :last_sign_in_at
t.jsonb :metadata
t.timestamps
end
add_index :client_accounts, [:user_id, :type], unique: true
end
end
class User
has_many :client_accounts
has_one :service_account, class_name: 'ServiceApp::ClientAccount'
# ...
end
class ClientAccount < ApplicationRecord
belongs_to :user
validates_uniqueness_of :user_id, scope: :type
end
module ServiceApp
class ClientAccount < ::ClientAccount
end
end
module SubscriptionApp
class ClientAccount < ::ClientAccount
end
end
module EcomApp
class ClientAccount < ::ClientAccount
end
end
This avoids the very unappealing duplication of having X number of tables in the schema to maintain and the JSONB column still gives you a ton of flexibility. However its in many ways just an upgrade over the EAV pattern.
It also has a lot in common with MTI. In MTI you would use an association to another table which fills the same purpose as the JSON column - to make the relational model more flexible. This can either be polymorphic or you can have X number of foreign keys for each specific type.
One table for each type.
class User < ApplicationRecord
has_one :subscription_account
has_one :service_account
# ...
end
class ClientAccount < ApplicationModel
self.abstract_class = true
belongs_to :user
end
class SubscriptionAccount < ClientAccount
end
class ServiceAccount < ClientAccount
end
# ...
This is the most flexible option but if you want to add a feature you will have to create migrations for each and every table. And this also means that you can't query a single homogenous collection if you want all the types. You have to perform X number of joins.
This is not really that appealing unless the requirements for each type are wildly different.

Has_one association confusion, I want it reversed

I'm pretty new to rails and lately I found that I understood activerecords has_one association contrary to how it actually works. Refering to the example from rubyonrails guide I imagined that it is the supplier that should hold its account_id, since I see no point in forcing every account to hold its supplier.
Because I don't really understand why, or simply don't agree with the object maintaining it's foreign keys in other objects, I don't know what would be the correct rails solution for my simple problem:
I have two objects - document, and draft. Every document has many drafts, and one of them is marked as the current draft. I imagined the table layout to be something like this:
table document
id
current_draft_id
table draft
id
document_id
text
What I'm looking for here is something like a has_one association, but reversed, so that the document would held and maintain the current_draft_id. Using belongs_to is not an option because of its different behaviour. For example I'd like document.current_draft = new_draft to update the foreign_key correctly.
How to approach this in rails?
-- update 1
To clarify my problem, please assume that the draft being current won't have nothing to do with created_at and updated_at fields, so scopes won't do.
Adding a current field to the drafts table would be a weird move from the table design point of view. I'm also planning to add information about the published draft to the document object, and multiplying such informations in drafts tableseems to be an odd step.
I like Amesee's idea, but still I have some inner resistances, similar to adding the current column to the drafts table.
I would argue that Draft is a Document so it may make more sense to manage these classes with single table inheritance. You can tell that a draft is a "current draft" by its type.
class CreateDocuments < ActiveRecord::Migration
def change
create_table :documents do |t|
t.string :type
# ...
t.timestamps
end
end
end
And the models.
class Document < ActiveRecord::Base
# ...
end
class Draft < Document
# ...
end
class CurrentDraft < Draft
# ...
end
Later on, when a draft isn't "current" anymore, update its type by changing its type attribute to "Draft" or "Document." I think this is a better solution than constantly checking a boolean or date attribute on the object and asking about its state everywhere in the application.
How do you enforce which draft is the "current" draft? Would it be the most recently created one? The last one edited? I would have the current draft be a logically calculated facet of the draft table and find it with scopes, rather than force a fixed ID that may not always match the logic.
class Document < ActiveRecord::Base
has_many :drafts
def current_draft
self.drafts.ordered.first
end
end
class Draft < ActiveRecord::Base
belongs_to :document
scope :all
scope :ordered, order_by(:updated_at)
end
Alternatively, add a :current, :boolean, :default => false field to the drafts table and have there be only one child with current being true. (A good explanation of the logic for this method can be found here: Rails 3 app model how ensure only one boolean field set to true at a time)
If you really want to have a fixed child ID in the parent, then you need a :belongs_to with a defined foreign key:
documents table:
id
current_draft_id
Model:
class Document < ActiveRecord::Base
has_many :drafts
belongs_to :current_draft, :class_name => 'Draft', :foreign_key => 'current_draft_id'
end
Controller code somewhere:
#document.current_draft = #draft

How to setup self-referential polymorphic relationship in rails?

I have a 3 models
User
Journal
Post
A couple of things can be assumed about these
User has_many :journals
Journal has_many :posts
I would like users to be able to subscribe to other users and specific journals on my site.
I am thinking I need a Polymorphic Subscription model that looks something like this
class CreateSubscriptions < ActiveRecord::Migration
def change
create_table :subscriptions do |t|
t.integer :user_id
t.references :subscribable, :polymorphic=>true
t.timestamps
end
end
end
But here's where I'm stuck. I don't know how to setup the polymorphic relationships in my User model.
I want to be able to get the following things:
#user.watched_users
#user.watched_journals
#user.followers
#journal.followers
Can someone help? The docs are kind sparse on this and I know these can be a real chore to setup.
^_^
While I understand the desire to want to just have the user subscribe to arbitrary objects, in practice, you'll end up dealing with these different subscriptions differently, so at least for now (until you find yourself wanting to subscribe to other objects on the site), just leave them separate (YAGNI).
You can do this through a simple join table for each relationship. the answer on How do I do reflexive self-join relationships in ActiveRecord? leads to a good example in http://railscasts.com/episodes/163-self-referential-association

many to many relationship in rails 3

I have created 2 models in rails and modified the models classes to add a many to many relationship (with has_and_belongs_to_many)
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :users
end
When I create a User though the web interface, I am not asked to select some categories.
Did I miss somthing ? I read that another table was required but it semmed it was in the case of has_many and not has_and_belongs_to_many statement).
Could you please help ?
I think this is a newby question but...
Thanks a lot,
Regards,
Luc
For HABTM you need a join table called categories_users. Use this migration:
def self.up
create_table :categories_users, :id => false do |t|
t.integer :category_id
t.integer :user_id
end
end
What does your view look like? Rails's scaffolding won't account for many-to-many, so you will need to handle it yourself.
What does your data store look like? If you are using a RDBMS, then for many to many relations you typically need a junction table. Many-to-many is not naturally handled by most (all?) SQL databases.

Ruby on Rails: Finding all topics in a certain category?

I'm trying to find all topics in a one particular category, but I'm not sure if this is the most efficient way to do it:
Topic.all.select { |topic| topic.categories.include?(category) }
The above works for me but it seems that it takes MySQL a long time to find the records. Is there anything more efficient?
It sounds like right now you have a has_many relationship between topics and categories, when you need a many-to-many relationship. Restructure your models like this:
# app/models/category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :topics
end
# app/models/topic.rb
class Topic < ActiveRecord::Base
has_and_belongs_to_many :categories
end
Then create a join table without a primary key. Create the new migration like so:
script/generate migration AddCategoriesTopicsJoinTable
And add these contents:
class AddCategoriesTopicsJoinTable < ActiveRecord::Migration
def self.up
create_table :categories_topics, :id => false do |t|
t.integer :category_id
t.integer :topic_id
end
end
def self.down
drop_table :categories_topics
end
end
Notice the join table is named by combining the two table names, in alphabetical order. That's how Rails will know how to find it automatically.
Now you can call #category.topics and get an array of the topics, and you can call #topic.categories and get the categories. It works in both directions.
UPDATE: questions about many-to-many relationships in Rails have come up often enough that I wrote an article called basic many-to-many associations to explain how to use habtm vs has_many :through, and the differences between them.

Resources