So I have the following models in my Ruby on Rails setup: users and courses
The courses need to have content_managers and those content_managers are made up of several individuals in the users model.
I'm a newbie, so bear with me. I was thinking of creating a new model called content_managers that has a user_id and a course_id that links the two tables. It makes sense to me that courses HAVE content_managers. However from the users model, it doesn't make sense that users HAVE content_managers. Some of them ARE content_managers.
From that point of view I believe I'm thinking about it incorrectly and need to set up my ActiveRecord in a different manner from what I'm envisioning. Any help is appreciated.
Thanks!
There's no "have" or "are" in ActiveRecord, only "has_many", "has_one" and "belongs_to". With those tools you should be able to do what you want.
An example:
class Course < ActiveRecord::Base
has_many :content_managers
end
class ContentManager < ActiveRecord::Base
has_many :content_manager_members
has_many :users,
:through => :content_manager_members,
:source => :user
end
class ContentManagerMember < ActiveRecord::Base
belongs_to :course_manager
belongs_to :user
end
class User < ActiveRecord::Base
has_many :content_manager_members
has_many :content_managers,
:through => :content_manager_members
end
Be sure to index these correctly and you should be fine, though navigating from User to Course will be slow. You may need to cache some of this in order to find the level of performance you want, but that's a separate issue that will be uncovered during testing.
Whenever implementing something like this, be sure to load it up with a sufficient amount of test data that will represent about 10x the anticipated usage level to know where the ceiling is. Some structures perform very well only at trivial dataset sizes, but melt down when exposed to real-world conditions.
Related
I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.
I am relatively new to ruby/rails and I have the following question:
I am working on a scheduling app and have a model named Classes and another named ClassEntries. The relationship between them is that each user can have multiple class entries per semester, each relating to one class. Each record in the Classes table belongs to a specific University. A User can have multiple entries in the ClassEntries table for 1 semester (typically 5). Their schedule is comprised of all their ClassEntries with the same semester ID.
I am not sure whether I should have a third model called Schedule that brings together the info in the ClassEntries and Classes models for the user at hand. I originally wrote this functionality in PHP and I simply used a MySQL JOIN to gather the necessary information. In Rails it seems that there should be a better way to accomplish this.
What would be the best way of going about this in Rails?
Many thanks
So, what you are looking for is pretty much associations in Rails.
You would have the following:
def User < ActiveRecord::Base
has_many :course_entries
has_many :courses, :through => :class_entries
end
def CourseEntry < ActiveRecord::Base
belongs_to :user
belongs_to :course
end
def Course < ActiveRecord::Base
has_many :course_entries
has_many :users, :through => :class_entries
end
With those associations set up, Rails would allow you to do such things like
some_user.courses or some_course.users and it will make the joins through CourseEntry for you.
Let me know if this helps. If you need me to go more in depth let me know.
I am using Ruby on Rails 3.2.2 and I would like to know what is a common approach in order to handle associated objects of a has_many :through ActiveRecord::Association. That is, I have:
class User < ActiveRecord::Base
has_many :article_editor_associations, :class_name => 'Articles::UserEditorAssociation'
has_many :articles, :through => :article_editor_associations
end
class Article < ActiveRecord::Base
has_many :user_editor_associations, :class_name => 'Articles::UserEditorAssociation'
has_many :editor_users, :through => :user_editor_associations
end
class Articles::UserAssociation < ActiveRecord::Base
belongs_to :editor_users
belongs_to :articles
end
By using the above code, I can run the #article.editor_users method so to retrieve an Array of Editor Users. However, in order to make things to fit better with my application (that is, for example, in order to handle things like I18n translations and similar in a "programmatic" way), I am thinking to add to my application a new model like the following:
class EditorUser < User # Note the class name and the class inheritance
...
end
This way, through my application, I can refer to the EditorUser class in order to handle article "editor user" instances as if they were User objects... more, since inheritance, in the EditorUser class I can state "specific methods" (for example, scope methods) available only to EditorUser instances (not to User instances)...
Is it a "common" approach to make things as I would like to make in my case? Is it the "Rails Way"? If so, what I could / should make to handle this situation? If no, how could / should I proceed?
In other words, I thought using class EditorUser < User ... end because associated EditorUser objects (retrieved by running the #article.editor_users method) are User objects. I think that by stating a EditoUser class in the app/models directory (or elsewhere) could simplify things in my application because you can work around that constant name (for example, you can "play" with that constant name in order to "build" translation strings or by stating new methods just to be used for EditorUser instances).
With Rails, I've learned to focus on the naming conventions and standard usage first ('convention' over configuration) and would set it up like this:
class User < ActiveRecord::Base
has_many :editors
has_many :articles, :through => :editors
end
class Article < ActiveRecord::Base
has_many :editors
has_many :users, :through => :editors
end
class Editor < ActiveRecord::Base
belongs_to :user
belongs_to :article
end
You can either use the presence of the join record, e.g. User.editor or add an additional attribute to editor if you want different editor access levels.
The above does not fully answer your question perhaps but should be a good starting point. I say this because one of the most important things about rails is that it uses a principle of 'convention over configuration'. This is good as it leads to terse, minimalist code. It's bad because you have to learn all the zillion conventions. If you don't know them or the framework well you can get yourself into a whole heap of trouble as I have seen with many rails applications that I have worked on over the years.
So my advice is really to step back. Don't try and force things to work with things like class renames. If the setup I have shown doesn't meet your needs, revisit your needs and read more on active record and associations in the API. I know this can be kinda frustrating for quite a while with rails but you really need to look how to do things the right way if you're going to be a good rails programmer in the long term.
I have this:
class User < ActiveRecord::Base
has_many :serials
has_many :sites, :through => :series
end
class Serial < ActiveRecord::Base
belongs_to :user
belongs_to :site
has_many :episodes
end
class Site < ActiveRecord::Base
has_many :serials
has_many :users, :through => :serials
end
class Episode < ActiveRecord::Base
belongs_to :serial
end
I would like to do some operations on User.serials.episodes but I know this would mean all sorts of clever tricks. I could in theory just put all the episode data into serial (denormalize) and then group_by Site when needed.
If I have a lot of episodes that I need to query on would this be a bad idea?
thanks
I wouldn't bother denormalizing.
If you need to look at counts, you can check out counter_cache on the relationship to save querying for that.
Do you have proper indexes on your foreign keys? If so, pulling the data from one extra join shouldn't be that big of a deal, but you might need to drop down to SQL to get all the results in one query without iterating over .serials:
User.serials.collect { |s| s.episodes }.uniq # ack! this could be bad
It really depends on the scale you are needing out of this application. If the app isn't going to need to serve tons and tons of people then go for it. If you are getting a lot of benefit from the active record associations then go ahead and use them. As your application scales you may find yourself replacing specific instances of the association use with a more direct approach to handle your traffic load though.
I'm a beginning to ROR, but here's what I'm trying to achieve. I have two items I want to associate: matters and people. Each matter can have many people. That is, I want to create people and matters separately and later be able to link them.
For example, I may create:
Bill Clinton
Barack Obama
I may create the matters:
Global warming
War on terror
I want to be able to associate the users Bill Clinton AND Barack Obama to BOTH matters. Can someone point me to a tutorial that can show me how to do this?
I think has_and_belongs_to_many is used less and less by the RoR community now. While still supported, I think it is now more common to have an intermediate model (in your case something like PoliticianMatter) to join your Politician and Matter models.
Then your politician_matter table will have a PK, a politician_id and a matter_id.
Then you have
class PoliticanMatter < ActiveRecord::Base
belongs_to :politician
belongs_to :matter
end
The advantage of this approach is that if there ever need to be future properties of the politician -> matter relationship (e.g importance, date of last occurrence) you have a model which affords this - has_and_belongs_to_many would not support the addition of these extra properties.
You can also access the many to many relationship directly from the Politician and Matter models like this
class Politician < ActiveRecord::Base
has_many :politician_matters
has_many :matters, :through => :politician_matters
end
class Matter < ActiveRecord::Base
has_many :politician_matters
has_many :politicians, :through => :politician_matters
end
You need a many2many relationship between these two entities.
A matter can be studied by many people
A person can studie several matters
Rails uses the has_and_belongs_to_many helper to do that. You'll find more about that in the documentation and many many blog posts!
has_and_belongs_to_many helper
class Politician < ActiveRecord::Base
has_and_belongs_to_many :tasks
end
class Task < ActiveRecord::Base
has_and_belongs_to_many :politicians
end
What you need are 3 tables:
politicians, tasks and politicians_tasks (having the two columns politician_id and task_id, no primary key)
Hope this helps
Seb