Is there a way to access the parent of a polymorphic model in Mongoid 3?
I have this relationship
class Project
...
field "comments_count", :type => Integer, :default => 0
has_many :comments, :as => :commentable
...
end
class Comment
...
field "status"
belongs_to :commentable, :polymorphic => true
before_validation :init_status, :on => :create
after_create :increase_count
def inactivate
self.status = "inactive"
decrease_count
end
private
def init_status
self.status = 'active'
end
def increase_count()
#commentable.inc(:comments_count, 1)
end
def decrease_count()
#commentable.inc(:comments_count, -1)
end
...
end
I'd like to be able to update the comments_count in the parent relationship when the comment is inactivated since doing a count() on the child is very expensive (and I'd need to do that a lot in the app). I have the increase_count working, but I can't access #commentable in decrease_count (#commentable = nil). Any ideas?
The # in #commentable is unnecessary because its not an instance variable of your model. So:
def increase_count()
commentable.inc(:comments_count, 1)
end
def decrease_count()
commentable.inc(:comments_count, -1)
end
should do the trick.
Related
AMS version 0.8.3,
I created a base_serializer.rb like this and extended the same.
class BaseSerializer < ActiveModel::Serializer
def include_associations!
if #options[:embed]
embed = #options[:embed].split(',').map{|item| item.strip.to_sym}
embed.each do |assoc|
include! assoc if _associations.keys.include?(assoc)
end
end
end
end
class EventSerializer < BaseSerializer
attributes :id, :name
has_many :organizers, serializer: OrganizerSerializer
has_many :participants, serializer: ParticipantSerializer
end
class OrganizerSerializer < BaseSerializer
attributes :id, :name
has_many :related, serializer: RelatedSerializer
end
class ParticipantSerializer < BaseSerializer
attributes :id, :name
has_many :related, serializer: RelatedSerializer
end
class RelatedSerializer < BaseSerializer
attributes :id, :name
has_many :something, serializer: SomethingSerializer
end
and the index method in EventsController is written as
# GET /events?embed=organizers,participants
def index
#events = Event.all
render json: #events, embed: params[:embed]
end
With this I can get the :id and :name of events, organizers and participants. But, I want the attributes of related association as well. I don't need details of something serializer. I want to go till this level for each association. How can I achieve that?
I ended up doing this to achieve the same.
class BaseSerializer < ActiveModel::Serializer
def include_associations!
#options[:embed_level] ||= 2
return unless #options.key?(:embed) && #options[:embed_level] != 0
embed = #options[:embed].split(',').map{|item| item.strip.to_sym}
embed.each do |assoc|
next unless _associations.key?(assoc)
assoc_serializer = serializer_for(assoc)
embed = #options[:embed]
embed_level = #options[:embed_level]
#options[:embed_level] = #options[:embed_level] - 1
#options[:embed] = assoc_serializer._associations.keys.join(",")
include! assoc
#options[:embed_level] = embed_level
end
end
def serializer_for(assoc)
serializer = _associations[assoc].options[:serializer]
return serializer if serializer
assoc.to_s.classify.concat("Serializer").constantize
end
end
Ref: Github Issue Link
Special Thanks to Yohan Robert!!!
I want to filter the Sunspot search results with with(:is_available, true).
This is working with the User model, but I can't make it work with the Itinerary model.
app/controllers/search_controller.rb:
class SearchController < ApplicationController
before_filter :fulltext_actions
private
def fulltext_actions
#itineraries = do_fulltext_search(Itinerary)
#users = do_fulltext_search(User)
#itineraries_size = #itineraries.size
#users_size = #users.size
end
def do_fulltext_search(model)
Sunspot.search(model) do
with(:is_available, true)
fulltext params[:search]
end.results
end
end
app/models/user.rb:
class User < ActiveRecord::Base
has_many :itineraries, :dependent => :destroy
searchable do
text :first_name, :boost => 3
text :last_name, :boost => 3
text :status
boolean :is_available, :using => :available?
end
def available?
!self.suspended
end
end
app/models/itinerary.rb:
class Itinerary < ActiveRecord::Base
belongs_to :user
searchable do
text :title, :boost => 3
text :budget
text :description
boolean :is_available, :using => :available?
end
def available?
!self.user.suspended
end
end
Any ideas?
Thanks!
Well, my real problem was the indexation.
When I update the User model, I set a flag (like user_instance.update_index_flag = true) in my controller.
In the User model:
attr_accessor :update_index_flag
after_save :reindex_sunspot
def reindex_sunspot
if self.update_index_flag
Sunspot.index(self.itineraries.to_a)
end
end
That's it...
I'm trying to find an elegant (standard) way to pass the parent of a polymorphic model on to the view. For example:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
The following way (find_imageable) works, but it seems "hackish".
#PictureController (updated to include full listing)
class PictureController < ApplicationController
#/employees/:id/picture/new
#/products/:id/picture/new
def new
#picture = imageable.pictures.new
respond_with [imageable, #picture]
end
private
def imageable
#imageable ||= find_imageable
end
def find_imageable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
Is there a better way?
EDIT
I'm doing a new action. The path takes the form of parent_model/:id/picture/new and params include the parent id (employee_id or product_id).
I'm not sure exactly what you're trying to do but if you're trying to find the object that 'owns' the picture you should be able to use the imageable_type field to get the class name. You don't even need a helper method for this, just
def show
#picture = Picture.find(params[:id])
#parent = #picture.imagable
#=> so on and so forth
end
Update
For an index action you could do
def index
#pictures = Picture.includes(:imagable).all
end
That will instantiate all 'imagables' for you.
Update II: The Wrath of Poly
For your new method you could just pass the id to your constructor, but if you want to instantiate the parent you could get it from the url like
def parent
#parent ||= %w(employee product).find {|p| request.path.split('/').include? p }
end
def parent_class
parent.classify.constantize
end
def imageable
#imageable ||= parent_class.find(params["#{parent}_id"])
end
You could of course define a constant in your controller that contained the possible parents and use that instead of listing them in the method explicitly. Using the request path object feels a little more 'Rails-y' to me.
I just ran into this same problem.
The way I 'sort of' solved it is defining a find_parent method in each model with polymorphic associations.
class Polymorphic1 < ActiveRecord::Base
belongs_to :parent1, :polymorphic => true
def find_parent
self.parent1
end
end
class Polymorphic2 < ActiveRecord::Base
belongs_to :parent2, :polymorphic => true
def find_parent
self.parent2
end
end
Unfortunately, I can not think of a better way. Hope this helps a bit for you.
This is the way I did it for multiple nested resources, where the last param is the polymorphic model we are dealing with: (only slightly different from your own)
def find_noteable
#possibilities = []
params.each do |name, value|
if name =~ /(.+)_id$/
#possibilities.push $1.classify.constantize.find(value)
end
end
return #possibilities.last
end
Then in the view, something like this:
<% # Don't think this was needed: #possibilities << picture %>
<%= link_to polymorphic_path(#possibilities.map {|p| p}) do %>
The reason for returning the last of that array is to allow finding the child/poly records in question i.e. #employee.pictures or #product.pictures
Errors are added to error object of record but associations are still saved.
class Parent < ActiveRecord::Base
validate :valid_child?
#validation methods
protected
def valid_child?
#child_names = Hash.new
self.children.each do |curr_child|
if #child_names[curr_child.name].nil?
#child_names[curr_child.name] = curr_child.name
else
errors.add(:base, "child name should be unique for children associated to the parent")
end
end
end
#associations
has_and_belongs_to_many :children, :join_table => 'map__parents__children'
end
#query on rails console
#parent = Parent.find(1)
#parent.children_ids = [1, 2]
#parent.save
The problem is that, for an existing record, #parent.children_ids = [1, 2] will take effect a change in the database before the call to #parent.save.
Try using validates_associated to validate the children rather than rolling your own validation.
To make sure that the children's names are unique within the context of the parent, use validates_uniqueness_of with the :scope option to scope the uniqueness to the parent id. Something like:
class Child < ActiveRecord::Base
belongs_to :parent
validates_uniqueness_of :name, :scope => :parent
end
Hi I am new to rails and I would like to know what is the best way who save dependent objects in an HBTM relation.
Specifically, I have two classes Post and Tag
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
I have a migration to create the joining table
class AddPostsTagsJoinTable < ActiveRecord::Migration
def self.up
create_table :posts_tags, :id => false do |t|
t.integer :post_id
t.integer :tag_id
end
end
def self.down
drop_table :postss_tags
end
end
All is good up to here
So I have a PostsController from which I handle the creation, updates and deletes for the posts, and I want to encapsulate the Tags so that the creation is via the PostsController... like so:
class PostsController < ApplicationController
#... code removed for brevity
def create
#post = current_user.posts.build(params[:post])
if #post.save
tag_names = params[:post][:tags].strip.split(' ')
tag_names.each do |t|
#see if the tag already exists
tag = Tag.find_by_name(t);
if tag.nil?
#post.tags.create!(:name => t)
else
#post.tags << tag #just create the association
end
end
flash[:success] = "Post created."
redirect_to(user_posts_path(current_user.username))
else
#user = current_user
render 'new'
end
end
end
I am not sure how I should handle the creation of my Tag(s) because if I just call
#post.tags.create!(:name => t)
this will create duplicate records in the Tags table (even when :uniq => true is specified in the model).
So to avoid the duplication I see if a tag is already present and then add it like this
tag = Tag.find_by_name(t);
if tag.nil?
#post.tags.create!(:name => t)
else
#post.tags << tag #just create the association
end
Is this the way it's supposed to be done?
This seems expensive (especially 'cause it's in a loop) so I am wondering if there is another "cleaner" way to do this? (pls forget the DRY'ing up of the action and so on)
Is there a clean way to create my Tags without having to manually check for duplicates?
thank you in advance for your help!
You can save tags attribute of post if automatically by adding accepts_nested_attributes_for to Post model
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
accepts_nested_attributes_for :tags
end
The next step is to output tags fields inside post form.