I need to query my models and produce a record similar to this:
[{
"subscriber": {
"email": "user#example.com",
"subscriptions": [{
"confirmed": true,
"subscriptionable": {
"name": "Place XYZ",
"comments": [{
"author": "John",
"body": "Hello."
}]
}
}]
}
}]
My models look like this:
class Subscriber < ApplicationRecord
has_many :subscriptions
end
class Subscription < ApplicationRecord
belongs_to :subscriptionable, polymorphic: true
belongs_to :subscriber
end
class Place < ApplicationRecord
has_many :subscriptions, as: :subscriptionable
has_many :comments, as: :commentable
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
So far I'm able to produce the record I want by running this query:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
The problem is I that, with the query above, I can't specify any where conditions. For example, this will fail:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
.where(subscriptions: { confirmed: true })
> ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :subscriptionable
And the other issue is that I can't just get certain attributes, for example:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
.pluck("subscribers.email")
> ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :subscriptionable
Edit
Maybe this will help clarify: what I would like to achieve is something in the lines of this SQL query:
SELECT subscriptions.name as sub_name, subscriptions.email as sub_email,
places.name as place_name, comments.author as com_author, comments.body as com_body, subscribers.token
FROM subscriptions
JOIN subscribers
ON subscriptions.subscriber_id = subscribers.id
JOIN places
ON subscriptionable_id = places.id
JOIN comments
ON places.id = commentable_id
WHERE subscriptions.confirmed
AND subscriptionable_type = 'Place'
AND commentable_type = 'Place'
AND comments.status = 1
AND comments.updated_at >= '#{1.week.ago}'
Related
I have models:
class Order < ApplicationRecord
acts_as_paranoid
has_paper_trail
enum status: %i[created in_process]
has_many :order_containers
has_many :line_items
end
class LineItem < ApplicationRecord
acts_as_paranoid
has_paper_trail
enum status: %i[in_process collected]
belongs_to :order
belongs_to :variant
end
class Variant < ApplicationRecord
acts_as_paranoid
has_paper_trail
has_many :line_items
belongs_to :product
validates :barcode, presence: true
end
class Product < ApplicationRecord
acts_as_paranoid
has_paper_trail
belongs_to :isles, required: false
has_many :variants
validates :name, :volume, :sku, :price, presence: true
end
class Isle < ApplicationRecord
acts_as_paranoid
has_paper_trail
has_many :products
validates :name, presence: true
end
I need to output only those orders in which the products belong to a specific island. For example, if there are no products in the order that belong to the island I need, then this order and its products do not need to be displayed. And if there are products in the order that belong to a specific island for example (isle.id 1), then such an order needs to be withdrawn and those products that belong to this department
I try this:
#products = Order.includes([:line_items, :variants, :products, :isles]).where('products.isle_id = isle.id').references(:orders)
but i got error:
ailure/Error: return { "#{root_name}": [] } if records.blank?
ActiveRecord::StatementInvalid:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "products"
LINE 1: ..."orders" WHERE "orders"."deleted_at" IS NULL AND (products.i...
I'm sorry if I didn't design well, I'm a novice developer, and here's my first assignment)
This will return all products in Order#1 from Isle#1. If order has multiple variants from the same product it will return duplicate products, if this is not what you need add .distinct to these queries.
>> order = Order.first
>> isle = Isle.first
>> Product.joins(variants: { line_items: :order }).where(isle_id: isle, line_items: { order_id: order })
=> [#<Product:0x00007f1551fc4820 id: 1, isle_id: 1>,
#<Product:0x00007f1551fc4258 id: 2, isle_id: 1>]
You can add a few associations to Order to simplify this:
class Order < ApplicationRecord
has_many :line_items
has_many :variants, through: :line_items
has_many :products, through: :variants
end
>> Order.first.products.where(isle_id: Isle.first)
=> [#<Product:0x00007f154babcb30 id: 1, isle_id: 1>,
#<Product:0x00007f154babca18 id: 2, isle_id: 1>]
Update
Make sure you're creating the associations correctly. Use create! and save! methods in the console to raise any validation errors.
# NOTE: create a new order with two products in two different isles
# just add the required attributes from your models.
order = Order.create!(line_items: [
LineItem.new(variant: Variant.new(product: Product.new(isle: (isle = Isle.create!)))),
LineItem.new(variant: Variant.new(product: Product.new(isle: Isle.new)))
])
# NOTE: verify that you have two products
>> order.products
=> [#<Product:0x00007f6f1cb964e0 id: 1, isle_id: 1>,
#<Product:0x00007f6f1cb963f0 id: 2, isle_id: 2>]
# NOTE: filter by isle
>> order.products.where(isle_id: isle)
=> [#<Product:0x00007f6f1ccda630 id: 1, isle_id: 1>]
>> order.products.where(isle_id: 2)
=> [#<Product:0x00007f6f1d140cd8 id: 2, isle_id: 2>]
https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
You have quite a few issues with the structure of that. If you truly just want the products for a specific Isle then you should be able to just use:
#products = Product.where(isle_id: my_isle_variable)
You also probably need to update the models so that Product belongs_to :isle (singular not plural)
Goal: I would like to include all of a customers medical conditions as an array in the result of a customer.
for:
cust = Customer.includes(:conditions).find(1)
expected result:
#<Customer id: 1, first_name: "John", last_name: "Doe", conditions [...]>
actual result:
#<Customer id: 1, first_name: "John", last_name: "Doe">
code:
I have 2 classes and a 3rd join class (ConditionsCustomer).
class Customer < ApplicationRecord
has_many :conditions_customers
has_many :conditions, through: :conditions_customers
end
#join table. Contains 2 foreign_keys (customer_id, condition_id)
class ConditionsCustomer < ApplicationRecord
belongs_to :customer
belongs_to :condition
end
class Condition < ApplicationRecord
has_many :conditions_customers
has_many :customers, through: :conditions_customers
end
What's interesting is that I see 3 select queries getting fired (customer, join table and medical conditions table) so I know the includes is somewhat working but unfortunately customer returns without the medical conditions.
I've also tried using a join but I get an array of same customer over and over again.
Is there an easy way to do this with ActiveRecord? I would prefer not having to merge the record manually.
Not really possible via active record, as json offers some cool possibilities :
render json: customers,
include: {
conditions: {
only: [:attr1, :attr2], # filter returned fields
methods: [:meth1, :meth2] # if you need model methods
},
another_joined_model: {
except: [:password] # to exclude specific fields
}
}
I'm trying to include / preload a has_many association that has a dynamic runtime condition. I'm getting the correct result, but my variable is hard coded and I'm not sure how to pass it from includes.
My query:
#volunteers = Volunteer.includes({ signups: [{position: [{ eventday: :event }] }] }, :event_survey_results).where(events: { id: #event.id })
Volunteer model:
class Volunteer < ApplicationRecord
# Hard coded solution works
has_many :event_survey_results, -> { where event_id: 60 }, class_name: 'Questionnaires::SurveyResult'
end
# Dynamic solution. How to pass event_id to this from :includes ?
has_many :event_survey_results, ->(event_id) { where event_id: event_id }, class_name: 'Questionnaires::SurveyResult'
end
I need to pass event_id to event_survey_results has_many dynamic solution in the :includes statement. How can I achieve this?
I'm using Rails 5.1
has_many doesn't have such feature as it can be done using a standard where clause as per Rails Team, check please :
There is no need to add this functionality. It's already there using standard where clauses
Reference:
https://github.com/rails/rails/issues/3912
You can do the following:
Edited query:
# #event.id refers to the value of 60
#volunteers = Volunteer.includes({ signups: [{position: [{ eventday: :event }] }] }, :event_survey_result).where(event_survey_results: { id: #event.id })
Volunteer model:
class Volunteer < ApplicationRecord
# No need to filter here, as you can filter it using standard where
has_many :event_survey_results , class_name: 'Questionnaires::SurveyResult'
end
I'm running into an issue with n+1 queries and I want to eager load a relationship, except I'm having trouble defining the relationship. It's complicated, haha, hear me out.
I have two models.
class Pokemon < ActiveRecord::Base
belongs_to :pokemon_detail, primary_key: "level", foreign_key: "level"
end
class PokemonDetail < ActiveRecord::Base
has_one :pokemons, primary_ley: "level", foreign_key: "level"
end
Let's say, I have a the following record:
<Pokemon id: 1, name: "squirtle", level: 1>
Which would obviously correspond with the following PokemonDetail
<PokemonDetail id: 1, name: "squirtle", level: 1, health: 150>
And that can be easily eager loaded like Pokemon.all.includes(:pokemon_detail), however, I want to eager load the information about one level higher.
<PokemonDetail id: 2, name: "squirtle", level: 2, health: 300>
I currently find the information about one level higher with the following method within the Pokemon model.
def next_level_info
PokemonDetail.where(level: self.level + 1)
end
But this isn't eager loaded. Any ideas?
Refactor schema first, to make it more sense:
pokemons { current_level_value, name }
pokemon_levels {value, pokemon_id (foreign key), health }
Redefine models like this:
class PokemonLevel < ActiveRecord::Base
belongs_to :pokemon
end
class Pokemon < ActiveRecord::Base
has_many :pokemon_levels
has_one :current_pokemon_level, -> { joins(:pokemon).where('pokemons.current_level_value = pokemon_levels.value') },
foreign_key: :pokemon_id, class_name: 'PokemonLevel'
has_one :next_pokemon_level, -> { joins(:pokemon).where('pokemons.current_level_value + 1 = pokemon_levels.value') },
foreign_key: :pokemon_id, class_name: 'PokemonLevel'
end
Simply use it eg:
Pokemon.includes(:current_pokemon_level, :next_pokemon_level).find(123)
I use PokemonLevel instead of PokemonDetail, because it is clearer to me
I currently have a model for team.rb and user.rb, which is a many to many relationship. I have created the join table teams_users but I am not sure how to populate this table in my seeds.rb?
For example, I have :
user = User.create({ first_name: 'Kamil', last_name: 'Bo', email: 'bo#gmail.com'})
team = Team.create([{ name: 'Spot Forwards', num_of_games: 10, day_of_play: 4}])
But the following does not work???
TeamsUsers.create({ team_id: team.id, user_id: user.id })
I get a message :
uninitialized constant TeamsUsers
This isn't optimized but
user.team_ids = user.team_ids < team.id
user.save
or if this is the first team
user.team_ids = [team.id]
user.save
ALso start using has_many :through. then you will have a TeamUser model. it's a life saver if the join table needs more attributes
Pick a side to work from and then, as #drhenner suggests, use the _ids property to create the association. For example, working with the User model, create the teams first, then the users, assigning them to teams as you go:
teams = Team.create([
{ name: 'Team 1' },
{ name: 'Team 2' },
{ name: 'Team 3' },
# etc.
])
User.create([
{ name: 'User 1', team_ids: [teams[0].id, teams[2].id] },
{ name: 'User 2', team_ids: [teams[1].id, teams[2].id] },
{ name: 'User 3', team_ids: [teams[0].id, teams[1].id] },
# etc.
])
From comment above:
You can have multiple relationships configured on a has_many :through relationship. It's up to you which ones you want to implement. These are all the possibilities:
class Team < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
end
class Membership < ApplicationRecord
belongs_to :team
belongs_to :user
end
class User < ApplicationRecord
has_many :memberships
has_many :teams, through: :memberships
end
So, when dealing with the Team model, you can use: team.memberships and team.users;
when dealing with the User model, you can use: user.memberships and user.teams;
and if dealing with the join model, you can use: membership.team and membership.user.
You can omit the relationship references to the join model if you don't use it—especially if you're treating the relationship between Team and User like a standard has_and_belongs_to_many relationship:
class Team < ApplicationRecord
has_many :users, through: :memberships
end
class User < ApplicationRecord
has_many :teams, through: :memberships
end
This gives you team.users and user.teams.
Using Rails, say you have tables foos and bars in a many-to-many relationship using table foos_bars, you can seed their associations like this:
bar1 = Bar.find(1)
bar2 = Bar.find(2)
foo1 = Foo.find(1) # for example
foo1.bars << bar1
foo1.bars << bar2
foo1.save
This will update the joins table foos_bars with associations <foo_id:1, bar_id:1>, <foo_id:1, bar_id:2>
Hope this helps.