Say I have a blog_posts table. But, the users writing posts belong to groups, and each group has its own blog.
For example:
Say, a user belongs to 3 groups: Marketing, Project Alpha, Administrators
He creates a blog post, but want that post to appear in the "Marketing Blog" and on the "Project Alpha" blog.
What would be the best way to model this?
Would it be a bad idea to have a field in the blog_posts table like: group_ids
and store a comma-delimited list of ids: 3,7 (where Marketing Group =3, and Project Alpha=7)
or should I create another table and store blog_posts id and groups?
Thanks.
Building on #minitech's answer i'd have 3 model's and 5 db tables
Models
rails g model <model_name>
Post (must have column user_id for FK)
User
Group
Additional tables
rails g migration <table_name>
groups_posts (columns group_id and post_id only)
groups_users (columns group_id and user_id only)
Then in your models configure the relations as follows
class Post < ActiveRecord::Base
has_and_belongs_to_many :groups
belongs_to :user
end
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
has_many :posts
end
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :posts
end
Then as per your example the code becomes:
user = User.find(<id of user>)
post = Post.create(:title => 'foo', :content => 'bar', :user => user)
post.groups << Group.find(3)
post.groups << Group.find(7)
I would recommend creating one table to hold all posts for all blogs, then have a table to store all the blogs' information, then a table to store all the blogs' posts, referenced by ID. Then you just add, for example:
Into posts: ID=(auto increment value), Title='The title', Content='The content'
Into blog posts: blogID=(the ID of blog #1 to insert the post into), postID=(the auto increment value from last time)
Into blog posts: blogID=(the ID of blog #2 to insert the post into), postID=(the auto increment value from last time)
That way, the post is linked in so when it is edited or removed changes are reflected on all blogs, and you can also get a quick list of all blogs a post is in using something like SELECT * FROM blogPosts WHERE postID=id_of_post.
Using comma delimited lists as a way around making all data items atomic is a bad idea.
It's basically a way of taking data that is not in first normal form and transfomring so that it looks like first normal form data even though it's not.
If you do this, for certain queries you will have to do a full scan of one of the tables instead of an indexed lookup. This will be monstrously slow, and get worse as data volume increases.
A better solution is to have a junction table that relates group_id to user_id.
Create Table Group_user
(group_id,
user_id primary key is (gorup_id, user_id))
Then do a three way join when you want all the data together. This is the basic way you model a many-to-many relationship.
Related
i'm new to rails and your help and advise would be much appreciated as i am finding this challenging
Aim: i want the creator of the event to be able to select more than one user as
hosts for a created event (just like how facebook allows the creator of
a page to be be able to select users as admins of a created page). Is the below how my model and schema should be displayed?
i was aiming to build something like this image. Event1 i can select Ian & Jesse as hosts, Event2 i can also select Ian again as a host and select Emma
This is how i imagine it so far to be built (your guidance would be much appreciated):
models
user.rb
has_many events
event.rb
belongs_to user
host.rb
belongs_to user
has_many events
schema
users
name
email
events
title
address
user_id
hosts
user_id
event_id
Started writing this as a comment but realised it was getting too wordy.
your model is broken ... an event has many users .. it doesn't belong_to a single user.
What you have is a many to many relationship between users and events which needs resolving through a join table (aka associative/junction table). You have gone some way to resolving this with the hosts table though this goes against the rails convention.
What you want is something like:
models
user.rb
has_and_belongs_to_many :events
event.rb
has_and_belongs_to_many :users
and create a join table that references the two models
users table
name
email
events table
title
address
events_hosts table
user_id
event_id
The rails convention is for the join table to be named by joining the two names of the tables it is joining lexically ordered - i.e. events before hosts, concatenated together to give events_hosts.
Alternatively, you can also create a join model if you prefer:
EventHost
belongs_to :user
belongs_to :event
and modify the has_and_belongs_to_many to has_many :event_hosts in the other two models - the database schema will remain the same.
Not sure which association best fits what I want. Basically it's a todo list. The list is a task and that task has many users.
ie: Dig a hole and plant some seeds. Digging a hole will require two or more people. Jim and Jo are digging the hole and Jo will plant the seeds after. So two lists to complete, the first by two users and the other by one. Both user can complete both lists if needed.
Todo: has many lists
List: belongs to todo
List: has many users
User: has many lists
If Im not clear, each task (list) on a todo can be completed by any user. I struggle to see where to put a list_id on the users table. That's not possible as that user can be doing another (list) at the same time. Im not sure how through: :association comes into play here.
User.first.lists #= []
Todo.first.lists.first.users #= []
I get nothing as the user_id needs to go somewhere.
If I'm not mistaken it sounds like you need a join table. you then state that your records have a relation :through the join table.
Example
you have a join table called: user_lists which will contain 3 pieces of data (id, user_id, list_id)
so each time a user has a list you create a record on this table.
Then in your User model
class User < ActiveRecord::Base
has_many :lists, :through => :user_lists
end
If I have understood your setup correctly then I hope this helps, if not let me know.
You can read more about associations here http://guides.rubyonrails.org/association_basics.html
Kind of new to Ruby/Rails, coming from c/c++, so I'm doing my baby steps.
I'm trying to find the most elegant solution to the following problem.
Table A, among others has a foreign key to table B (let's call it b_id), and table B contains a name field and a primary (id).
I wish to get a list of object from A, based on some criteria, use this list's b_id to access Table B, and retrieve the names (name field).
I've been trying many things which fail. I guess I'm missing something fundamental here.
I tried:
curr_users = A.Where(condition)
curr_names = B.where(id: curr_users.b_id) # fails
Also tried:
curr_names = B.where(id: curr_users.all().b_id) # fails, doesn't recognize b_id
The following works, but it only handles a single user...
curr_names = B.where(id: curr_users.first().b_id) # ok
I can iterate the curr_users and build an array of foreign keys and use them to access B, but it seems there must be more elegant way to do this.
What do I miss here?
Cheers.
Assuming you have following models:
class Employee
belongs_to :department
end
class Department
has_many :employees
end
Now you can departments based on some employee filter
# departments with employees from California
Department.include(:employees).where(:employees => {:state => "CA"}).pluck(:name)
For simplicity, let's take an example of Article and Comments, instead of A and B.
A Comment has a foreign key article_id pointing at Article, so we can setup a has_many relationship from Article to Comment and a belongs_to relationship from Comment to Article like so:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Once you have that, you will be able do <article>.comments and Rails will spit out an array of all comments that have that article's foreign key. No need to use conditionals unless you are trying to set up a more complicated query (like all comments that were created before a certain date, for example).
To get all the comment titles (names in your example), you can do <article>.comments.map(&:title).
My application has about half a dozen different types of items that a user can comment on (articles, photos, videos, user profiles, blog posts, forum posts).
My plan right now is to have a single comments table in my database and then have parent_id and type fields in the table.
The type field would just be a string and the contents of it would be the name of it's parent table. So for an Article comments, the type would be article, for example.
Is that the best way to handle the comments table? Or is there some other, more efficient way to do that?
Using a polymorphic association would be the best way to achieve this - check out Ryan's railscast on the subject (or the ASCIIcast at http://asciicasts.com/episodes/154-polymorphic-association). I think you'll end up with something like:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
belongs_to :user
end
Then each model you want users to be able to comment on will have the line:
has_many :comments, :as => :commentable
I also found this post useful when setting up polymorphic comments. Hope that helps!
What you are describing is a called a polymorphic association. Rails can handle that out of the box, see this episode:
http://railscasts.com/episodes/154-polymorphic-association
If a comment can't apply to multiple things (the same comment can't apply to both an article and a blog post, or two different articles) then why is it a base entity?
If you're committed to it, I'd have a comment table that looked like this:
COMMENT_ID
COMMENT_BODY
USER_ID
DATE
ARTICLE_ID references ARTICLE on delete cascade
BLOG_POST_ID references BLOG_POST on delete cascade
... etc
And then have constraint that says one and only one of the parents can apply.
An alternative is to have a COMMENT table for each base entity, so you'd have ARTICLE_COMMENTS, BLOG_POST_COMMENTS, and so forth.
I'd suggest looking at plugins like acts_as_commentable, and looking at how they are implemented. Or just use the plugin.
I think your design is pretty good. It is simple and easy to implement. The only requirement would be that the data-types of the row identifiers would have to be the same as comment.parent_id so that your joins are consistent. I would actually define views on the comments table for each 'type'. For example, "create photo_comments as select * from comments where type = 'PHOTOS'". then you could join from PHOTOS to PHOTO_COMMENTS on PHOTOS.PHOTO_ID = PHOTO_COMMENTS.PARENT_ID, and so on.
You could also create a supertype say, widget, and each of your photo, blog_post etc. could be sub-types of widget. Then you could constrain your comments table to have an FK to the widget table. The 'type' column could then be moved to the widget table.
I am starting to create my sites in Ruby on Rails these days instead of PHP.
I have picked up the language easily but still not 100% confident with associations :)
I have this situation:
User Model
has_and_belongs_to_many :roles
Roles Model
has_and_belongs_to_many :users
Journal Model
has_and_belongs_to_many :roles
So I have a roles_users table and a journals_roles table
I can access the user roles like so:
user = User.find(1)
User.roles
This gives me the roles assigned to the user, I can then access the journal model like so:
journals = user.roles.first.journals
This gets me the journals associated with the user based on the roles. I want to be able to access the journals like so user.journals
In my user model I have tried this:
def journals
self.roles.collect { |role| role.journals }.flatten
end
This gets me the journals in a flatten array but unfortunately I am unable to access anything associated with journals in this case, e.g in the journals model it has:
has_many :items
When I try to access user.journals.items it does not work as it is a flatten array which I am trying to access the has_many association.
Is it possible to get the user.journals another way other than the way I have shown above with the collect method?
Hope you guys understand what I mean, if not let me know and ill try to explain it better.
Cheers
Eef
If you want to have user.journals you should write query by hand. As far as I know Rails does has_many :through associations (habtm is a kind of has_many :through) one level deep. You can use has_many with finder_sql.
user.journals.items in your example doesn't work, becouse journals is an array and it doesn't have items method associated. So, you need to select one journal and then call items:
user.journals.first.items
I would also modify your journals method:
def journals
self.roles(:include => :journals).collect { |role| role.journals }.flatten.uniq
end
uniq removes duplicates and :inlcude => :journals should improve sql queries.
Similar question https://stackoverflow.com/questions/2802539/ruby-on-rails-join-table-associations
You can use Journal.scoped to create scope with conditions you need. As you have many-to-many association for journals-roles, you need to access joining table either with separate query or with inner select:
def journals
Journal.scoped(:conditions => ["journals.id in (Select journal_id from journals_roles where role_id in (?))", role_ids])
end
Then you can use user.journals.all(:include => :items) etc