I have a post controller that has many comments.
The post model has a field called has_comments which is a boolean (so I can quickly select from the database only posts that have comments).
To create a new comment for a post, I use the create action of my comments controller.
After I create the comment I need to update my post's has_comments field and set it to true.
I can update this field from the create action of my comments controller, but that doesn't seem right - I feel that I should really be using the post's update action, but I'm not sure if it's right to call it (via send?) from the create action of the comments controller.
Where should the code for updating the post be?
Thank you!
Why clutter your database with another column when the interface is programmatic? Make has_comments a method on Post:
def has_comments
comments.size > 0
end
Then implement a counter_cache as suggested to reduce the query load.
EDIT: Alternatively, after implementing a counter cache, you could use a named_scope on Post to retrieve all posts that have comments, using a single query, if that's the main goal:
class Comment
belongs_to :post, :counter_cache => true
end
class Post
named_scope :with_comments, {:conditions=>"comments_count > 0"}
end
EDIT: You can also avoid the famous n+1 query problem by a judicious use of :include:
posts = Post.find(:all, :include => :comments)
You could use before_save callback in your model.
Even better way would be to use built in :counter_cache option which automatically caches the number of comments for each post. (see http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001835 under options for belongs_to)
use after_save in comment model
def after_save
#this will set "has_comment" of the Specified Post to true if it's not true already
self.post.update_attribute('has_comment', true) unless self.post.has_comment
end
Related
I've got an ActiveAdmin index page
ActiveAdmin.register Bill
And I am trying to display links to associated models
index do
column "User" do |bill|
link_to bill.user.name, admin_user_path(bill.user)
end
end
But I run into the N+1 query problem - there's a query to fetch each user.
Is there a way of eager loading the bills' users?
The way to do this is to override the scoped_collection method (as noted in Jeff Ancel's answer) but call super to retain the existing scope. This way you retain any pagination/filtering which has been applied by ActiveAdmin, rather than starting from scratch.
ActiveAdmin.register Bill do
controller do
def scoped_collection
super.includes :user
end
end
index do
column "User" do |bill|
link_to bill.user.name, admin_user_path(bill.user)
end
end
end
As noted in official documentation at http://activeadmin.info/docs/2-resource-customization.html
There is an answer on a different post, but it describes well what you need to do here.
controller do
def scoped_collection
Bill.includes(:user)
end
end
Here, you will need to make sure you follow scope. So if your controller is scope_to'ed, then you will want to replace the model name above with the scope_to'ed param.
The existing answers were right at the time, but ActiveAdmin supports eager loading with a much more convenient syntax now:
ActiveAdmin.register Bill do
includes :user
end
See the docs for resource customization
IMPORTANT EDIT NOTE : what follows is actually false, see the comments for an explanation. However I leave this answer where it stands because it seems I'm not the only one to get confused by the guides, so maybe someone else will find it useful.
i assume that
class Bill < ActiveRecord::Base
belongs_to :user
end
so according to RoR guides it is already eager-loaded :
There’s no need to use :include for immediate associations – that is,
if you have Order belongs_to :customer, then the customer is
eager-loaded automatically when it’s needed.
you should check your SQL log if it's true (didn't know that myself, i was just verifying something about :include to answer you when i saw this... let me know)
I've found scoped_collection loads all the entries, instead of just the ones for the page you are displaying. I think a better option is apply_collection_decorator that will only preload the items you are effectively displaying.
controller do
def apply_collection_decorator(collection)
collection.includes(:user)
end
end
My models are like this: a discussion has_many posts (nested resource).
I want to add a starter_post_id column to the discussions table, and have it record the 'thread starter post id'. The discussion is created along with the post in the nested form, and that when the logic should be called, because other posts to that discussion will be replies not starter posts.
I am not sure what I need to do after the add_column db migration.
Do I need a belongs_to :post in my Discussion model?
What's the order of creation for these nested objects. e.g. parent's creation ends before child's starts? or will the parent constructor call the child constructor?
Which model should the starter post assignment logic go to? This is related to Q2 since both objects needs to be initiated, but preferably before the DB call.
I would use the created_at field from you post model to determine the starter_post of a discussion. No need for any columns.
Add something like this in your discussion model
def starter_post
self.posts.order("created_at ASC").first()
end
If you use this in you discussion.rb :
has_many :posts , :order => "created_at ASC"
you can then simply use :
def starter_post
self.posts.first()
end
I tried before_save and it won't work because at that point in time the discussion has no way to get hold of the starter post object. I was pointed out to use after_create instead.
def after_create
self.starter_post_id = self.posts.first.id
self.save!
end
This will cause one extra sql query, but it is better than doing it at the post model.
I used belongs_to so I can use discussion.start_post_id, but I guess it is optional.
I have a single text area input that I would like to get as a blob my new method on my controller, but would like to parse and otherwise mess with the input before it's saved.
I know I can arbitrarily set attributes on a model by saying something like
#post.user_id = current_user.id
where that attribute isn't coming directly from a form. My issue here though is that I want to set a nested model's values.
Let's say the association is post has_many comments and comment belongs_to post
Does post.comments just get set to a hash that looks like comments? Like
#post.comment = {'comment' => 'foo'}
Or something similar?
Thanks for any guidance on this.
Usually I'd say it's best to DRY up this sort of thing and just handle the parsing on the comments model itself with a before_save callback.
class Comment < ActiveRecord::Base
before_save :parse_comment
protected
def parse_comment
self.comment = ...
end
end
But if a callback isn't going to work for you, #corroded's suggestion should work.
if you have nested form fors, you can just get the comment values from your params via:
#post.comment.update_attributes(params[:comment])
(you should have called #post.build_comment in your #new though)
If you're looking to set them in your controller, then you need a hash 'container' for your comment like so:
{'comment' => {:message => 'foo', :author => current_user}}
or something like that
I have a model called Stem. I need a 'thumbs up' feature, so I have created a second model called Thumb, which consists of stem_id and user_id.
I'm also using the restful authentication plugin for user credentials.
I have the 'thumbs up' button working, which adds a row to the thumbs table fine, but I'd like to be able to check if the currently logged in user has already given a thumbs up to this particular stem.
I tried adding this to the Stem model:
def thumbed
Thumb.count_by_sql ["SELECT COUNT(*) FROM thumbs WHERE user_id = ? AND stem_id = ?", current_user.id, self.id ]
end
The problem here is that the stem model has no access to the current_user variable the the controllers have.
Is there a way I can get access to this property, or alternatively, is there another way I could go about checking this? I was hoping to get this as a property in the model because the stems are passed over to a Flex app using RubyAMF.
Thanks!
Your controller knows the current_user, and your Stem model probably shouldn't. You can, however clean up your code and avoid hard-wiring SQL with a named_scope and pass the user into that.
#thumb.rb
named_scope :for_user_id, lambda {|id| {:conditions => {:user_id => id}}}
#stem.rb
def thumbed_by_user(user)
self.thumbs.for_user_id(user.id).count > 0
end
# some controller
stem = some_method_that_gets_a_stem
is_already_thumbed = stem.thumbed_by_user(current_user)
Can you pass the current user to thumbed? Where are you calling it from?
BTW, you could try to simplify all of this using associations. I'm not 100% sure I understand what you're trying to do, but it sounds like you have the following...
class Stem
has_many :thumbs
end
class User
has_many :thumbs
end
class Thumb
belongs_to :user
belongs_to :stem
end
Then you can use find though associations to get at your thumbs without resorting to direct SQL. Check out this RailsCast: http://railscasts.com/episodes/3-find-through-association
What ended up working for me first was something like this:
I added these methods to my stem model:
def thumbed_by? usr_id
Thumb.count(:conditions => {:user_id => usr_id, :stem_id => self.id}) > 0
end
def owned_by? usr_id
self.id == usr_id
end
I also added this:
attr_accessor :thumbed, owned
Then in my show action where these were needed, I added the following lines:
#stem.thumbed = #stem.thumbed_by? current_user.id
#stem.owned = #stem.owned_by? current_user.id
This works exactly how I would like (the Flex app is already interpreting it as properly), but is there a way I could shorten this?
Suppose we have the standard Post & Comment models, with Post having accepts_nested_attributes_for :commments and :autosave => true set.
We can create a new post together with some new comments, e.g.:
#post = Post.new :subject => 'foo'
#post.comments.build :text => 'bar'
#post.comments.first # returns the new comment 'bar'
#post.comments.first.post # returns nil :(
#post.save # saves both post and comments simultaneously, in a transaction etc
#post.comments.first # returns the comment 'bar'
#post.comments.first.post # returns the post 'foo'
However, I need to be able to distinguish from within Comment (e.g. from its before_save or validation functions) between
this comment is not attached to a post (which is invalid)
this comment is attached to an unsaved post (which is valid)
Unfortunately, merely calling self.post from Comment doesn't work, because per above, it returns nil until after save happens. In a callback of course, I don't (and shouldn't) have access to #post, only to self of the comment in question.
So: how can I access the parent model of a new record's nested associations, from the perspective of that nested association model?
(FWIW, the actual sample I'm using this with allows people to create a naked "comment" and will then automatically create a "post" to contain it if there isn't one already. I've simplified this example so it's not specific to my code in irrelevant ways.)
I think it is strange that Rails does not let you do this. It also affects validations in the child model.
There's a ticket with much discussion and no resolution in the Rails bug tracker about this:
Nested attributes validations
circular
dependency
And a proposed resolution:
nested models: build should directly
assign the
parent
Basically, the deal is, the nested attributes code doesn't set the parent association in the child record.
There's some work-arounds mentioned in the second ticket I linked to.
I don't think you can do this. On the other hand, your validations shouldn't be failing, as the order of the transaction will create the post record before saving the comment.