How to get has_and_belongs_to_many relation objects together - sql

I have HABTM associated tables like below.
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
end
How do I get User(s) with associated Groups together(nested), like below
{
id: 8,
name: "John",
groups: [
{
id: 17,
name: "Alpha Group",
user_id: 8
},
{
id: 18,
name: "Bravo Group",
user_id: 8
}
],
},
Is there a nice rails way of doing this?
I can manually create object like above but it would be nice to have a simpler query.

def index
#group = Group.find(params[:id])
respond_to do |format|
format.html
format.json { #group.users.map do |user|
user.to_json(:include => :groups)
end
}
end
end
It'll return an array like:
[
{
"id":1,
"email":"admin#example.com",
"groups":[
{
"id":1,
"name":"Yo"
},
{
"id":2,
"name":"Bro"
},
]
},
{
"id":2,
"email":"valar#example.com",
"groups":[
{
"id":1,
"name":"Arya"
},
{
"id":2,
"name":"Stark"
},
]
}
]

Related

Counting in Many-To-Many Relations Sequelize

I am trying to count number of Followers and Followings of a user.
as followingCount and followerCount
User Model
User.init(
{
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
email: {
type: DataTypes.STRING,
},
password: {
type: DataTypes.STRING,
},
}
);
static associate(models) {
// Follow relationship
this.belongsToMany(models.User, {
through: 'UserFollow',
foreignKey: 'followerId',
as: 'following',
});
// Follow relationship
this.belongsToMany(models.User, {
through: 'UserFollow',
foreignKey: 'followeeId',
as: 'follower',
});
}
Where UserFollow is a Joint Table with columns followeeId and followerId.
My current approach for finding number of followings is something like this :
const user = await User.findOne({
where: {
id,
},
attributes: [
'id',
'userName',
'email',
[sequelize.fn('COUNT', sequelize.col('following->UserFollow.followeeId')), 'followingCount'],
],
include: [
{
model: User,
as: 'following',
attributes: ['id', 'userName', 'email'],
through: {
attributes: [],
},
},
],
group: ['User.id', 'following.id'],
});
return user;
And Output getting is like this:
Here I am getting followingCount as 1... but it should be 3.
"data": {
"id": "1af4b9ea-7c58-486f-a37a-e46461487b06",
"userName": "xyz",
"email": "xyz#gmail.com",
"followingCount": "1", <------ I want this to be 3
"following": [
{
"id": "484202b0-a6d9-416d-a8e2-6681deffa3d1",
"userName": "uqwheuo",
"email": "uqwheuo#gmail.com"
},
{
"id": "56c8d9b0-f5c6-4b2e-b32c-be6363294614",
"userName": "aiwhroanc",
"email": "aiwhroanc#gmail.com"
},
{
"id": "9a3e4074-c7a0-414e-8df4-cf448fbaf5fe",
"userName": "iehaocja",
"email": "iehaocja#gmail.com"
}
]
}
I am not able to count in Joint Table..
The reason that you are getting followingCount: 1 is that you group by following.id (followeeId). It only counts unique followeeId which is always 1.
Although, if you take out following.id from group, the SQL doesn't work any more. It will crash with "a column must appear in GROUP BY clause...". This is a common issue in Postgres and this link (https://stackoverflow.com/a/19602031/2956135) explains the topic well in detail.
To solve your question, instead of using group, you can use COUNT OVER (PARTITION BY).
const user = await User.findOne({
where: {
id,
},
attributes: [
'id',
'userName',
'email',
[Sequelize.literal('COUNT("following->UserFollow"."followeeId") OVER (PARTITION BY "User"."id")'), 'followingCount']
],
include: [
{
model: User,
as: 'following',
attributes: ['id', 'userName', 'email'],
through: {
attributes: [],
}
},
],
});
======================================================
Update:
The original query only fetch "following" relationship. In order to fetch followers of this user, you first need to add "follower" association.
Then, since 2 associations is added, we need to add 1 more partition by column to count exactly the followers or followees.
const followeeIdCol = '"following->UserFollow"."followeeId"';
const followerIdCol = '"follower->UserFollow"."followerId"';
const user = await User.findOne({
where: {
id,
},
attributes: [
'id',
'userName',
'email',
// Note that the COUNT column and partition by column is reversed.
[Sequelize.literal(`COUNT(${followeeIdCol}) OVER (PARTITION BY "Users"."id", ${followerIdCol})`), 'followingCount'],
[Sequelize.literal(`COUNT(${followerIdCol}) OVER (PARTITION BY "Users"."id", ${followeeIdCol})`), 'followerCount'],
],
include: [
{
model: User,
as: 'following',
attributes: ['id', 'userName', 'email'],
through: {
attributes: [],
}
},
{
model: User,
as: 'follower', // Add follower association
attributes: ['id', 'userName', 'email'],
through: {
attributes: [],
}
},
],
});

How to search Elasticsearch-rails with association model conditions?

I'm trying to search Doc model has_and_belongs_to_many projects which belongs to specific project.
models/doc.rb
require 'elasticsearch/model'
class Doc < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
settings index: {
analysis: {
tokenizer: {
ngram_tokenizer: {
type: "nGram",
min_gram: "2",
max_gram: "3",
token_chars: [
"letter",
"digit",
"punctuation"
]
}
},
analyzer: {
ngram_analyzer: {
tokenizer: "ngram_tokenizer"
}
},
},
} do
mappings do
indexes :sourcedb, type: 'string', analyzer: 'ngram_analyzer'
indexes :sourceid, type: 'string', analyzer: 'ngram_analyzer'
indexes :body, type: 'string', analyzer: 'ngram_analyzer'
indexes :docs_projects do
indexes :doc_id
indexes :project_id
indexes :projects do
indexes :id, index: :not_analyzed
end
end
end
end
def as_indexed_json(options={})
as_json(
only: [:id, :sourcedb, :sourceid, :body],
include: { projects: {only: :id} }
)
end
end
search method is below
search_docs = docs.search(
query: {
bool:{
must: [
{match: {
'projects.id' => project_id
}
}
]
}
},
size: 5000,
).records.order('sourcedb ASC, sourceid ASC').paginate(page:params[:page], per_page: 10)
This search method finishes without errors but nothing returned.
Doc Load (4.6ms) SELECT "docs".* FROM "docs" INNER JOIN "docs_projects" ON "docs"."id" = "docs_projects"."doc_id" WHERE "docs_projects"."project_id" = 56
(0.4ms) SELECT COUNT(*) FROM "docs" WHERE 1=0
Doc Load (0.3ms) SELECT "docs".* FROM "docs" WHERE 1=0 ORDER BY sourcedb ASC, sourceid ASC LIMIT 10 OFFSET 0
CACHE (0.0ms) SELECT COUNT(*) FROM "docs" WHERE 1=0
I've tried to search with where([projects.id IN (?)], project_ids), but it cannot search docs belongs to project with max size.
How to search by match with associations?
Thanks in advance.
Now, I found the solution. This is how I solved.
doc.rb
settings index: {
analysis: {
tokenizer: {
ngram_tokenizer: {
type: "nGram",
min_gram: "2",
max_gram: "3",
token_chars: [
"letter",
"digit",
"punctuation"
]
}
},
analyzer: {
ngram_analyzer: {
tokenizer: "ngram_tokenizer"
}
},
},
} do
mappings do
indexes :sourcedb, type: 'string', analyzer: 'ngram_analyzer'
indexes :sourceid, type: 'string', analyzer: 'ngram_analyzer'
indexes :body, type: 'string', analyzer: 'ngram_analyzer'
# indexes :docs_projects, type: 'nested' do
indexes :docs_projects do
indexes :doc_id
indexes :project_id
end
indexes :projects do
indexes :id, index: :not_analyzed
end
end
end
search method
def self.search_docs(attributes = {})
minimum_should_match = 0
minimum_should_match += 1 if attributes[:sourcedb].present?
minimum_should_match += 1 if attributes[:sourceid].present?
minimum_should_match += 1 if attributes[:body].present?
if attributes[:project_id].present?
must_array = [
{match: {
'projects.id' => attributes[:project_id]
}
}
]
end
docs = search(
query: {
bool:{
must: must_array,
should: [
{match: {
sourcedb: {
query: attributes[:sourcedb],
fuzziness: 0
}
}
},
{match: {
sourceid: {
query: attributes[:sourceid],
fuzziness: 0
}
}
},
{match: {
body: {
query: attributes[:body],
fuzziness: 'AUTO'
}
}
},
],
minimum_should_match: minimum_should_match
}
},
size: SEARCH_SIZE,
)
return {
total: docs.results.total,
docs: docs.records.order('sourcedb ASC, sourceid ASC')
}
end

Sequelize the nodejs throught model association

I have tree models User, Company and UserCompany.
I have associations
Company.belongsToMany(User, {
as: 'users',
through: {
model: UserCompany,
unique: false
},
onDelete: 'cascade'
});
User.belongsToMany(Company, {
as: 'companies',
through: {
model: UserCompany,
unique: false
},
onDelete: 'cascade'
});
I am trying to query companies buy runing this code
sql.Company.findAll({
where: query,
include: [{model:sql.User, as:'users', attributes: ['id', 'first_name', 'last_name', 'email']}],
order: sort,
limit: limit,
offset: offset
})
I have two elements of UserCompany with CompanyId=10 and UserId=50, but query returns only one of them.
It returns array
{id: 10,
...,
users: {
id: 50,
...,
UserCompany: {}
}
}
So UserCompany is not array, it is just one element. But I want to get them all.
How can I fix my association code?
Your associations are right. The UserCompany property isn't supposed to be showing all of each user's UserCompany relationships -- it's just showing the one that got the user included in that query's results.
If what you're looking for is a list of companies associated with each user in the returned array, you can add an include for that:
Company.findAll({
include: [{
model: User,
as: 'users',
attributes: ['id', 'first_name', 'last_name', 'email'],
// Add this
include: [{
model: Company,
as: 'companies',
attributes: ['id', 'name']
}],
}],
})
This gives you the following format in the returned array:
{
id: 1,
first_name: 'Nelson',
last_name: 'Bighetti',
email: 'bighead#hooli.com',
UserCompany: {...},
companies: [
{ id: 1, name: 'Hooli', UserCompany: {...} },
{ id: 2, name: 'Pied Piper', UserCompany: {...} }
]
}

Autosave issue in rails 3.2

In the given code,
class Supplier < ActiveRecord::Base
has_one :criteria, foreign_key: "crt_sup_id", :autosave => true
self.primary_key = 'sup_id'
end
class Criteria < ActiveRecord::Base
belongs_to :supplier, foreign_key: "crt_sup_id"
self.primary_key = 'crt_id'
self.table_name = 'criterias'
end
autosave is not working when I am submitting the form. Supplier records are created but not Criteria.
Form code
class SupplierForm < Netzke::Basepack::Form
def configure(c)
c.model = 'Supplier'
super
c.items = [
{field_label: "Name", name: :bname},
{field_label: "Detail", name: :detail},
{
layout: :hbox, border: false, defaults: {border: false}, items: [
{
flex: 1,
layout: :anchor,
defaults: {anchor: "-8"},
items: [
{field_label: "Value 1", name: :criteria__val_one, xtype: :checkbox, nested_attribute: true},
{field_label: "Value 2", name: :criteria__val_two, xtype: :checkbox, nested_attribute: true}
]
}
]
}
]
end
end
Controller code
def index
end
Solved with the help of Netzke author. Replace criteria__val_one with criteria_val_one and
criteria__val_two with criteria_val_two. Create virtual attributes in the model class. Now all the values entered in the form is accessible with these virtual attributes and can be saved. Credit goes to Max Gorin. Thanks for the great work (Netzke)

Mongoid querying

I have some application on RoR with Mongodb database. I use Mongoid mapper. Model post.rb
class Post
include Mongoid::Document
field :title, :type => String
field :text, :type => String
embeds_many :comments
end
Model comment.rb
class Comment
include Mongoid::Document
field :name, :type => String
field :content, :type => String
embedded_in :post, :inverse_of => :comments
end
In database this post with some comment has next structure:
{
"_id": ObjectId("4ecbeacf65430f0cef000003"),
"comments": {
"0": {
"name": "my name",
"content": "example content",
"_id": ObjectId("4ecbead365430f0cef000005")
},
"1": {
"name": "some name",
"content": "example content",
"_id": ObjectId("4ecbead665430f0cef000007")
},
"2": {
"name": "some name",
"content": "example content",
"_id": ObjectId("4ecbeada65430f0cef000009")
}
},
"text": "example text",
"title": "example title"
}
And, for example, in database was a few posts with my comments.
I need to find all posts, where "name": "my name", i.e. I need to find all editable by me posts.
It should appear as a array of comments instead of a hash. See my example below.
Also, as per the mongoid docs use the new style field declarations.
comment.rb:
class Comment
include Mongoid::Document
field :name, type: String
field :content, type: String
embedded_in :post
end
post.rb:
class Post
include Mongoid::Document
field :title, type: String
field :text, type: String
embeds_many :comments
end
Rails Console:
p = Post.new(:title => "title", :text => "post")
c1 = Comment.new(:name => "Tyler", :comment => "Awesome Comment!")
c2 = Comment.new(:name => "Joe", :comment => "comment 2")
p.comments << c1
p.comments << c2
p.save
Mongo Console:
> db.posts.findOne()
{
"_id" : ObjectId("4ecd151d096f762149000001"),
"title" : "title",
"text" : "post body",
"comments" : [
{
"name" : "Tyler",
"comment" : "Awesome Comment!",
"_id" : ObjectId("4ecd1569096f762149000002")
},
{
"name" : "Joe",
"comment" : "comment 2",
"_id" : ObjectId("4ecd157f096f762149000003")
}
]}
Then, to do the query you want, which I think was "comments posted by me?":
> db.posts.findOne({"comments.name": "Tyler"})
Also, I would add an index to the comments.name field:
> db.posts.ensureIndex({"comments.name": 1})