Rails, nested has_many association, finding sum of field of last children of each - postgresql

I have nested models like (using PostgresSQL)
class User
has_many :pages
end
class Page
has_many :details
belongs_to :user
end
class Detail
belongs_to :page
#It has a column named "count"
end
What I need is say
User with id=1, it has many pages with ids= 1,2,3.
these pages with ids(1,2,3) has many details.
What I need is to get latest detail of each of these pages and get sum of field named "counts" from these latest details
What I 'm doing now is
page_ids = current_user.pages.pluck(:id)
last_followers = Detail.select("DISTINCT ON(page_id) *").where(page_id: page_ids).order("page_id, created_at DESC")
last_followers.each { |detail| last_sum += detail.counts }
But it doesn't look ok to find page_ids, than details and than looping through all of them.
Is there a direct way like single query, thanks

Not entirely sure of what you ask for but here's a try:
sum = current_user.pages.map { |page| page.details.last.counts }.sum

Related

Rails 4.2 + Postgres not equals != <> on INNER JOIN on polymorphic association

I am trying to return all Posts that belong to an Hnode and that have not been hidden by the user. I have a 'hides' table that allows a user to hide a post. Hide is a polymorphic association (hideable) because I also have comments that can be hidden. When a user hides a Post, a Hide record is created with the following data:
Hide:
id: #{some_id}
hideable_type: 'Post'
hideable_id: #{some_post_id}
user_id: #{some_user_id}
I want to fetch Posts that have not been hidden by the user. (See below for models and associations.)
In short, when I try to remove all posts which have been hidden by the current user, signified by the many-to-many association table "hides" I get results that should be hidden:
posts = Post.where(hnode_id: params[:hnode_id])
.joins("LEFT JOIN hides ON hides.hideable_type = 'Post' AND hides.hideable_id = posts.id AND hides.user_id != #{current_user.id}")
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."uid" = $1 LIMIT 1 [["uid", "email#fake-email.com"]]
Post Load (0.7ms) SELECT "posts".* FROM "posts" LEFT JOIN hides ON hides.hideable_type = 'Post' AND hides.hideable_id = posts.id AND hides.user_id != 1 WHERE "posts"."deleted_at" IS NULL AND "posts"."hnode_id" = $1 ORDER BY "posts"."id" DESC LIMIT 10 OFFSET 0 [["hnode_id", 12]]
My data is correct - meaning that I do have Hide records with the current user's id in the user_id column.
Here are my models with the appropriate associations:
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :hides, as: :hideable
...
end
#app/models/hnode.rb
class Hnode < ActiveRecord::Base
has_many :posts
...
end
# app/models/hide.rb
class Hide < ActiveRecord::Base
belongs_to :user
belongs_to :hnode
belongs_to :hideable, polymorphic: true
...
end
# app/models/user.rb
class User < ActiveRecord::Base
has_many :hides
has_many :posts
...
end
What is the right way to exclude posts from query results based on a many-to-many relationship association such as hides (based on hides.user_id)?
Thanks in advance!

Mongoid/MongoDB: Order a query by the value of an embedded document?

I am attempting to order the results of a query by the value of a specific embedded document, but even with what seems to be a valid set of options and using the $elemMatch operator, my results are coming back in natural order.
My model is composed of Cards, which embeds_many :card_attributes, which in turn reference a specific CardAttributeField and contain an Integer value. I would like to be able to order a collection of Cards by that value.
I am able to isolate a collection of Cards which have a CardAttribute referencing a specific CardAttributeField like this:
cards = Card.where(:card_attributes.elem_match => {
:card_attribute_field_id => card_attribute_field.id
})
If I knew the order in which the card_attributes were set, I could use MongoDB array notation, like this:
cards.order_by(['card_attributes.0.value', :asc])
This does deliver my expected results in test scenarios, but it won't work in the real world.
After much messing around, I found a syntax which I thought would allow me to match a field without using array notation:
cards.asc(:'card_attributes.value'.elem_match => {
:card_attribute_field_id => card_attribute_field.id
})
This produced a set of options on the resulting Mongoid::Criteria which looked like:
{:sort=>{"{#<Origin::Key:0x2b897548 #expanded=nil, #operator=\"$elemMatch\", #name=:\"card_attributes.value\", #strategy=:__override__, #block=nil>=>{:card_attribute_field_id=>\"54c6c6fe2617f55611000068\"}}"=>1}}
However, the results here came back in the same order regardless or whether I called asc() or desc().
Is there any way to do what I'm after? Am I taking the wrong approach, or do I have a mistake in my implementation? Thanks.
Simplified, my model is:
class Card
include Mongoid::Document
# various other fields
has_many :card_attribute_fields
embeds_many :card_attributes do
def for_attribute_field card_attribute_field
where(:card_attribute_field_id => card_attribute_field.id)
end
end
end
class CardAttributeField
include Mongoid::Document
belongs_to :card
field :name, type: String
field :default_value, type: String
field :description, type: String
end
class CardAttribute
include Mongoid::Document
embedded_in :card
field :card_attribute_field_id, type: Moped::BSON::ObjectId
field :value, type: Integer
end

Rails 4 query on delegated attribute from has_many association

In my Rails 4 app (with PostgreSQL DB) I have the following model structure:
class Product
has_many :items
end
class Item
belongs_to :user
belongs_to :product
end
class User
has_one :profile
delegate :name, to: :profile
end
class Profile
belongs_to :user
end
I'm having trouble doing a query for Items on a Product by the item's user name. A few things I've tried:
Product.first
.items
.includes(:user)
.where("user.name = ?", "Blah")
.references(:user)
Item.where(product_id: 2)
.includes(:user)
.where("user.name = ?", "Blah")
.references(:user)
I've also tried things like the below with no success:
Product.first
.items
.includes(user: :profile)
.where("items.user.profile.name = ?", "Blah")
.references(:user)
All of the above queries give me syntax errors. I'm sure I'm doing something silly but am wondering if anyone can help me figure out how to perform my query.
Product.first.items.joins(user: :profile).where(profiles: {name: "Blah"})

Get all keys in MongoMapper Model including its association

Suppose the following model :
class Product
include MongoMapper::Document
key :name, String
key :product_category_id, ObjectId
belongs_to :product_category
end
class ProductCategory
include MongoMapper::Document
key :name, String, :required => true, :unique => true
timestamps!
userstamps!
end
I want to implement an advanced search that will inspect all value inside my Model including its all association like :
I have :
Product A data named "aLaptop" belongs_to:ProductCategory named "Notebook".
Product B data named "aGreatNotebook" belongs_to:ProductCategory named "Notebook".
When I search with keyword named "Notebook", I want to search it to Product.name fields and also its associations which mean ProductCategory.name also. So it will returned both of that items, because Product A have ProductCategory.name "Notebook" & Product B have Product.name "aGreatNotebook" and ProductCategory "Notebook"..
How can I do this?? I've already searching for 2 days and not success until now:(.. When is in MySQL, I used join table.. But hows in MongoMapper??
Please help.. Thank you..
You can't do joins in MongoDB. So the basic idea is to get the ObjectId associated with the "Notebook" category and then to query the products where product_category is equal to notebook_id. This generally involves two queries. So that'd be something like this:
notebook_id = ProductCategory.first(:name => "Notebook")
if notebook_id
Product.where({:product_category_id => notebook_id['_id']})
end
The question is confusing, but the title of the question is clear.
So in case someone comes here hoping to see how to get all of the:
keys
associations
Read on...
To get the keys in a model:
ConfinedSpace.keys.keys
=> ["_id", "photo_ids", "include_in_qap", "position", "created_at", "updated_at",
"structure_id", "identifier", "name", "description", "notes", "entry_info",
"anchor_points", "nature", "special_equipment", "rescue_overview"]
And to get the associations:
ConfinedSpace.associations.each{|name,assoc| puts name}
photos
attachments
activities
structure
videos
And the class (edited for brevity):
class ConfinedSpace
include MongoMapper::EmbeddedDocument
include Shared::HasPhotos
include Shared::HasAttachments
include HasActivities
TAG = "ConfinedSpace"
belongs_to :structure
many :videos, :as => :attachable
key :identifier, String
key :name, String
key :description, String
key :notes, String
key :entry_info, String
key :anchor_points, String
key :nature, String
key :special_equipment, String
key :rescue_overview, String
validates :identifier, presence: true
end

mongoid - how to query by embedded object

I have the following model:
class User
include Mongoid::Document
store_in :users
field :full_name, :type => String
end
class Message
include Mongoid::Document
embeds_one :sender, :class_name => "User"
field :text, :type => String
end
I would like to store User and Message in separated standalone collections so that they could be queried directly, and I would like to have one copy of user for sender in each Message entry. Is my model correct for this kind of request?
And when I have an instance of User user, how could I query the messages where sender = user?
I've tried:
Message.where(:sender => user)
Message.where('sender.id' => user.id)
both not work.
only Message.where('sender.full_name' => user.full_name) worked, but I don't want to rely on a text field when there's an id field to use.
What's the best way to do that?
How I save Message/User:
user = User.new
user.full_name = 'larry'
user.save
m = Message.new(:text => 'a text message')
m.sender = user
m.save
And it results in the database:
> db.users.find({ 'full_name' : 'larry'})
> db.messages.find({})[0]
{
"_id" : ObjectId("4f66e5c10364392f7ccd4d74"),
"text" : "a text message",
"sender" : {
"_id" : ObjectId("4f62e0af03642b3fb54f82b0"),
"full_name" : "larry"
}
}
Like explain by Jordan Durran ( Mongoid lead developer ) in Google group of Mongoid : http://groups.google.com/group/mongoid/browse_thread/thread/04e06a8582dbeced#
You're going to need a separate model if you want to embed the user
data inside the message. When denormalizing like this I generally
namespace one of them, and create a module with the common fields to
include in both - maybe in your case you can call it Sender?
class Sender
include Mongoid::Document
include UserProperties
class << self
def from_user(user)
Sender.new(user.attributes)
end
end
end
class User
include Mongoid::Document
include UserProperties
end
module UserProperties
extend ActiveSupport::Concern
included do
field :full_name, type: String
end
end
class Message
include Mongoid::Document
embeds_one :sender
end
You also don't need the :store_in macro on User - by default it's name
would be "users".
You can't do what you do.
Your user document is save in his one collection because you use the store_in method. And you try save it on an other document ( Message)
If you really want 2 collections, you need use has_one :user in your Message class.
class Message
has_one :sender, :class_name => 'User', :foreign_key => 'sender_id'
end
After you can get your message like :
Message.senders to have all of your sender.