Mongoid model with hardcoded data - ruby

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

Related

Drying up Models for Solr Sunspot searchable with Concerns

I want to dry up my models; but Sunspot only allows one(1) "searchable" block in a model. making 2 just ignores the second one. Failed Example below:
digital.rb
class Digital < ActiveRecord::Base
include BaseConcerns
belongs_to :digitable, polymorphic: true
# IGNORES THIS
searchable do
string :url
text :url
string :remark
text :remark
end
end
base_concerns.rb
module BaseConcerns extend ActiveSupport::Concern
included do
before_create{
self.id = UUIDTools::UUID.timestamp_create().to_s.downcase if id.blank?
}
self.primary_key = 'id'
# DOES THIS
searchable do
text :id
time :created_at
time :updated_at
end
end
end
I want to DRY up my models using some concern and want some similarly named columns to be searchable; while others I can just specify; how can I do this?

How to define method names and object references before actually having to use them (Ruby)

Say I am keeping track of email correspondances. An enquiry (from a customer) or a reply (from a supporter) is embedded in the order the two parties are corresponding about. They share the exact same logic when put into the database.
My problem is that even though I use the same logic, the object classes are different, the model fields I need to call are different, and the method names are different as well.
How do I put methods and objects references in before I actually have to use them? Does a "string_to_method" method exists or something like that?
Sample code with commentaries:
class Email
include Mongoid::Document
field :from, type: String
field :to, type: String
field :subject, type: String
belongs_to :order, :inverse_of => :emails
def start
email = Email.create!(:from => "sender#example.com", :to => "recipient#example.com", :subject => "Hello")
from_or_to = from # This represents the database field from where I later on will fetch the customers email address. It is either from or to.
enquiries_or_replies = enquiries # This represents a method that should later be called. It is either enquiries or replies.
self.test_if_enquiry_or_reply(from_or_to, enquiries_or_replies)
end
def test_if_enquiry_or_reply(from_or_to, enquiries_or_replies)
order = Order.add_enquiry_or_reply(self, from_or_to, enquiries_or_replies)
self.order = order
self.save
end
end
class Order
include Mongoid::Document
field :email_address, type: String
has_many :emails, :inverse_of => :order
embeds_many :enquiries, :inverse_of => :order
embeds_many :replies, :inverse_of => :order
def self.add_enquiry_or_reply(email, from_or_to, enquiries_or_replies)
order = Order.where(:email_address => email.from_or_to).first # from_or_to could either be from or to.
order.enquiries_or_replies.create!(subject: email.subject) # enquiries_or_replies could either be enquiries or replies.
order
end
end
Judging by the question and the code sample, it sounds like you are mixing concerns too much. My first suggestion would be to re-evaluate your method names and object structure. Ambiguous names like test_if_thing1_or_thing2 and from_or_to (it should just be one thing) will make it very hard for others, and your future self, to understand the code laster.
However, without diverging into a debate on separation of concerns, you can change the methods you call by using public_send (or the private aware send). So you can do
order.public_send(:replies).create!
order.public_send(:enquiries).create!
string to method does exist, it's called eval
so, you could do
method_name = "name"
eval(method_name) #calls the name method

How to prevent multiple DB roundtrips for parents of has_many relations

I'm likely missing something here, but it seems like this is a performance oversight. I first noticed this when looking at the query logs of Errbit and noticing hundreds of queries for the same objects.
It seems that all the children of a has_many relation don't have a reference back to their parent object after being loaded through the relation. i.e. accessing parent.children.map &:parent will get the parent from the DB once for each child instead of being setup with the in-memory copy of parent
Example:
Using a very simple belongs_to / has_many setup:
class Person
include Mongoid::Document
field :name
has_many :posts, :inverse_of => :person
end
class Post
include Mongoid::Document
field :text
belongs_to :person, :inverse_of => :posts
end
Then, in the Rails console, a simple demonstration:
Loading development environment (Rails 3.2.12)
[1] pry(main)> tom = Person.create(:name => 'Tom')
[2] pry(main)> tom.posts.create(:text => 'stuff')
[3] pry(main)> tom.posts.create(:text => 'other stuff')
[4] pry(main)> Person.first.posts.map {|post| post.person.object_id}
=> [50687740, 50719060]
Note that last line, each person reference points to a different ruby object. I'm using ruby's object_id attribute to highlight the fact that these are literally different objects and this requires two round-trips to the database.
Why isn't the parent relation just a reference to the parent object after loading through the has_many relation?
Turns out that this is a known deficiency and adding this feature is planned for version 4.0.
In the mean time, you can drastically reduce duplicated queries and improve performance by enabling the Mongoid Identity Map

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.

What is the efficient way of counting 2 level nested model (using mongoid or MongoDB in general)?

I have following data models. In which a Project embeds many ComponentDescriptor, and a ComponentDescriptor embeds many Statistic.
class Project
include Mongoid::Document
embeds_many :component_descriptors
field :status, :type => Integer
backgrounded :publish
end
class ComponentDescriptor
include Mongoid::Document
include Mongoid::Acts::Tree
embeds_many :statistics
embedded_in :project, :inverse_of => :component_descriptors
end
class Statistic
include Mongoid::Document
field :statistics_type, :type => String
field :data, :type => String
field :playhead_time, :type => String
field :remote_ip, :type => String
field :user_agent, :type => String
embedded_in :component_descriptor, :inverse_of => :statistics
end
The question is what is the best way to count the total number of Statistic object in a Project.
One way I can think of is looping through each ComponentDescriptor and count the number of Statistics objects and then sum them up. But I think this is not efficient way.
Thank in advance.
If you can store the count field at the Project level that would be the most optimal and fastest way for you to obtain it.

Resources