Querying and sorting embedded document in mongoid - ruby

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.

Related

Ruby: undefined method `delete', deleting parent and child data at the same time

I'm a newbie programmer, basically I'm trying to delete a record from a database table, however this record would be used on a child table with a composed primary key taking both from different tables.
This is the class of the table I need to delete the child data from.
class Detalle_Mapa
include DataMapper::Resource
storage_names[:default] = 'detalle_ma'
property :clave_mapa, String, :length => 2, :required => true, :key => true
property :clave_asig, String, :length => 10, :required => true, :key => true
property :clave_cuatri, String, :length => 2, :required => true
belongs_to :mapa_curricular, 'Mapa_Curricular', :child_key => [:clave_mapa], :key => true
belongs_to :cuatrimestre, 'Cuatrimestre', :child_key => [:clave_cuatri], :key => true
belongs_to :asignatura, 'Asignatura', :child_key => [:clave_asig]
end
So now when I try to delete data from the parent table, it won't delete it since the PK is being used on the child table for other data. If this was normal SQL sintax it wouldn't be a big deal but I'm having a hard time finding a way around it on Ruby.
This is the basic method that works when deleting data that's not being used as a FK.
delete '/deleteMapCurricular/:clave_mapa' do
#mapa = Mapa_Curricular.get(params[:clave_mapa])
if #mapa
#mapa.destroy
redirect '/catMapCurricular'
end
end
And this is one of the ways I've tried to delete the child data, which is clearly not right...
Detalle_Mapa.where(:clave_mapa => [params[:clave_mapa]]).delete_all
Is there an easy way to just delete a bunch of data from a database that I'm not aware of? Or what I'm I not getting about this ActiveRecords on Ruby?
I'm sorry if the question is ambiguous or if I'm not explaining myself clearly, I usually find everything on forums and there is no need to ask myself. Any help will be greatly appreciated :)
Sorry for not giving out the exact answer. But I can tell a concept in this answer
class User < ActiveRecord::Base
has_many :fruits, dependent: :destroy
end
class Fruit < ActiveRecord::Base
belongs_to :user
end
So in the above example, if you delete a user, the associated fruits for that user will also be deleted automatically.

Mongoid model with hardcoded data

I have a mongoid model
class MyMongoidModel
include Mongoid::Document
include Mongoid::Timestamps
field :name, :type => String
field :data_id, :type => Integer
has_and_belongs_to_many :the_other_model, :class_name => 'class_name_model'
has_many :model2
def self.all
[
#.... the hardcoded data that will never be changed
]
end
end
it's used by the other model and it uses them as well. However, it contains the data that won't be changed for a very long time, let's say, at all. Thus, I don't want to retrieve it from db, I want it to be hardcoded and, at the same time, I want it acts like a normal mongoid model. Using caching is not what I'm looking for.
I hope you understand what I mean.
How do accomplish it?
There's a great gem called active_hash that provides this functionality for ActiveRecord: defining a fixed set of data as models you can reference/relate to normal models, but have it defined in code and loaded in memory (not stored/retrieved from DB).
https://github.com/zilkey/active_hash
Interestingly, since Mongoid and ActiveRecord both share common ActiveModel basis, you may be able to use active_hash with a Mongoid document now.
For example:
class Country < ActiveHash::Base
self.data = [
{:id => 1, :name => "US"},
{:id => 2, :name => "Canada"}
]
end
class Order
include Mongoid::Document
include Mongoid::Timestamps
has_one :country
end

MongoMapper: how do I create a model like this

Suppose we have two models, Task and User.
So a user can have many tasks and tasks should be able to have many users too. But, a task should also have a unique creator who is also a user.
Exemple:
A task in this context is like this:
Task ID, Task Creator, Users who should do the task
User_1 creates a task and he is then the creator.
User_1 specifies User_2 and User_3 as users who should do the task. So these two last users are not creators of task.
How do I create this models so that if I have a task object, I can find it's creator and users who should complete it. And how do I do, if I have a user, to find all tasks he created and all tasks he should complete.
You'll need a many-to-many relationship between the Tasks and Users, and you need an additional one-to-many relationship between Users and Tasks, pointing to the creator (User).
Something along these lines: (I usually use Mongoid, so double-check the syntax for the relations in the MongoMapper API - link below.. you might to manually specify :foreign_key and :class)
The idea is that you have two relationships between the models, one which models the many-to-many relationship
with which you get either to the assigned_users or assigned_tasks, and a one-to-many relationship with which you get to either the creator of a task, or the created_tasks for a given user. If you chose these names for the relationships, it will be clear which is which.
class Task
include MongoMapper::Document
key :title, String , :required => true
key :user_ids , Array
has_many :users, :in => user_ids # , :as => :assigned_users
key :creator_id , ObjectId
belongs_to: user, :as => :creator
end
class User
include MongoMapper::Document
key: name, String, :required => true
has_many :tasks # , :as => :assigned_tasks
has_many :tasks, :as => :created_tasks
end
See:
http://mongomapper.com/documentation/plugins/associations.html
The answer suggested by Tilo is correct about how to model the data, but the example code is incorrect and will not work. The :as option is for polymorphic associations, you want to use the :foreign_key option. Also, you can't have two associations named the same. See below for revised code.
class Task
include MongoMapper::Document
key :title, String , :required => true
key :assigned_user_ids, Array
has_many :assigned_users, :in => :assigned_user_ids
key :creator_id , ObjectId
belongs_to :creator, :class => User
# userstamps! also accomplishes the above
end
class User
include MongoMapper::Document
key: name, String, :required => true
has_many :created_tasks, :foreign_key => :creator_id, :class => Task
# inverse of many :in is still in the works
# see https://github.com/jnunemaker/mongomapper/pull/259
# this is a decent workaround for now
def assigned_tasks
Task.where(:assigned_user_ids => self.id)
end
end
See also:
MongoMapper userstamps! documentation

ActiveRecord has_one and has_many relation with the same :foreign_key

I have two models, Story and Chapter. A story has_many chapters, one of those is a chapter which serves as its first chapter. I used to have a foreign key start_id in the stories table to indicate which chapter is the first. Hovewer, the the database schema had to be changed a little, now every chapter has a code. If the code is '1a', then that is first chapter of the story which owns the chapter.
The following seems to work, including #create_start:
has_many :chapters, :dependent => :destroy, :inverse_of => :story
has_one :start, :class_name => 'Chapter', :foreign_key => 'story_id', :conditions => {:code => '1a'}
This way, the foreign key start_id of the stories table is unneeded, and #start still remains an association, with all the benefits (I need #start as an association, because I use CanCan with associations for authorization).
Does my approach has any drawbacks that I currently fail to realize, or I am relatively safe with it?
Relying on the code == '1a' to find the first chapter is a bit wonky. I'd probably add a flag to the chapters table like 'first_chapter' or something that was true or false indicating whether it was the first chapter. This way the first_chapter-ness of a chapter would survive a change to the code field, but this is a bit nitpicky.
Also, to avoid repeating yourself, you could change the declaration of has_one :start to something like:
has_one :start, :through => :chapters, :conditions => { :code => '1a' }

activerecord validation - validates_associated

I'm unclear on what this method actually does or when to use it.
Lets say I have these models:
Person < ...
# id, name
has_many :phone_numbers
end
PhoneNumber < ...
# id, number
belongs_to :person
validates_length_of :number, :in => 9..12
end
When I create phone numbers for a person like this:
#person = Person.find(1)
#person.phone_numbers.build(:number => "123456")
#person.phone_numbers.build(:number => "12346789012")
#person.save
The save fails because the first number wasn't valid. This is a good thing, to me. But what I don't understand is if its already validating the associated records what is the function validates_associated?
You can do has_many :phone_numbers, validate: false and the validation you're seeing wouldn't happen.
Why use validates_associated then? You might want to do validates_associated :phone_numbers, on: :create and skip validation on update (e.g. if there was already bad data in your db and you didn't want to hassle existing users about it).
There are other scenarios. has_one according to docs is validate: false by default. So you need validates_associated to change that.

Resources