Let's say I have three models: User, Blog, and Post. My user model will have:
User Model
has_one :blog
has_many :posts through: :blog
Blog Model
belongs_to :user
has_many :posts
Post
belongs_to :blog
When I do a factory, I can do something like this:
FactoryGirl.define do
factory :post do
title "something"
content "long text"
blog
end
end
Which creates a post belonging to a blog. What I don't see is what I should put in :post so that FactoryGirl creates the user, then creates a blog belonging to that user, and then creates a post belonging to that blog/user. I didn't really see anything in FactoryGirl's documentation to address this.
FactoryGirl.define do
factory :post do
title "something"
content "long text"
blog
end
factory :blog do
user
# blog attributes
end
factory :user do
# user attributes
end
end
Then
#post = FactoryGirl.create(:post)
#blog = #post.blog
#user = #post.blog.user
Related
I've tried wrapping my mind around how to navigate the associations I want, but I can't seem to figure it out. I'm trying to get all the Posts given a Tag. Each Post currently has a title and body text, both of which are represented as TaggedText. Each TaggedText can have many unique tags — like tagging multiple people/pages in a Facebook post (uniqueness is enforced in the model when saving an instance).
class Tag < ActiveRecord::Base
has_many :tagged_texts, through: :tag_ranges
end
class Post < ActiveRecord::Base
has_many :tagged_texts
end
class TaggedText < ActiveRecord::Base
# Each TaggedText cannot have more than one of each tag
has_many :tags, through: :tag_ranges
belongs_to :post
end
class TagRange < ActiveRecord::Base
# TaggedText cannot have more than one of each tag
belongs_to :tagged_text
belongs_to :tag
end
I tried joining the tables, but I get the error Association named 'tag_ranges' was not found on Post:
def get_posts_by_tag(tag, page, posts_per_page)
Post
.joins(:tagged_texts)
.joins(:tag_ranges)
.joins(:tags)
.where('tag.id = ?', tag.id)
.uniq
.limit(posts_per_page)
.offset(page - 1)
.to_a
end
What am I missing to get the query to work — or should I restructure my models and associations somehow?
As you error states, you need to add a tag_ranges association to your Post model. I've also added a few associations that you may or may not find useful, and one that will simplify your query greatly. Not that your TagRange class's associations are fine as is.
class Tag < ActiveRecord::Base
has_many :tag_ranges # need this association in order to get tagged_texts
has_many :tagged_texts, through: :tag_ranges
has_many :posts, -> { uniq }, through: :tagged_texts # posts with the given tag
end
class Post < ActiveRecord::Base
has_many :tagged_texts
has_many :tag_ranges, through: :tagged_texts # Post now has association named 'tagged_ranges'
has_many :tags, -> { uniq }, through: :tag_ranges # tags that given post has
end
class TaggedText < ActiveRecord::Base
has_many :tag_ranges # all tag ranges for a tag text
has_many :tags, through: :tag_range
belongs_to :post
end
And now, your query to get all the posts for a tag:
def get_posts_by_tag(tag, page, posts_per_page)
tag.posts.limit(posts_per_page).offset(page - 1).to_a
end
Hopefully this helps!
I'm using the public_activity gem with rails 4 to generate an activity feed. The models are as follows:
Class Post
has_many :comments
has_many :taggings
has_many :tags, through: :taggings
Class Comment
belongs_to :post
Class Tagging
belongs_to :post
belongs_to :tag
Class Tag
has_many :posts, through: :taggings
has_many :taggings
On the Tags#show action, I'd like to show a feed of all activities of posts and comments belonging to posts belonging to that particular tag. How do I write the query?
class TagsController
activities = PublicActivity::Activity.order("created_at desc").where('trackable_type =? OR trackable_type = ?', 'Post', 'Comment')
#activities =.....
SOLUTION.
Ok managed to get it working.
TagsController
def show
post_ids = #tag.posts.map(&:id)
comment_ids = Comment.where('post_id IN (?)', post_ids).map(&:id)
#activities = PublicActivity::Activity.
where("(trackable_id IN (?) AND trackable_type = 'Post')
or (trackable_id IN (?) AND trackable_type = 'Comment')", post_ids, comment_ids).
order("created_at desc")
end
It's ugly and unsatisfying but it gets the job done. Bonus points for whoever can optimize this query!
I am using cancan to authorize my controller actions. One of classes where access is authorized by cancan is a tree, implemented with acts_as_ancestry. I'm having problems using load_and_authorize_resource when the user is not permitted to access the root level, but rather is allowed access starting at an interior node.
Here are some relavant class definitions:
class User < ActiveRecord::Base
belongs_to :organization, :inverse_of => :users
end
class Post < ActiveRecord::Base
belongs_to :organization, :inverse_of => :posts
end
class Organization < ActiveRecord::Base
has_ancestry :cache_depth => true
has_many :users, :inverse_of => :organization
has_many :posts, :inverse_of => :organization
end
The rules for managing posts are "You can manage posts in any organization below yours". My cancan abilities definition is this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
# subtree_ids is added by acts_as_ancestry
can :manage, Post, {:organization_id => user.organization.subtree_ids}
end
end
In the controller, I have this (other actions omitted)
class PostsController < ApplicationController
load_and_authorize_resource :post
def index
end
def new
end
end
Everything works fine when the authorized user belongs to the root organization. However, when I login as a user authorized at an internal node, the index action works fine, but when the new action is invoked, I get a can-can authorization error.
Here is what I see in the log:
Access denied on new #<Post id: nil, organization_id: 1>
The organization_id 1 (the root) is coming from the schema:
create_table "posts", :force => true do |t|
t.integer "organization_id", :default => 1
end
With cancan, the new action will build a new Post and assign it to #post. When it does this, it will initialize all the attributes with values taken from the can definition in Abilities.rb. However, it will not do anything if those attributes are Arrays, Hashes or Ranges and the default value ends up coming from the schema.
How can I authorize users to manage posts in their subtree, but when they create a new post, default it to their organization?
In cancan, if the #post variable is already initialized by you, it will not call load_resource on it, only do the authorize part. See this part of the docs: https://github.com/ryanb/cancan/wiki/Authorizing-controller-actions, "Override loading".
So the simplest solution is to take control of the initialization yourself and make it what you need, like here:
class PostsController < ApplicationController
before_filter :initialize_post, :only => [:new, :create]
def initialize_post
#post = current_user.organization.posts.build(params[:post]||{:name=>'Smashing Kittens'})
end
load_and_authorize_resource :post
def index
end
def new
end
def create
end
end
You can see it working in this test project that I created from your post: https://github.com/robmathews/cancan_test.
I had a similar issue and ended up writing ancestry related permissions in blocks like so:
can :manage, Post do |post|
post.organization.subtree_ids.include?(user.organization_id)
end
I am currently building very simple Comment system on Rails. The primary models are User, Albumpost, and Comment. Users can post Albumposts. For each Albumpost, Users can add Comments to the Albumpost. As a result, a Comment belongs to a User and belongs to an Albumpost.
The problem I'm having is that even with the proper associations in my models (see below), I can't get
#comment.user.name
when I'm trying to render the comments in the albumpost 'show' page (/views/albumposts/show.html.erb). When I go to the page, I can't get #comment.user.name (doesn't understand the association) and get a
"undefined method `name' for nil:NilClass"
Oddly I can get
#comment.albumpost.content
I've double-checked my models and also added the proper foreign keys to the models. Am I doing something wrong in the controllers?
Here are my models:
class Comment < ActiveRecord::Base
attr_accessible :body, :albumpost_id, :user_id
belongs_to :albumpost
belongs_to :user
end
class Albumpost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
has_many :comments, dependent: :destroy
end
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_many :albumposts, dependent: :destroy
has_many :comments, dependent: :destroy
end
Here are the relevant parts of my Albumpost and Comments controllers:
class AlbumpostsController < ApplicationController
def show
#albumpost = Albumpost.find(params[:id])
#comments = #albumpost.comments
#comment = Comment.new
#comment.albumpost_id = #albumpost.id
#comment.user_id = current_user.id
end
end
class CommentsController < ApplicationController
def create
albumpost_id = params[:comment].delete(:albumpost_id)
#comment = Comment.new(params[:comment])
#comment.albumpost_id = albumpost_id
#comment.user_id = current_user.id
#comment.save
redirect_to albumpost_path(#comment.albumpost)
end
end
I think you should prefer setting objects to relations instead of setting their ids. For example, you should do this:
#comment.user = current_user
instead of
#comment.user_id = current_user.id
ActiveRecord will take care of setting corresponding *_id fields. I'm not sure how it handles the reverse. (it should autoload though, if I understand correctly)
class Hashtag < ActiveRecord::Base
has_many :messages
end
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :hashtag, :foreign_key => "hashtag_id"
belongs_to :user, :foreign_key => "user_id"
end
Now if in the hashtag's show view we want to show all the messages belonging to hashtag and also an input box to create a new message.
Any kind of code examples or links or videos will be helpful ?
If the association is set up correctly, calling hashtag.messages will return an array of messages associated with that specific hashtag.
To create messages on the hashtag 'show' page you'll need to create the new message in hashtag#show.
#hashtag.messages.build
On the hashtags/show.html page, add a form for the user to edit the message. Then submit the message to message#create.