How to declare a scope based on embedded model attributes with Mongoid - ruby-on-rails

I have a User model which embeds a Profile:
# app/models/user.rb
class User
embeds_one :profile
end
# app/models/profile.rb
class Profile
embedded_in :user, inverse_of: :profile
field :age, type: integer
end
Now I want to declare a scope in User which can list out all users whose profile.age is > 18.

You can query attributes of embedded documents via:
User.where(:'profile.age'.gt => 18)
or as a scope:
class User
embeds_one :profile
scope :adults, -> { where(:'profile.age'.gt => 18) }
end

When you use embeds you can access only associated B objects of A. So, your need i.e all B where age>x doesn't work. So, go for has_one and belongs_to
A.rb
class A
has_one :b
scope :adults, -> { Bar.adults }
end
B.rb
class B
field :age ,type:integer
belongs_to :a
scope :adults, -> { where(:age.gt=> 18)}
end

This should save your day -))
Class B
field :age, type:integer
scope :adults, -> { where(:age.gt => 18) }
end
class A
embed_one :b
def embed_adults
b.adults
end
end
https://mongoid.github.io/old/en/mongoid/docs/querying.html#scoping

Related

How to remove N+1 queries

I have a rails API that currently has quite a few N+1 queries that I'd like to reduce.
As you can see it's going through quite a few loops before returning the data.
The relationships are as follows:
Company Model
class Company < ApplicationRecord
has_many :jobs, dependent: :destroy
has_many :contacts, dependent: :destroy
has_many :listings
end
Job Model
class Job < ApplicationRecord
belongs_to :company
has_many :listings
has_and_belongs_to_many :technologies
has_and_belongs_to_many :tools
scope :category, -> ( category ) { where category: category }
end
Listing Modal
class Listing < ApplicationRecord
belongs_to :job, dependent: :destroy
belongs_to :company, dependent: :destroy
scope :is_active, -> ( active ) { where is_active: active }
end
Job Serializer
class SimpleJobSerializer < ActiveModel::Serializer
attributes :id,
:title,
:company_name,
attribute :technology_list, if: :technologies_exist
attribute :tool_list, if: :tools_exist
def technology_list
custom_technologies = []
object.technologies.each do |technology|
custom_technology = { label: technology.label, icon: technology.icon }
custom_technologies.push(custom_technology)
end
return custom_technologies
end
def tool_list
custom_tools = []
object.tools.each do |tool|
custom_tool = { label: tool.label, icon: tool.icon }
custom_tools.push(custom_tool)
end
return custom_tools
end
def tools_exist
return object.tools.any?
end
def technologies_exist
return object.technologies.any?
end
def company_name
object.company.name
end
end
Current query in controller
Job.eager_load(:listings).order("listings.live_date DESC").where(category: "developer", listings: { is_active: true }).first(90)
I've tried to use eager_load to join the listings to the Jobs to make the request more efficient but i'm unsure how to handle this when some of the n+1 queries are coming from inside the serializer as it tries to look at tools and technologies.
Any help would be much appreciated!
You might was well eager load tools and technologies since you know that the serializer is going to use them:
Job.eager_load(:listings, :tools, :technologies)
.order("listings.live_date DESC")
.where(category: "developer", listings: { is_active: true })
.first(90)
After that you really need to refactor that serializer. #each should only be used when you are only interested in the side effects of the iteration and not the return value. Use #map, #each_with_object, #inject etc. These calls can be optimized. return is implicit in ruby so you only explicitly return if you are bailing early.
class SimpleJobSerializer < ActiveModel::Serializer
# ...
def tool_list
object.tools.map { |t| { label: tool.label, icon: tool.icon } }
end
# ...
end
Try nested preload:
Job.preload(:technologies, :tools, company: :listings).order(...).where(...)

Scopes and associations not working

I'm trying to return records where the association is either present or not:
I tried these scopes:
class Booking < ActiveRecord::Base
has_one :availability
scope :with_availability, -> {where{availability.not_eq nil}}
scope :without_availability, -> {where{availability.eq nil}}
end
Try this:
class Booking < ActiveRecord::Base
has_one :availability
scope :with_availability, -> { joins{availability} }
scope :without_availability, -> { joins{availability.outer}.where{availability.id.eq nil} }
end
Use instance methods instead
def with_availability
availability.present?
end
def without_availability
availability.blank?
end
I know there is better way but this should work as well:
class Booking < ActiveRecord::Base
has_one :availability
scope :with_availability, -> {where(id: Availability.pluck(:booking_id))}
scope :without_availability, -> {where.not(id: Availability.pluck(:booking_id))}
end
Also I tried to reproduce the solution by the link below but I didn't manage to do this (but it could be helpful):
Rails - has_one relationship : scopes for associated and non-associated objects

Validation to prevent parent referencing self as children

I'm looking for a solution to prevent "parents" to add his self as "children".
My Model looks like this:
class Category < ActiveRecord::Base
belongs_to :parent, :class_name => 'Category'
has_many :children, :class_name => 'Category', :foreign_key => 'parent_id'
end
Now I look for a solution to prevent things like this
parent = Category.create(name: "Parent")
Category.new(name: "Children", parent_id: parent.id).valid? # should be => false
You can add a custom validation for that.
Something like
class ParentValidator < ActiveModel::Validator
def validate(record)
if record.parent_id == record.id
record.errors[:parent_id] << 'A record\'s parent cannot be the record itself'
end
end
end
class Category
include ActiveModel::Validations
validates_with ParentValidator
end
or even simpler (if it is a one off thing)
class Category < ActiveRecord::Base
validate :parent_not_self, on: :save
def parent_not_self
if parent_id == id
errors.add(:parent_id, 'A record\'s parent cannot be the record itself')
end
end
end
Both cases will generate a validation error when you try to assign the record itself as the parent's record

Mongoid: How to have distinct fields in User & Message n to m relation

I have got two models like following:
class User
include Mongoid::Document
has_many_and_belongs_to :messages
end
class Message
include Mongoid::Document
has_many_and_belongs_to :users
field :text
field :visited, type: Boolean, default: false
end
As far as i know when this template implements message fields are common among users. What i want to have here is visited field be distinct for every user to work with. Any idea how would that applicable? I have an alternative such as a middle model to save user & message id with desire field but that seems messy.
You need to build the has_and_belongs_to_many relation yourself. This way you can customize the class used to link a User and a Message:
class User
include Mongoid::Document
has_many :user_messages
end
class Message
include Mongoid::Document
has_many :user_messages
end
class UserMessage
include Mongoid::Document
belongs_to :user
belongs_to :message
field :visited, type: Boolean, default: false
scope :visited, -> { where(visited: true) }
scope :unvisited, -> { where(visited: false) }
end
Then you can do:
u = User.new
m = Message.new
u.user_messages.build(message: m, visited: true)
u.user_messages.build(message: m, visited: false)
u.user_messages.count
=> 2
u.user_messages.visited.count
=> 1
u.user_messages.unvisited.count
=> 1
More information about this kind of association from Rails Guide here.
Note that the UserMessage class name is quite annoying. You could instead use a different naming such as Conversation:
class User
has_many :messages
end
class Conversation
has_many :messages
end
class Message
belongs_to :user
belongs_to :conversation
field :visited, type: Boolean, default: false
end
Which is used nicely:
u = User.new
c = Conversation.new
u.messages.build(conversation: c)

Possible to specify what fields a belongs_to or has_many relationship object(s) return(s)?

A Contact has a User assigned to them:
class Contact < ActiveRecord::Base
...
belongs_to :user
...
end
The user model has a field I want to exclude any time a user object or objects are returned from db. One of the ways to make it work is to add a default scope:
class User < ActiveRecord::Base
...
has_many :contacts
...
default_scope select((column_names - ['encrypted_password']).map { |column_name| "`#{table_name}`.`#{column_name}`"})
end
So in console if I do:
User.first
The select statement and result set do not include 'encrypted_password'.
However, if I do:
c = Contact.includes(:user).first
c.user
they do. The default scope on the User model does not get applied in this case and the 'encrypted_password' field is shown.
So my question is why? And also, is there a clean way to specify what fields should be returned on related object(s)?
You should just be able to use the :select option on the belongs_to relationship. Something like this:
class Contact < ActiveRecord::Base
...
belongs_to :user, :select => [:id, :first_name, :last_name, :email]
...
end

Resources