Forem gem: how to link a forum to other models - ruby-on-rails

I have groups (Group model) in my app, which represent groups of people.
I want each group to have its own forum.
Should I just have the forum id in the groups table? It doesn't feel right. If I did it myself, the forum would have a polymorphic association to a "forumable" element (groups in this case, but I have other models that would need a forum).
Any opinions on what I should do? Modify the gem to fit my needs, or just have the forum_id in my models that need a forum? Or another solution maybe?

I'm the guy who started Forem (its the volunteers who did most of the hard work, though!), I think I can answer this question.
If you want only certain groups to have access to one and only one forum then you can put the forum_id field on the groups table and do it that way. What you can do then is override the can_read_forem_forum? method in your User model to perform a permission check for that user:
def can_read_forem_forum?(forum)
groups.where(:forum_id => forum.id).any?
end
This is used in Forem's ability model to determine whether or not a person can access a forum. What this method is going to do is that it will only return groups for that user that have link that specific forum. If there are any, then it's known that the user can access that forum.
Now if you're going the other route where a group may have access to many forums, well then you'd define a joins table between groups and forem_forums (called forum_groups) and define it as an association in your Group model like this:
has_many :forum_groups
has_many :forums, :through => :forum_groups, :class_name => "Forem::Forum"
You would need to also define a new model inside your application for this forum_groups association, it would be called ForumGroup and go a little like this:
class ForumGroup < ActiveRecord::Base
belongs_to :forum, :class_name => "Forem::Forum"
belongs_to :group
end
We're doing it this way so you have an easy way to manage the associations between forums and groups. If you did has_and_belongs_to_many, it generally only provides a gigantic pain in the ass when you want to delete one specific record from that join table.
Now, with that all nicely set up, the method you want to define in your User model is this one:
def can_read_forem_forum?(forum)
groups.joins(:forums).where("forem_forums.id = ?", forum.id).any?
end
Same thing, except this time we find all the groups that are linked to a specific forum through that association we set up earlier. This will do an INNER JOIN on the forum_groups table, and then another on the forem_forums table, getting the data required.
I hope this helps you, and thanks for using Forem!

Related

Looking for a way to track history in rails database

I'm considering this an add-on question of sorts to the thread below:
Using join tables in ruby on rails
So we have 'Student' and 'Course' scaffolds joined by a many-to-many association, but in addition there is also a 'Semester' scaffold and what I wish to do is, for each student that is added to a course, for the application to search for previous iterations of the same course through past semesters, to that it's known how many times a student has taken that class before. I'm kind of mixed up at the moment as to how to implement this, so I was hoping someone could help me pin down the logic and code I should be operating by.
Some underlying assumptions I have so far:
'Course' and 'Semester' should, like 'Student' and 'Course', be joined
by a many-to-many association (many courses are taught per semester,
and a course is taught for more than one semester).
There should be an action (let's say get_student) within the course
controller to locate the student via student_id. This would be the main area I'm scratching my head as to what to do. What would this method look like in code?
Within the student-course join table I should have an attribute
'attempts' which increments each time get_student finds this
student_id combined with the course_id that calls the method.This
would be the mechanism that actually tells how many times the course
had been attempted by the student.
I initially wondered if there should be a 'semester' controller
action to activate get_student across all semesters, but now I'm
thinking that get_student should work fine without that.
Appreciate any help I can get on this. Thanks.
This is not a good answer, just a comment.
I would comment, but hear will be more clear. I ll update for the other points. This is just an ongoing feedback/discussion, not an answer.
class Semester < ApplicationRecord
has_many :courses
end
class Course < ApplicationRecord
has_many :students
end
And
semester.courses[0].students => outputs the students array for that
This could be the method to calculate the number of student that did that course:
def studentForCourse
#input_params.course_id => course id you are selecting
semester = Semester.find(input_params)
semester.courses.each do |course|
if course.id = input_params.course_id
nstudents = course.students.size
end
end

Controller design for likable/followable objects

I have three models, User, Product, Like. That is, a user can (un)like a product...
I wonder what would be best practice to design controller and models.
Creating likes_controller and process create/destroy actions
Creating like/unlike actions inside products_controller
or something else.
Second question; into which model should I put methods such as "retrieve_likes," "liked?," or "like! /unlike!"
Edit: I found this excellent gem through TeachMeToCode.com, https://github.com/cavneb/make_flaggable
I've done both option 1 and 2 before and they've both worked fine. It really depends on your philosophy and how much of the REST kool-aid you've drank. Go with #1 to be RESTful. I've found sticking to a more RESTful interface makes testing seem a bit cleaner and the code, in general, seem a bit cleaner. At the expense, however, of having way more files.
As for the models, you could create a Likable module which contains the methods for "liked?", etc... Then you can include this module in any of the models which need them. Take a look at some gems that extend ActiveRecord models to get an idea how to do that.
You could have 3 tables:
users: user_id, name, like_id
products: product_id, info, like_id
likes: like_id, user_id, product_id
In your User model:
has_many :likes
In your Product model:
has_many :likes
In your Like model:
belongs_to product
belongs_to user
This would allow you to do things like call <%= #product.likes.count %> for a count of the number of times something was liked.
Or in the users controller: #liked_products = User.likes.map(&:product).
To create links that populate the likes table, you will need to make a likes controller with the add_like and remove_like methods.
You may also need to learn about something called "nested routing" to get it all working.

DRY way to add same condition on many queries

I use rails 3.0.5
I've User and Company models, a user belongs_to a Company.
The Product, Bill, a several others also belongs_to a Company.
For obvious reasons, a User can acts on a Product, Bill... only if the Product's Company is the same as the User's Company.
I can use custom scope queries adding the condition on the company for everything, but it's not DRY at all.
How would you do that the the nice way?
Thanks
#product = current_user.company.products.find params[:id]
should work. If you want to DRY it up further I'd recommend using the plugin InheritedResources which has a sweet method called begin_of_association_chain (see in the README under overwriting defaults) that let's you define this stuff globally.
Have you looked into named scopes? http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html
Seems like exactly what you are looking for
You can define a association
has_many :products, :through => :company
Then you can do
user.products.find(params[:id])
Not sure if this is the right way to do it!!!

Access join table data in rails :through associations

I have three tables/models. User, Alliance and Alliance_Membership. The latter is a join table describing the :Alliance has_many :Users through :Alliance_Membership relationship. (:user has one :alliance)
Everything works ok, but Alliance_Membership now has an extra field called 'rank'. I was thinking of the best way to access this little piece of information (the rank).
It seems that when i do "alliance.users", where alliance is the user's current alliance object, i get all the users information, but i do not get the rank as well. I only get the attributes of the user model. Now, i can create a helper or function like getUserRole to do this for me based on the user, but i feel that there is a better way that better works with the Active Record associations. Is there really a better way ?
Thanx for reading :)
Your associations are all wrong - they shouldn't have capital letters. These are the rules, as seen in my other answer where i told you how to set this up yesterday :)
Class names: Always camelcase like AllianceMembership (NOT Alliance_Membership!)
table names, variable names, methods and associations: always underscored and lower case:
has_many :users, :through => :alliance_memberships
To find the rank for a given user of a given alliance (held in #alliance and #user), do
#membership = #alliance.alliance_memberships.find_by_user_id(#user.id)
You could indeed wrap this in a method of alliance:
def rank_for_user(user)
self.alliance_memberships.find_by_user_id(user.id).rank
end

How do I organize/store a "type" field for a comments table?

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.

Resources