Mongo / Mongoid will create, but not update a model - ruby-on-rails

I have an app which updates a post if it exists, otherwise it creates a new one. This post contains embedded documents:
class Post
embeds_one :tag, :as => :taggable, :class_name => 'TagSnippet'
end
class TagSnippet
include Mongoid::Document
field :name
embedded_in :taggable, polymorphic: true
end
The post is updated in a controller with the following code:
#post = Post.where(--some criteria which work--).first
if #post
#post.attributes = params
else
#post = Post.new(params)
end
#post.save!
This code runs and updates the non-embedded documents, but does not update the embedded docs. Oddly, when I debug in Rubymine, all the attributes of the #post change appropriately (including the embedded ones), but regardless the database does not get updated.
This indicates to me it's some mongo or mongoid problem, but rolling back mongo and mongoid gems produced no change.

I guess that your embedded document is defined like this:
field :subdoc, type: Hash
I bumped into this a couple of times already. Short explanation: Mongoid doesn't track changes inside subhashes.
doc.subdoc.field_a = 1 # won't be tracked
sd = doc.subdoc.dup
sd.field_a = 1
doc.subdoc = sd # should be tracked
So, if Mongoid doesn't detect assignments, it doesn't mark attribute dirty, and therefore doesn't include it in the update operation.
Check this theory by printing doc.subdoc_changed? before saving.

Related

Model's changes for Algolia not showing in the rails console in PRODUCTION

I have a model as bellow:
class Note < Record
include Shared::ContentBasedModel
algoliasearch disable_indexing: AppConfig.apis.algolia.disable_indexing do
attributes :id, :key
[:keywords, :tags, :description, :summary].each do |attr|
attribute [attr] do
self.meta[attr.to_s]
end
end
attribute :content do
Nokogiri.HTML(self.meta["html"]).text.split(' ').reject { |i| i.to_s.length < 5 }.map(&:strip).join ' '
end
attribute :photo do
unless self.meta["images"].blank?
self.meta["images"].first["thumb"]
end
end
attribute :slug do
to_param
end
attribute :url do
Rails.application.routes.url_helpers.note_path(self)
end
end
end
I am using AlgoliaSearch gem to index my models into the Algolia's API and when I was trying to index the model with some long content I get the following error:
Error: Algolia::AlgoliaProtocolError (400: Cannot POST to https://XXXX.algolia.net/1/indexes/Note/batch: {"message":"Record at the position 1 objectID=56 is too big size=20715 bytes. Contact us if you need an extended quota","position":1,"objectID":"56","status":400} (400))
After this, I removed EVERYTHING as the following, BUT I am still getting the exact same error!!
class Note < Record
include Shared::ContentBasedModel
algoliasearch disable_indexing: AppConfig.apis.algolia.disable_indexing do
attributes :id
end
end
It seems that Rails does not update the cached models.
Envirnoment: production
Rails version: v6
Question: Why is this happening & how can I clear cached model?
Note: I have tried everything, including removing the tmp/cache folder but it does not go away!
It looks like the object's size itself is bigger than some max allowed size.
objectID=56 is too big size=20715 bytes
Contact https://www.algolia.com/ (as the suggest)
Contact us if you need an extended quota
How do you check your code? Are you entering in rails console on your server? Might it be that you run an old release instead of the new one, in the case if you use Capistrano or Mina for deploy?

How to disregards the object while querying using ActiveRecord::Base methods whose associated object has been deleted from database in rails

I have Person<ActiveRecord::Base model
class Person < ActiveRecord::Base
has_many :sent_messages, :class_name => 'Message', :inverse_of => :sender
has_many :received_messages, :class_name => 'Message', :inverse_of => :receiver
#.....
end
and another modeActiveRecord::Base model
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'Person'
belongs_to :receiver, :class_name => 'Person'
# ......
end
; and ActiveRecord::Migration class supporting these models. Now my problem is that I need to output the messages received by current user with the Person name of sender by querying database, but while querying it seems that some of the person(user) who sent the messages has been deleted from database(but messages once exchanged b/w two persons is saved permanently in database and no body have the permission to delete it, So each message will be there with all the details like receiver_id and sender_id .). So when I query like this.
#messages = Message.where(:receiver_id => current_user.id)
.includes(:sender).order("updated_at DESC")------------(1)
it works fine if person who has sent the message to currently logged in user has it's delete_at attribute null(meaning not deleted from database and can be used without any error in views for sender name) but for those which has been deleted it gives this error in browser console
in application controller asset is undefined method or variable ----------(2)
where a method in application controller to handle no record found exception
def respond_to_not_found(*types)
flash[:warning] = t(:msg_asset_not_available, asset)
respond_to do |format|
format.html { redirect_to(redirection_url) }
# ...
end
end
please tell What is the t and asset is in answer ???
So as a workaround of problem (1) -------- (3).
I queried this and
def show_list
#messages = Array.new
#person = Array.new
messages_temp = Message.where(:receiver_id => current_user.id).
includes(:sender).order("updated_at DESC")
i = 0
messages_temp.each do |msg|
if(!(msg.sender.nil?))
#messages[i] = msg
#person[i] = msg.sender
i = i+1
end
end
end
My questions:-
Q.1) I haven't much worked with database so this is valid question for me(as the project I am working on explicitly destroys the requested user):- Why the user info has not been deleted from database but rather it's deleted_at attribute has become not null whereas for others which are intact have same attribute null. And when I find(query by method find) an object of Person from database then while querying it's SQL conversion query for deleted_at attribute should be null. So why is happening am I missing something in my project code or it is general behaviour. If it general then how to completely expunge the data.
Even if such behaviour is general or not how to recover it, without manually changing each deleted Person deleted_at attribute to null. And how to access some of it's attribute without fully restoring the Person(or if such thing is possible).
Q.2) What might be other reasons for getting error #(2) even after workaround. As I tested the workaround and it worked fine for some of deleted sender's. But even after at some places I am getting error #(2) so in general what does this error stand for and what might be it's other cause then the one I mentioned???
Q.3)Is there better solution then the workaround given in eq #(3) because you see in my workaround msg.sender.nil? is true for the deleted object so I think there might be some. I tried the net but with no success. So, how to filter out those messages whose associated object is deleted how to query them all at once then doing it one by one as in #(2).
As workaround is giving me hard time to paginate the output because all the available pagination gem (for example 'will_paginate', 'kaminari', 'pagination') works on ActiveRelation but in my workaround I have Array object i.e #messageson which I can not use these methods. So it would be great help if one can answer a way to paginate my workaround i.e object #messages which is an array or a way to filter out those messages whose sender is deleted from database then I can paginate in this way :-
#messages = Message.<ActiveRecord::Base method to get the desired output>.paginate[params]
PS:- I know the question is tedious but I believe it is essential for this question I am seeking answer to. Any help will be appreciated even the partial answer to the question.
Thanks a lot!!!
Ok...
Well it took me couple of days but with some help from my vicinity I did figure out answer to most of the confusion and ambiguity:
Ans:-
1:) There is a way to soft delete the data meaning hidden from normal query... Most of ambiguities of question one can be answered from this [link][1]. This gem(acts_as_paranoid) helps soft deleting the data. yes, attributes of soft deleted object can be accessed without restoring the date; and data can be completely expunged as well(everything in the link). The changes I needed to make in my code base to avail these benefits are these:-
In Person<ActiveRecord::Base model
class Person < ActiveRecord::Base
# has_many :sent_messages, :class_name => 'Message', :inverse_of => :sender
# has_many :received_messages, :class_name => 'Message', :inverse_of => :receiver
has_many :sent_messages, :class_name => 'Message',
:inverse_of => [:sender, :sender_including_deleted]
has_many :received_messages, :class_name => 'Message',
:inverse_of => [:receiver, :receiver_including_deleted]
#.....
end
and in the model ActiveRecord::Base
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'Person'
belongs_to :receiver, :class_name => 'Person'
# Add these two lines:-
belongs_to :sender_including_deleted, :class_name => 'Person',
:foreign_key => 'sender_id', :with_deleted => true
belongs_to :receiver_including_deleted, :class_name => 'Person',
:foreign_key => 'receiver_id', :with_deleted => true
# ......
end
So, adding these two lines worked for me but most importantly the gem helped me a lot.
2:) One of prominent reason for getting error in (2) is that one may be trying to directly access the attribute of object which may be deleted. Suppose a Person is deleted(soft or hard doesn't matter whose id was 15) so first_name = Person.find(15).first_name will give error where as person = Person.find(15) if(!person.nil?) return first_name will not as we are checking if returned person is nil or not. and if Person is deleted with gem acts_as_paranoid then a soft deleted(meaning deleted_at attribute is not null. The deleted_at(this attribute has to be added in migration to use) attribute is updated when object is deleted for clear picture see the embedded link here) object accessed first_name = Person.with_delete.find(15) like this won't generate an error
Note:- I don't what might be other reason. please answer this part if you can think of any.
3:) Although I do not have direct answer to question asked in 3 but since now I can access the message sender info even though they are deleted(in active relation form) so ordering and pagination works but I surely like to know how to paginate if object in not active relation.
So, this one I best answered like this:-
def show_list
# #messages = Array.new
# #person = Array.new
# messages_temp = Message.where(:receiver_id => current_user.id).
# includes(:sender).order("updated_at DESC")
# i = 0
# messages_temp.each do |msg|
# if(!(msg.sender.nil?))
# #messages[i] = msg
# #person[i] = msg.sender
# i = i+1
# end
# end
# REPLACE ABOVE CODE BY THIS. THe following code will
# include deleted messages as well.
#person = Array.new
#messages = Message.where(:receiver_id =>current_user.id).
includes(:sender_including_deleted).
order("created_at DESC").paginate(params).readonly
i = 0
#messages.each do |msg|
#person[i] = msg.sender_including_deleted(:with_deleted => true)
i = i + 1
end
end
I hope the people who stumbled upon this question get what they are looking. If they didn't get any part of my answer or question then they are welcome to comment here and I will be happy to answer to best of my abilities.

Rails counter_cache update too slow?

Using Rails 3. I have the following:
# shop.rb
class Shop < ActiveRecord::Base
belongs_to :country, :touch => true, :counter_cache => :total_shops
...
end
# shops_controller.rb
class ShopsController < ApplicationController
def create
...
#shop.save
#new_total_shops = #country.total_shops
end
end
Let's say initial #country.total_shops is 2, then when it's created, it should be incremented to 3, but when I try abort(#country.total_shops) right after the line #shop.save, it still shows 2. When I refresh the page, it shows 3. I guess it just gets updated a bit slow.
How can I get the latest value quickly?
Thanks.
My guess is that since you've (I assume) already loaded the Country instance before you save the new shop, you're seeing the total_shops value as it was when the country was loaded.
I.e. you've got the old value in memory, even though the underlying database values have changed.
Try this:
#shop.save
# reload the country instance from the database
# to get the updated counter cache value
#country.reload
#new_total_shops = #country.total_shops

Mongoid update_attributes creating new referenced document

I'm having trouble using update_attributes with referenced documents. I've reduced my problem to a simple example that AFAICT should work, but doesn't:
class Account
include Mongoid::Document
has_many :submissions, :autosave => true
end
class Submission
include Mongoid::Document
belongs_to :account
end
a = Account.new
a.save!
s = Submission.new
s.update_attributes({"account" => {"id" => a.id}})
s.save!
a.id == s.account.id # false
The call to update_attributes is creating a new blank Account object instead of referencing the existing one that I'm telling it to use. What's going on?
UPDATE
To be clear, I'm trying to process an HTML form in an update action which adds an Account to a Submission. I understand there are other ways to link these documents by writing specific code. But the normal rails way should allow me to use an HTML form to update the documents this way, right?
Change your HTML form to make "account_id" not "account[id]" then it starts working:
s.update_attributes({"account_id" => a.id})
s.save!
a.id == s.account.id # true
a == s.account # true
Very odd what it's doing. Maybe mongoid bug?
That's not the way to add s to a. What you want to do is this:
a = Account.new
a.submissions << Submission.new
a.save!

How can I improve this Rails code?

I'm writing a little browser game as a project to learn RoR with and I'm quite new to it.
This is a little method that's called regularly by a cronjob.
I'm guessing there should be some way of adding elements to the potions array and then doing a bulk save at the end, I'm also not liking hitting the db each time in the loop to get the number of items for the market again.
def self.restock_energy_potions
market = find_or_create_market
potions = EnergyPotion.find_all_by_user_id(market.id)
while (potions.size < 5)
potion = EnergyPotion.new(:user_id => market.id)
potion.save
potions = EnergyPotion.find_all_by_user_id(market.id)
end
end
I'm not sure I'm understanding your question. Are you looking for something like this?
def self.restock_energy_potions
market = find_or_create_market
potions = EnergyPotion.find_all_by_user_id(market.id)
(potions.size...5).each {EnergyPotion.new(:user_id => market.id).save }
end
end
Note the triple dots in the range; you don't want to create a potion if there are already 5.
Also, if your potions were linked (e.g. by has_many) you could create them through the market.potions property (I'm guessing here, about the relationship between users and markets--details depend on how your models are set up) and save them all at once. I don't think the data base savings would be significant though.
Assuming that your market/user has_many potions, you can do this:
def self.restock_energy_potions
market = find_or_create_market
(market.potions.size..5).each {market.potions.create(:user_id => market.id)}
end
a) use associations:
class Market < AR::Base
# * note that if you are not dealing with a legacy schema, you should
# rename user_id to market_id and remove the foreigh_key assignment.
# * dependent => :destroy is important or you'll have orphaned records
# in your database if you ever decide to delete some market
has_many :energy_potions, :foreign_key => :user_id, :dependent => :destroy
end
class EnergyPotion < AR::Base
belongs_to :market, :foreign_key => :user_id
end
b) no need to reload the association after adding each one. also move the functionality
into the model:
find_or_create_market.restock
class Market
def restock
# * note 4, not 5 here. it starts with 0
(market.energy_potions.size..4).each {market.energy_potions.create!}
end
end
c) also note create! and not create.
you should detect errors.
error handling depends on the application.
in your case since you run it from cron you can do few things
* send email with alert
* catch exceptions and log them, (exception_notifier plugin, or hoptoad hosted service)
* print to stderror and configuring cron to send errors to some email.
def self.restock_potions
market = find_or_create
market.restock
rescue ActiveRecord::RecordInvalid
...
rescue
...
end

Resources