Mongoid: Run callback from embedded document on parent - ruby

Rails 3.0.1
Mongoid (2.0.0.beta.20)
Class Post
embeds_many :comments
field :comments_count
end
Class Comment
embedded_in :commentable, :inverse_of => :comments
end
I want to select the 10 most commented posts. To do that I need comments_count field in Post. But since my Comment is polymorphic (Post.comments, Message.comments etc) I don't want create inc callbacks in Post. What I wan't to do is create callback in Comment which will update comment_count field in Post.
I don't know how I can perform inc operation in embedded document on Field from parrent document and execute this callback from parrent document

Here is how to increment the Post from the embedded polymorphic Comment:
Class Comment
after_create :update_post_comment_count
def update_post_comment_count
if self._parent.class == Post
Post.collection.update( {'_id' => self._parent._id},
{'$inc' => {'comment_count' => 1}} )
end
end
end
I am pretty sure that this callback will execute whenever a new Comment is created so I don't think you need to worry about executing it from the parent document. Let me know if it works.
See this SO answer and this Github issue for more info on callbacks in embedded documents.

Related

Rails Action Text for existing text field

I am upgrading one of my news app to Rails 6.0.0. While working around I got a problem using Rich Text. My app is pointing the rich text body field instead of the my existing table body field.
Is it possible to use the existing table text field for the rich text, so that I can edit the contents whenever I need it. Like for new posts I can use the action_text_rich_texts table but for the existing posts I want to use the existing table body field.
Assuming there's a content in your model and that's what you want to migrate, first, add to your model:
has_rich_text :content
Then create a migration
rails g migration MigratePostContentToActionText
Result:
class MigratePostContentToActionText < ActiveRecord::Migration[6.0]
include ActionView::Helpers::TextHelper
def change
rename_column :posts, :content, :content_old
Post.all.each do |post|
post.update_attribute(:content, simple_format(post.content_old))
end
remove_column :posts, :content_old
end
end
Refer to this Rails Issue comment.
ActionText's helper has_rich_text defines getter and setter methods for you.
You can redefine the body method again, providing ActionText with the value stored in the table using read_attribute:
class Post
has_rich_text :body
# Other stuff...
def body
rich_text_body || build_rich_text_body(body: read_attribute(:body))
end

Rails Newby: Strong parameters in Rails 5 not letting specified parameter Through - HABTM

I'm new to rails, and I'm currently trying to develop an API based app using Rails 5, on one of my controllers I have a function to filter the allow parameters like so
def provider_params
params.require(:provider).permit(:name, :phone, :email, :website, :address, :provider_id, :bio, :specialty_ids => [])
end
Then posting from Paw I noticed that the arguments that are not attributes of the table are no included in provider_params, the parameter I'm supposed to receive is an array, which is defined by a HABTM relation-ship.
This is how my models look like
specialty.rb
class Specialty < ApplicationRecord
has_and_belongs_to_many :providers
end
provider.rb
class Provider < ApplicationRecord
has_and_belongs_to_many :specialties
end
And this is how the join table was created via migration
class CreateProvidersSpecialties < ActiveRecord::Migration[5.0]
def change
create_table :providers_specialties, :id => false do |t|
t.integer :provider_id
t.integer :specialty_id
end
add_index :providers_specialties, :provider_id
add_index :providers_specialties, :specialty_id
end
end
The JSON I'm posting
{
"name": "the name",
"specialty_ids": [
1,
2
]
}
So as I mentioned, the array specialty_ids doesn't seem to be coming through, and even if it did, I suspect there's still something else I need to do in order for rails to insert the content of specialty_ids in the ProvidersSpecialties Table
So the problem was finally solved by removing the requir call from the method provider_params, since I wasn't wrapping the json-payload in a provider key. Apparently once you add the require(:key) call you would only be able to add parameters that belong to the Model, which is weird since an error should be raised when the key is not present, what was the case with my payload, lacking the provider key.

Retrieve an embedded document using Mongoid

I have a Mongoid document called Equipment which can embed multiple Question documents. Here are the document schemas:
class Equipment
include Mongoid::Document
include Mongoid::Timestamps
field :description
field :modelNumber
field :title
field :categoryId
field :subCategoryId
field :stateId
field :city
field :askingPrice
embeds_many :questions
end
class Question
include Mongoid::Document
field :content
attr_accessible :content
embedded_in :equipment, :inverse_of => :questions
embeds_one :answers
end
My issue is that I can retrieve a Question document based on the Question Id. However, my current query returns the parent Equipment document. I would have expected the query to return the embedded Question document. I can eventually get the embedded Question document but I have to loop through all the Question documents of the parent Equipment document.
Here is my current query:
#question = Equipment.where('questions._id' => Moped::BSON::ObjectId(params[:id])).first
Is there a way to directly obtain the embedded Question document?
Because you are using embedded documents, grabbing a single question may not make sense. It does, however, make sense in the context of the parent document. If you really just want a specific question, you can just use normal ARish syntax:
question = Question.find(params[:id])
I would strongly suggest you make sure you have an index on the question._id field. This will help ensure this query is a fast read operation. Otherwise, Mongo will need to run through all of the Equipment documents and check each of the embedded Question objects.
If the above doesn't work, you can try a nested relation:
Equipment.find(params[:equipment_id]).questions.find(params[:id])
With rails (3.2.15) and mongoid (3.1.5) I can only do it in this way:
#equipment = Equipment.where('questions._id' => Moped::BSON::ObjectId(params[:id])).first
#question = #equipment.questions.find(params[:id])

Querying and sorting embedded document in mongoid

I have three classes
class Post
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :user, :inverse_of => nil
embeds_many :comments, :as => :commentable
field :content, :type => String
end
class Commment
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :user, :inverse_of => nil
embedded_in :commentable, :polymoriphic => true
end
class User
has_many :posts, :dependent => :destroy
field :name, :type => String
end
Whenever the user creates a new comment, I want to compare the contents of it with the latest comment that the user has made. Here is my code that fetches the latest comment by the user:
comments_user=[]
Post.where("comments.user_id" => user.id).
only(:comments).
each {|p| comments_user += p.comments.where(:user_id => user.id).to_a}
latest_comment = comments_user.sort_by{|comment| comment[:updated_at]}.reverse.first
The above code gives me the result but the approach taken is not efficient as I have to traverse through all the posts that the user has commmented to find the latest comment. If any, can anyone provide me a more efficient solution to this problem?
Simply speaking, Isn't there any way I can get all the comments made by this user?
This should fetch the latest user`s comment:
Post.where("comments.user_id" => user.id).order_by(:'comments.updated_at'.desc).limit(1).only(:comments).first
This is standard problem with embedding. It greatly improves some queries ("load post with all its comments"), but makes others non-efficient/impractical ("find latest comment of a user").
I see two options here:
Keep embedding and duplicate data. That is, when user makes a comment, embed this comment to a post document and to the user document. This data duplication has its drawbacks, of course (what if you need to edit comments?);
Stop embedding and start referencing. This means that comment is now a top level entity. You can't quickly load a post with comments, because there are no joins. But other queries are faster now, and there's no data duplication.

Rails nested form on many-to-many: how to prevent duplicates?

I've setup a nested form in my rails 3.2.3 app, it's working fine, my models are:
class Recipe < ActiveRecord::Base
attr_accessible :title, :description, :excerpt, :date, :ingredient_lines_attributes
has_and_belongs_to_many :ingredient_lines
accepts_nested_attributes_for :ingredient_lines
end
and:
class IngredientLine < ActiveRecord::Base
attr_accessible :ingredient_id, :measurement_unit_id, :quantity
has_and_belongs_to_many :recipes
belongs_to :measurement_unit
belongs_to :ingredient
end
As above, a Recipe can have multiple IngredientLines and vice versa.
What I'm trying to avoid is record duplication on IngredienLine table.
For example imagine that for recipe_1 an IngredientLine with {"measurement_unit_id" => 1, "ingredient_id" => 1, "quantity" => 3.5} is associated, if for recipe_5 the IngredientLine child form is compiled by the user with the same values, I don't want a new record on IngredientLine table, but only a new association record in the join table ingredient_lines_recipes.
Note that currently I dont't have any IngredientLine controller as saving and updating IngredientLines is handled by nested form routines. Even my Recipe controller is plain and standard:
class RecipesController < ApplicationController
respond_to :html
def new
#recipe = Recipe.new
end
def create
#recipe = Recipe.new(params[:recipe])
flash[:notice] = 'Recipe saved.' if #recipe.save
respond_with(#recipe)
end
def destroy
#recipe = Recipe.find(params[:id])
#recipe.destroy
respond_with(:recipes)
end
def edit
respond_with(#recipe = Recipe.find(params[:id]))
end
def update
#recipe = Recipe.find(params[:id])
flash[:notice] = 'Recipe updated.' if #recipe.update_attributes(params[:recipe])
respond_with(#recipe)
end
end
My guess is that should be enough to override the standard create behavior for IngredientLine with find_or_create, but I don't know how to achieve it.
But there's another important point to take care, imagine the edit of a child form where some IngredientLines are present, if I add another IngredientLine, which is already stored in IngredientLine table, rails of course should not write anything on IngredientLine table, but should also distinguish between child records already associated to the parent, and the new child record for which needs to create the relation, writing a new record on the join table.
Thanks!
in Recipe model redefine method
def ingredient_lines_attributes=(attributes)
self.ingredient_lines << IngredientLine.where(attributes).first_or_initialize
end
Old question but I had the same problem. Forgot to add :id to white list with rails 4 strong_parameters.
For example:
widgets_controller.rb
def widget_params
params.require(:widget).permit(:name, :foos_attributes => [:id, :name, :_destroy],)
end
widget.rb
class Widget < ActiveRecord::Base
has_many :foos, dependent: :destroy
accepts_nested_attributes_for :foos, allow_destroy: true
end
foo.rb
class Foo < ActiveRecord::Base
belongs_to :widget
end
I have run into a similar situation and found inspiration in this answer. In short, I don't worry about the duplication of nested models until save time.
Translated to your example, I added autosave_associated_records_for_ingredient_lines to Recipe. It iterates through ingredient_lines and performs a find_or_create as your intuition said. If ingredient_lines are complex, Yuri's first_or_initialize approach may be cleaner.
I believe this has the behavior you're looking for: nested models are never duplicated, but editing one causes a new record rather than updating a shared one. There is the strong possibility of orphaned ingredient_lines but if that's a serious concern you could choose to update if that model has only one recipe with an id that matches the current one.

Resources