I have a Post model that has_many :authors and has_many :tags through :tag_posts, an Author model that has_many :posts and has_many :tags through :posts, and a Tag model that has_many :posts through :tag_posts.
Posts have to pass moderation (stored as an enum Post.status) before they're visible on the site. Moderators can see unmoderated posts by looking for ones with a different Post.status, but regular users should never be able to see them.
Is there any way I can filter all these associations to make sure they only return the posts that moderators have approved?
For example: if I call Author#tags now, that returns all the tags on all the posts that author has written, but I only want it to return the tags on the approved posts that the author has written. Say the author has two posts, the first is approved and is tagged 'octopus', and the second hasn't been approved yet and is tagged 'squid': calling #tags on that author should only return the octopus tag, not the squid tag.
I have a Post model that has_many :authors and has_many :tags through :tag_posts, an Author model that has_many :posts and has_many :tags through :posts, and a Tag model that has_many :posts through :tag_posts.
First thing's first, let's tidy up those relations. I believe this is what you really want:
class Post < ActiveRecord::Base
belongs_to :author # NOT has_many
has_and_belongs_to_many :tags # use an implicit join table
end
class Author < ActiveRecord::Base
has_many :posts
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts # use an implicit join table
end
Is there any way I can filter all these associations to make sure they only return the posts that moderators have approved?
Yes. Without going into to much detail, the key technique is to define a scope on the Post model; something like this:
class Post < ActiveRecord::Base
scope :approved -> { where(status: Post.statuses['approved']) }
end
You can then show only the approved posts via Post.approved; or only the approved posts for a certain user via Post.where(user: foo).approved.
In order the display a list of tags, you could then, for example, just do:
Post.where(user: foo).approved.map(&:tags).uniq
This will only give you an Array, not an ActiveRecord::Collection. But that's probably good enough, for your purposes.
You can scope has_many itself.
Eg.
class Author
has_many :moderated_posts, -> { moderated }, class_name: 'Post'
end
class Post
scope :moderated, -> { where(status: approved_statuses) }
end
Related
I would like to achieve something as follows where PersonSubject has many topics, but the choices of these topics are limited to the the selection of topics through another model (ie: through the associated subject):
class Topic < ApplicationRecord
belongs_to :subject
end
class Subject < ApplicationRecord
has_many :topics
end
class PersonSubject < ApplicationRecord
belongs_to :person
belongs_to :subject
has_many :topics # where the choices are limited to the subject.skills
end
I would then like if any person_subject.subject.topics are deleted (or association removed), it would automatically update the person_subject.topics to no longer "point" to the Topic(s) that were deleted.
Is this possible?
You can use a lambda to put arbitrary filters on an association. See What is the equivalent of the has_many 'conditions' option in Rails 4?
has_many :topics, -> { where(skill: subject.skills) }
I don't know that this is exact code will work without seeing your schema (what is the data type of subject.skills, and how do you join this with topic?). But hopefully this gets you on the right track
edit
in response to your comment, I think
has_many :topics, through: :skills
would work
A posts model:
class Post < ActiveRecord::Base
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
and a tags model:
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, through: :taggings
and the associated join table:
class Tagging < ActiveRecord::Base
belongs_to :tag, dependent: :destroy
belongs_to :post
so, the dependent: :destroy in Post makes the db entries in Tagging to be destroyed when doing for instance Post.last.destroy, and the dependent: :destroy in Tagging makes the associated Tag object to be destroyed as well. Fine.
My problem is, 2 posts may share the same Tag, and I would like to destroy that tag -only- if the post being deleted is the last one referencing it. I don't allow duplicate entries in the Tag table. The tags for a post are submitted as a a string with the tags separated by commas, and upon creation of a post I do the following:
def tag_names=(names)
self.tags = names.split(",").map{ |tag| Tag.where(name: tag.squish).first_or_create! }
end
Any idea on how I could achieve this? Thanks!
Are you using a plugin? act_as_taggable or something like that?
If so, take a look to the docs because probably is already implemented.
If not, you can always implement the after_destroy callback to count the elements associated to a tag and delete the tag in the case that you have "empty" tags.
For instance, in your Post model:
class Post
before_destroy :clean_up_tags
protected
def clean_up_tags
tags_to_delete = Tagging.where(id: self.tag_ids).group(:tag_id).having("count(distinct taggable_id) = 1").pluck(:id)
Tag.find(tags_to_delete).map(&:destroy)
end
end
This method is assuming that you have a method tag_ids that returns the tags associated to an specific Post and that your taggings model is polymorphic).
As you probably have more than one model with the tags feature, a good approach would be packing this method into a module and include it in all the models, in this way you keep the things DRY.
I'm currently developing an application using Rails 3.2 and have run into a bit of a problem. I know this has been asked hundreds of times before but I couldn't find an answer that solved it. Here is a similar ER: http://i.stack.imgur.com/x5V0G.png
Fairly obvious what i'm trying to do. I'm hoping for the association to read like the following:
Supplier.first.theatres.first.events.first.orders
TourEvent.first.orders
Tour.first.orders
Now it would be nice to be able to define my models like so:
class Event < ActiveRecord::Base
has_many :orders
belongs_to :eventable, polymorphic: true
# id, eventable_id, eventable_type, title, date, price
end
class TourEvent < Event
belongs_to :tour
# id, tour_id, rendezvous, guide_name
end
class Tour < ActiveRecord::Base
has_many :events, class_name: 'TourEvent'
# id, name, venue, duration
end
But I understand that's reserved for "STI" rather than "MTI". Any ideas how to get my solution working without the need for complicated mixins or plugins? Or is it just not possible?
I think you can make something like this:
suppliers
has_many :events
events
belongs_to :suppliers
belongs_to :institutions
has_many :orders
# id, supplier_id, institution_id, ...
institutions
# id, type, title, ...
types: theatre, club, tour
orders
belongs_to :events
# id, event_id
Then, you can access to event orders:
Supplier.first.events.first.orders
I have users which have products through a habtm link, which is working.
I want to add a link between the user model and the product model, to keep track of the creator of that product (who doesn't always own the product, of course)
But when I write in my user and product models a new link, the application screws up because I can't distinguish the creator of a product from the owner of (a lot of) products.
Can you help me ? Here is my models :
class Product < ActiveRecord::Base
belongs_to :group
has_and_belongs_to_many :authors
has_and_belongs_to_many :users # THIS IS OK (with appart table)
has_many :users, :as => creator # THIS LINE DOES NOT WORK AT THE MOMENT
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
belongs_to :user # THIS LINE DOES NOT WORK AT THE MOMENT
default_scope :order => "username ASC"
end
The database is ok, and I can store the user_id under the creator column from my product, but the link product.creator.name doesn't work (because of the model is not correct, I presume), I can only read the user_id which is in the column but not get the user object with all his attributes.
rem : user.products works perfectly, but only when I remove my new link for creator...
Thanks !
The :as syntax is for polymorphic associations - this is not what you want. Your comments about your column names are a bit ambiguous, so I'm going to assume that you have a user_id column in your products table which is the id of the creator of that product (I'm only including the relevant associations)...
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :creator, :foreign_key => :user_id, :class_name => "User"
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
has_many :owned_products, :class_name => "Product"
end
There is a nice gem for that: https://github.com/spovich/userstamp
I used it in my project and it works good - you just adds 'stampable' to your models and then creator_id (and updater_id) are filled authomaticly.
I'm trying to figure out how to best way to create associations for the following models:
User
Category
Post
Comments
Here are the requirements I'm trying to meet:
A user can have many posts
A post belongs to a user
A post can have many comments
A comment belongs to a post
A post can belong to a category
A user does NOT have many categories.
(The number of categories is fixed and the same for all users)
A category can have many posts
In terms of routing, I'd like to be able to access a post within a certain category for a specific user. For example:
http://domain.com/users/1/categories/1/posts
Since there is no direct relationship between User and Category, I'm not quite sure how to best set up the associations. And I'm totally lost on how to configure the routes.
Here's what I have for my models:
class User < ActiveRecord::Base
has_many :posts
end
class Category < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :metric
has_many :comments
end
class Comments < ActiveRecord::Base
belongs_to :post
end
Is this a case where I should be using has_many :through relationships? Or something more complex like polymorphic associations? Thanks in advance!
Yes, it would be a very good idea to use :
User has_many :comments, :through => :posts
If you like, you can also get categories comments, by :
Category has_many :comments, :through => :posts
Remember that a through association is just a facility that allows you to do things like user.comments directly (and through is the way for the association to find the user comment that is referred to post model).