I have two entities Posts and Comments associated as follows
class Post < ActiveRecord::Base
attr_accessible :title, :msg
has_many :comments
end
class Comment < ActiveRecord::Base
attr_accessible :msg
belongs_to :post
scope :search, lambda { |msg| where(arel_table[:msg].matches('%#{msg}%'))}
end
The scope :search now search only for comments(msg), I want to write another scope to search for posts(msg) in comments.
How to write this?
try the following (I favor class methods than scopes with lambdas because they look cleaner and are easier to read)
# comment.rb
def self.search(msg)
where(arel_table[:msg].matches('%#{msg}%'))
end
def self.post_search(msg)
joins(:post).where(Post.arel_table[:msg].matches("%#{msg}%"))
end
Related
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(', ') %>
I have a Region > Store > Device hierarchy, where each of these things can have a config_set.
class Region < ActiveRecord::Base
has_many :stores, dependent: :destroy
has_one :config_set, as: :configurable
end
class Store < ActiveRecord::Base
has_many :devices
has_one :config_set, as: :configurable
belongs_to :region
end
class Device < ActiveRecord::Base
has_one :config_set, as: :configurable
belongs_to :store
end
class ConfigSet < ActiveRecord::Base
belongs_to :configurable, polymorphic: true
end
From the config_set show page, I want to generate a link to whatever configurable that config_set belongs to. link_to is using polymorphic_url internally, which wants either a #device, a [#store, #device], or a [#region, #store, #device]. Is there a simple way to deal with this?
Found out a not very nice solution abusing the fact that to polymorphic_url #device and [nil, #device] are equivalent, combined with ruby's try :
<%= link_to 'Back', [#config_set.configurable.try(:store).try(:region), #config_set.configurable.try(:store) || #config_set.configurable.try(:region), #config_set.configurable] %>
I would like to use pluck method in Active Model Serializer for an object association:
Post has_many :comments
Is there a way to override
has_many :comments
in the serializer to use pluck(:id, :title) on the comments ?
You can use block with has_many to extend your association with methods. See comment "Use a block to extend your associations" here.
class Post < ActiveRecord::Base
has_many :comments do
def plucked()
select("id, title")
end
end
end
or other approach, from the same link can be, have your custom sql query when fetching comments from the database:
has_many :comments, :class_name => 'Comment', :finder_sql => %q(
SELECT id, title
FROM comments
WHERE post_id = #{id}
)
In rails documentation, It shows, there is an option :select, which can also be used for this purpose.
What I usually do in that situation, I have two different serializers CommentSerializer and CommentInfoSerializer. So CommentInfoSerializer only includes minimum attributes that I want to response in embedded attribute of their parent resource.
class CommentSerializer < ActiveModel::Serializer
attributes :id, :title, :body
end
class CommentInfoSerializer < ActiveModel::Serializer
attributes :id, :title
end
class PostSerializer < ActiveModel::Serializer
has_many :comments, serializer: CommentInfoSerializer
end
We've got 2 models & a join model:
#app/models/message.rb
Class Message < ActiveRecord::Base
has_many :image_messages
has_many :images, through: :image_messages
end
#app/models/image.rb
Class Image < ActiveRecord::Base
has_many :image_messages
has_many :messages, through: :image_messages
end
#app/models/image_message.rb
Class ImageMessage < ActiveRecord::Base
belongs_to :image
belongs_to :message
end
Extra Attributes
We're looking to extract the extra attributes from the join model (ImageMessage) and have them accessible in the Message model:
#message.image_messages.first.caption # -> what happens now
#message.images.first.caption #-> we want
We've already achieved this using the select method when declaring the association:
#app/models/message.rb
has_many :images, -> { select("#{Image.table_name}.*", "#{ImageMessage.table_name}.caption AS caption") }, class_name: 'Image', through: :image_messages, dependent: :destroy
Delegate
We've just found the delegate method, which does exactly what this needs. However, it only seems to work for has_one and belongs_to associations
We just got this working with a single association, but it seems it does not work for collections (just takes you to a public method)
Question
Do you know any way we could return the .caption attribute from the ImageMessage join model through the Image model?
We have this currently:
#app/models/image.rb
Class Message < ActiveRecord::Base
has_many :image_messages
has_many :messages, through: :image_messages
delegate :caption, to: :image_messages, allow_nil: true
end
#app/models/image_message.rb
Class ImageMessage < ActiveRecord::Base
belongs_to :image
belongs_to :message
def self.caption # -> only works with class method
#what do we put here?
end
end
Update
Thanks to Billy Chan (for the instance method idea), we have got it working very tentatively:
#app/models/image.rb
Class Image < ActiveRecord::Base
#Caption
def caption
self.image_messages.to_a
end
end
#app/views/messages/show.html.erb
<%= #message.images.each_with_index do |i, index| %>
<%= i.caption[index][:caption] %> #-> works, but super sketchy
<% end %>
Any way to refactor, specifically to get it so that each time .caption is called, it returns the image_message.caption value for that particular record?
delegate is just a shorthand as equivalent instance method. It's not a solution for all, and there are even some debate that it's not so explicit.
You can use an instance method when simple delegate can't fit.
I reviewed and found any association is unnecessary is this case. The ImageMessage's class method caption is more like a constant, you can refer it directly.
def image_message_caption
ImageMessage.caption
end
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.