Rails Model: How to do the relation - ruby-on-rails

I would like to do an app, but I am stuck on how to design the model. I have these models so far: User, Post, Tag, Like, Comment. I created for each a model, controller, view and migration file. So far so good, BUT now my question is should I just do the relations between them or create these models too: PostTag, PostLike, PostComment. You know, they would just have the relation between those, so when somebody deletes a tag in a post, he actually just deletes the relation and not the tag itself because the tag is maybe used in another post.

You don't need separate models for relations. Just add has_many and belongs_to to existing models and add needed fields (post_id, user_id etc.) to your database tables.
And tags won't disappear when you delete it from a post in case you won't write a code for a tag destroy. Nothing happens by itself.
P.S. I recommend to use acts-as-taggable-on gem for tags

You will only need to add a Tagging model, and use a polymorphic association to handle Likes. So, you need a User, Post, Comment, Like, Tag, and Tagging
You will not need a controller for Tags or Taggings or Likes
If you want users to be able to create posts and comments as well as like both posts and comments, then here is one way to configure your models:
User model:
class User < ActiveRecord::Base
has_many :posts
has_many :comments
has_many :likes, as: :likeable, dependent: :destroy
end
Post model:
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
has_many :likes, as: :likeable, dependent: :destroy
has_many :taggings
has_many :tags, through: :taggings
def self.tagged_with(name)
Tag.find_by_name!(name).posts
end
def self.tag_counts
Tag.select("tags.*, count(taggings.tag_id) as count").
joins(:taggings).group("taggings.tag_id")
end
def tag_list
tags.map(&:name).join(", ")
end
def tag_list=(names)
self.tags = names.split(",").map do |n|
Tag.where(name: n.strip).first_or_create!
end
end
end
Comment model:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
Like model:
class Like < ActiveRecord::Base
belongs_to :likeable, polymorphic: true
end
Tag migration generator:
rails g model tag name
Tag model:
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, through: :taggings
end
Tagging migration generator:
rails g model tagging tag:belongs_to post:belongs_to
Tagging model
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :post
end
The Post model assumes your tag uses the attribute :name
In order to access your tagged Posts add this route
get 'tags/:tag', to: 'posts#index', as: :tag
And change your index action in your Posts controller to this:
def index
if params[:tag]
#posts = Post.tagged_with(params[:tag])
else
#posts = Post.all
end
end
Finally, add a link to your Post Show view like this:
<%= raw #post.tags.map(&:name).map { |t| link_to t, tag_path(t) }.join(', ') %>

Related

Issues with a tag system's creation

I am following this tutorial to create some custom tags. I the instructions excepted for the AJAX and Foundation part since I haven't built my website this way. I'm experiencing an issue with the "tag research" part when you only need to click on a tag to see all posts about the tag, Rails tells me the following:
undefined method `articles' for #<Tag:0x007f0a690eeb40>
Here are my files adapted from the tutorial:
article.rb
class Article < ApplicationRecord
mount_uploader :image, ArticleUploader
extend FriendlyId
friendly_id :titre, use: :slugged
has_many :taggings
has_many :tags, through: :taggings
def all_tags=(names)
self.tags = names.split(',').map do |name|
Tag.where(name: name.strip).first_or_create!
end
end
def all_tags
self.tags.map(&:name).join(", ")
end
def self.tagged_with(name)
Tag.find_by_name!(name).articles # Issue comes from here
end
end
tag.rb
class Tag < ApplicationRecord
has_many :taggings
has_many :tags, through: :taggings
end
tagging.rb
class Tagging < ApplicationRecord
belongs_to :article
belongs_to :tag
end
config/routes.rb
get 'tags/:tag', to: 'articles#index', as: "tag"
resources :articles
Please ask if you need to see something else
As you can see, i simply used my 'Article' model that already existed when starting the tutorial, so I used 'articles' instead of 'posts' as the author asked. I can't find anything related on Google and i'm not working with any IDE right now so I have no clue about what to do..
Any help is welcomed!
In the Tag model you are trying to associate tags only means you are associating itself. So change your association
has_many :tags, through: :taggings
to:
has_many :articles, through: :taggings
and try. You should not face issue.

Rails: collect through association?

For example, Topic has many comment, and each comment belongs to a user.
How can I get all the users have commented on the one topic, efficiently?
Now I doing this by
#commenters = #topic.comments.collect do |post|
user = post.user
user
end
And, how can I make #commenters uniq? Turn it into an array?
You could define through relation
Rails through association
Topic model
class Topic < ActiveRecord::Base
...
has_many :comments
has_many :users,
:through => :comments # add this line, it will enable association
...
end
Comment model
class Comment < ActiveRecord::Base
..
belongs_to :topic
belongs_to :user
..
end
User model
class User < ActiveRecord::Base
...
has_many :comments
...
end
then you can find users on topic.
#topic.users

Add an attribute to a joining model in Rails has_many through

I have a User model and a Book model joined with a Like model.
class Book < ActiveRecord::Base
belongs_to :user
# the like associations
has_many :likes
has_many :liking_users, :through => :likes, :source => :user
class User < ActiveRecord::Base
has_many :books
# the like associations
has_many :likes
has_many :liked_books, :through => :likes, :source => :book
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :book
I want to add an attribute to the Like model so while right now a User can Like a book to add it to their profile, I want the User to be able to write a recommendation.
I generated the attribute Recommendation:text to the Like (joining) model, but am unsure how to add this to views so a User can write a recommendation that will be tied to the Like (and thus that book and user).
I'm looking at this post - Rails has_many :through Find by Extra Attributes in Join Model - which describes something similar but does not explain how to implement this in the views.
Let me know if you can point me in the right direction. Thanks!
I think you should create a separate model for this Recommendation, dependent of the Like model:
class Book < ActiveRecord::Base
belongs_to :user
has_many :comments
has_many :commenters, :through => :comments, :source => :user
class User < ActiveRecord::Base
has_many :books
has_many :comments
has_many :commented_books, :through => :comments, :source => :book
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :book
Then, the logic to create a comment for a book:
# Books Controller
def show
#book = Book.find(params[:id])
#comment = #book.comments.build
end
# show view of the book
form_for #comment do |form|
form.hidden_field :user_id, value: current_user.id
form.hidden_field :book_id
form.text_area :content # the name of the attribute for the content of the comment
form.submit "Post comment!"
end
To list all the comments of a specific User:
# users controller (profile page)
def show
#user = User.find(params[:id])
end
# show view of Users
#user.comments.includes(:book).each do |comment|
"Comment on the book '#{comment.book.name}' :"
comment.content
end
I suppose that the like functionnality works with ajax ? (remote: :true or in pure javascript)
You can append a form with the response of your request with the id of the new like, and again handle the recommendation by an ajax request

Rails: alternatives to using nested loops to find match in controller

There has to be a better way to do this. My Favorite model belongs to User while Applicant belongs to both Gig and User. I am trying to efficiently determine whether a user has applied for Gig that was favorited (<% if #application.present? %>).
I tried chaining the collection by using something like #favorites.each.gig to no avail. While the below index action for Favorites seems to work, it's really verbose and inefficient. What is a more succinct way of doing this?
def index
#favorites = Favorite.where(:candidate_id => current_candidate)
#applications = Applicant.where(:candidate_id => current_candidate)
#favorites.each do |favorite|
#applications.each do |application|
if favorite.gig.id == application.id
#application = application
end
end
end
end
class User
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :candidate
belongs_to :gig
end
class Applicant < ActiveRecord::Base
belongs_to :gig
belongs_to :candidate
end
class Candidate < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Gig < ActiveRecord::Base
belongs_to :employer
has_many :applicants
has_many :favorites
has_many :users, :through => :applicants
end
For lack of a better answer, here's my idea:
--
User
Your user model should be structured as such (I just highlighted foreign keys, which I imagine you'd have anyway):
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants, foreign_key: "candidate_id"
has_many :favorites, foreign_key: "candidate_id"
end
This means you'll be able to call:
current_candidate.favorites
current_candidate.applicants
This will remove the need for your #applications and #favorites queries
--
Favorite
You basically want to return a boolean of whether applicant is part of the favorite model or not. In essence, for each favorite the candidate has made, you'll be able to check if it's got an application
I would do this by setting an instance method on your favorites method using an ActiveRecord Association Extension, like so:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :favorites do
def applied?
self.applicant.exists? proxy_association.owner.gig.id
end
end
end
This will allow you to call:
<%= for favorite in current_candidate.favorites do %>
<%= if favorite.applied? %>
<% end %>
This is untested & highly speculative. I hope it gives you some ideas, though!

Modeling comments on rails

I have a Post model that looks like this:
# id :integer
# author_id :integer
# owner_id :integer
# owner_type :string(255)
# content :text
class Post < ActiveRecord::Base
belongs_to :author, class_name: 'User'
belongs_to :owner, polymorphic: true
end
The owner can be a User, Group, or a Place. I'm wondering what is the best approach to model a Comment. Considering that it shares most of its attributes with Post, I thought that the same Post model could serve as Comment using a relation like:
has_many :comments, class_name: 'Post', :as => :owner
But indeed I'm not happy at all with this solution since Post uses the same relation for storing its owner.
It's better to create a different model for comment? What about STI?
To make an abstraction of the real world and keep the things simple (clear, succinct), my suggestion is to use a Comment model:
class Comment < ActiveRecord::Base
belongs_to :post
end
class Post < ActiveRecord::Base
belongs_to :author, class_name: 'User'
belongs_to :owner, polymorphic: true
has_many :comments
end
If you are planning to add comments to another entity, for example, photos, use a Polymorphic Association:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
class Post < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Photo < ActiveRecord::Base
has_many :comments, :as => :commentable
#...
end
The rails tutorial does exactly this: guides.rubyonrails.org/getting_started.html. It makes a blog with a posts model and a comments controller attached to it.
resources :posts do
resources :comments
end
run this
$ rails generate controller Comments
And add this to the generated controller:
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:commenter, :body))
redirect_to post_path(#post)
end
end
You should look at this railscast:
http://railscasts.com/episodes/154-polymorphic-association?view=asciicast
It's a bit old, but it is one of the free ones. You do want to create a different model for your comments, and you want to create a polymorphic association.

Resources