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
Related
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.
In a Rails 4 application, I have an STI model that stores metadata in a jsonb column.
Base Class:
class Post < ActiveRecord::Base
...
end
Subclass:
class JobPost < Post
# has a jsonb column for metadata
end
One of the data attributes in the metadata column of a JobPost is a foreign_key reference to another table (company_id). I'd like to add a belongs_to :company reference in the JobPost model. It seems like this should be possible by doing something like
class JobPost < Post
belongs_to :company do
Company.find_by_id self.metadata['company_id']
end
end
but that doesn't appear to work. Help?
Note: I am not necessarily intent on using belongs_to rather than writing needed methods like def company by hand, but I do need a way to eager load companies when listing job posts. If there's a way to do that eager loading without a belongs_to relation I'm all ears.
Update1
I have also tried the following, which doesn't appear to work either:
class JobPost < Post
belongs_to :company, foreign_key: "(posts.metadata->>'company_id')::integer".to_sym
end
Update2
To be more clear about my intentions and need:
1) A JobPost belongs_to a Company, but a Post (and other subclasses of Post) does not. I'd prefer not to jankily add the company_id column to the posts table when it won't be used by the other subclasses.
2) A JobPost could justify having it's own table (perhaps the relationship with a Company is enough to justify it). There are reasons why this wouldn't be ideal, but if that's the only answer I'm open to it. I'd, however, like a more definitive "what you're trying to do can't be done" response before going down this road, though.
The primary question is whether you can customize belongs_to so that it uses the metadata column rather than expecting the foreign key to be a column in the table.
The secondary question is whether you can eager load companies alongside job posts without having that belongs_to relation set up.
EDIT
UPD 2
You need to add "company_id" column to the base class of your STI table. If JobPost inherits from Post, and it should have "company_id" then add the "company_id" column to Post (base table).
Remember STI stands for "Single Table Inheritance" so there is only one table on database schema level. Imagine a column of a Post table, where few data records are the foreign key entries for Companies with company_id and what about the other records of this column with non JobPost subclass types, are they null/empty? Hence the relationship is defined with parent STI table and subclass inherits these relations. Additional type column in STI defines the subclass type.
Check here
You may need to dig further on Polymorphic classes instead of STI if both JobPost and Post have relationship with Company, else create two separate model, as they tend do have some unique relationships and column fields.
UPD
Based on updated ask
app/model/company.rb
class Company < ActiveRecord::Base
has_many :posts
delegate :jobposts, to: :posts
end
app/model/post.rb
class Post < ActiveRecord::Base
belongs_to :company
self.inheritance_column = :ptype
scope :job_posts, -> { where(ptype: 'JobPost') }
def self.ptype
%w(JobPost)
end
end
app/models/jobpost.rb
class JobPost < Post; end
Create a company
company = Company.create!(company_params)
Create some posts and add them to the company
company.posts << JobPost.new(jobpost_params)
To fetch jobpost by company relationship
company.job_posts
In case you are storing company_id in jsonb in any which column, just format your jobpost_params hash input accordingly and it should do the deed for you
OLD ASK
To find by primary key
Company.find(id)
In your case, id is self.metadata['company_id']
To find by other keys
Company.find_by(key: value)
Company.find_by_id is no more recommended
Please remove do and end after belongs_to in your model, instead in your controller you can write:
Jobpost.all.each do |x|
# your do
end
regarding foreign key, as rails is convention over configuration, it by default includes company_id reference to Jobpost which you can change in your Company.rb model
Let's say I have:
class Foo < ActiveRecord::Base
has_many :bar
end
class Bar < ActiveRecord::Base
belongs_to :foo
end
Also assume that the Bar class has timestamp fields (the ones that are automatically updated when the record is updated).
Question: Is there existing functionality/method calls in Rails, ActiveRecord, or a gem somewhere that allows me to call a method that will tell me most recent value in the updated_at columns in any of the records, in any of the associations defined on the Foo class?
So, a theoretical foo.latest_change_to_associated_objects method should give me a timestamp of when the latest updated_at field is for any of my associated records (in this case, for any Bar record).
The simplest case, where you have a parent class and just one association is simple enough - a callback or two would do the trick. I'm curious about situations where a class has many associations defined. Is there a gem (or functionality already inside of ActiveRecord that I'm missing) that allows me to declare any/all of my associations to have this functionality?
Obviously I could do a SQL call to figure this out, but I don't want to do that every time I check for the latest change. It seems like there should be a callback chain the automatically updates a latest_change_on_association field (or whatever you want to call it) on the parent when any associated record is changed, such that retrieving the latest change timestamp is a matter of checking a single field on the parent record.
Note: I looked into http://api.rubyonrails.org/classes/ActiveModel/Observer.html, but the problem there is that I'd have to write an observer for every class. It'd be great if the automation of creating the observers from a list of associations was already wrapped up in a gem or something.
You can use :touch to automatically update the updated_at timestamp (or any other timestamp field) of a parent record of a belongs_to when one of its associated records changes:
Emphasis is mine:
:touch
If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or destroyed. If you specify a symbol, that attribute will be updated with the current time in addition to the updated_at/on attribute.
If you really want to use your last_association_updated_at column, you can:
belongs_to :parent_class, :touch => :last_association_updated_at
See the Rails Guide on Associations.
I would recommend adding a column to the table for the parent class which would hold the timestamp of the latest change. Let's call the column "last_association_updated_at". Then use the :touch option on the association in each child class. Any time a child record is created, modified or deleted the last_association_updated_at column will get updated with the current time.
class Foo < ActiveRecord::Base
has_many :bar
end
class Bar < ActiveRecord::Base
belongs_to :foo, :touch => :last_association_updated_at
end
class Baz < ActiveRecord::Base
belongs_to :foo, :touch => :last_association_updated_at
end
Hi Stack Overflowers: I'm building a Ruby on Rails application that has several different models (e.g. Movie, Song, Photo) that I am storing movie clips, mp3s and photos. I'd like for users to be able to comment on any of those Models and have control over which comments are published.
Is the best practice to create a Comment model with:
belongs_to :movie
belongs_to :song
belongs_to :photo
And then tie each Model with:
has_many :comments
Then, I'm guessing in the Comment table, I'll need a foreign key for each Model:
comment, movie_id, song_id, photo_id
Is this the correct way to build something like this, or is there a better way? Thanks in advance for your help.
Use acts_as_commentable. It creates a comment table with a commentable_type (model name of the commented-upon item) and commentable_id (the model's ID). Then all you need to do in your models:
class Photo < ActiveRecord::Base
acts_as_commentable
end
Create a table to hold the relationships for each type of comment:
movie_comments, song_comments, photo_comments
and then use:
class Movie < ActiveRecord::Base
has_many :movie_comments
has_many :comments, :through => :movie_comments
end
class MovieComment < ActiveRecord::Base
include CommentRelationship
belongs_to :comment
belongs_to :movie
end
You can use a module (CommentRelationship) to hold all of the common functionality between your relationship tables (movie_comments)
This approach allows for the flexibility to be able to treat your comments differently depending on the type, while allowing for similar functionality between each. Also, you don't end up with tons of NULL entries in each column:
comment | movie_id | photo_id | song_id
----------------------------------------------------
Some comment 10 null null
Some other comment null 23 null
Those nulls are definitely a sign you should structure your database differently.
Personally I would model it this way:
Media table (media_id, type_id, content, ...)
.
MediaType table (type_id, description, ... )
.
MediaComments table ( comment_id, media_id, comment_text, ...)
After all, there is no difference to the database between a Song, Movie, or Photo. It's all just binary data. With this model you can add new "media types" without having to re-code. Add a new "MediaType" record, toss the data in the Media table.
Much more flexible that way.
I have no RoR Experience, but in this case you'd probably better off using inheritance on the database level, assuming your dbms supports this:
CREATE TABLE item (int id, ...);
CREATE TABLE movie (...) INHERITS (item);
CREATE TABLE song (...) INHERITS (item);
[...]
CREATE TABLE comments (int id, int item_id REFERENCES item(id));
Another approach could be a single table with a type column:
CREATE TABLE item (int id, int type...);
CREATE TABLE comments (int id, int item_id REFERENCES item(id));
As expressed before, I can't tell you how to exactly implement this using RoR.
The best idea is probably to do what Sarah suggests and use one of the existing plugins that handle commenting.
If you wish to roll your own, or just understand what happens under the covers, you need to read about Single Table Inheritance, the way Rails handles inheritance. Basically, you need a single comments table ala:
# db/migrate/xxx_create_comments
create_table :comments do |t|
t.string :type, :null => false
t.references :movies, :songs, :photos
end
Now you can define your comment types as
class Comment < ActiveRecord::Base
validates_presence_of :body, :author
# shared validations go here
end
class SongComment < Comment
belongs_to :song
end
class MovieComment < Comment
belongs_to :movie
end
class PhotoComment < Comment
belongs_to :photo
end
All your comments will be stored in a single table, comments, but PhotoComment.all only returns comments for which type == "Photo".
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.