I have a query in which I pull all users who have invested in the portfolio, like below:
class PortfolioShareholdersQuery
def initialize(portfolio)
#portfolio = portfolio
end
def call
User.joins(:cash_transactions)
.where(cash_transactions: { to_wallet: portfolio.wallet })
.select('users.*, SUM(cash_transactions.shares_number) as total_shares_number')
.group('users.id')
end
attr_reader :portfolio
end
Is it possible to add inside of above query a line that will do the division of two numbers as below?
(user.total_shares_number / portfolio.portfolio_setup_infos.last.total_shares_sold)
# total_shares_number comes from first select
# portfolio_setup_infos is a has_many relation to portfolio
[Edit]
#models association
class User < ApplicationRecord
has_one :wallet, as: :walletable
has_many :cash_transactions, through: :wallet
end
class Portfolio < ApplicationRecord
has_one :wallet, as: :walletable
end
class Wallet < ApplicationRecord
belongs_to :walletable, polymorphic: true
has_many :cash_transactions
end
class CashTransaction < ApplicationRecord
belongs_to :wallet
belongs_to :to_wallet, class_name: 'Wallet', optional: true
end
[Edit 2]
> divisor = portfolio.portfolio_setup_infos.last.total_shares_sold
PortfolioSetupInfo Load (0.5ms) SELECT "portfolio_setup_infos".* FROM "portfolio_setup_infos" WHERE "portfolio_setup_infos"."portfolio_id" = $1 ORDER BY "portfolio_setup_infos"."id" DESC LIMIT $2 [["portfolio_id", 6], ["LIMIT", 1]]
=> 263
> shareholders = User.joins(:cash_transactions).where(cash_transactions: { to_wallet: portfolio.wallet }).select("users.*, SUM(cash_transactions.shares_number) as total_shares_number, (total_shares_number/#{divisor}").group('users.id')
Wallet Load (0.6ms) SELECT "wallets".* FROM "wallets" INNER JOIN "spv_setups" ON "wallets"."walletable_id" = "spv_setups"."id" WHERE "spv_setups"."portfolio_id" = $1 AND "wallets"."walletable_type" = $2 LIMIT $3 [["portfolio_id", 6], ["walletable_type", "SpvSetup"], ["LIMIT", 1]]
User Load (3.3ms) SELECT users.*, SUM(cash_transactions.shares_number) as total_shares_number, (total_shares_number/263 FROM "users" INNER JOIN "wallets" ON "wallets"."walletable_type" = $1 AND "wallets"."walletable_id" = "users"."id" INNER JOIN "cash_transactions" ON "cash_transactions"."wallet_id" = "wallets"."id" WHERE "cash_transactions"."to_wallet_id" = $2 GROUP BY "users"."id" /* loading for inspect */ LIMIT $3 [["walletable_type", "User"], ["to_wallet_id", 8], ["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: syntax error at or near "FROM")
LINE 1: ... as total_shares_number, (total_shares_number/263 FROM "user...
^
class Portfolio < ApplicationRecord
def divisor
portfolio_setup_infos.last.total_shares_sold
end
end
class PortfolioShareholdersQuery
def initialize(portfolio)
#portfolio = portfolio
#divisor = portfolio.divisor.to_f
end
def call
select =<<-SEL.squish
users.*,
SUM(cash_transactions.shares_number) as total_shares_number,
(SUM(cash_transactions.shares_number)/#{divisor}) as ratio
SEL
User.joins(:cash_transactions)
.where(cash_transactions: { to_wallet: portfolio.wallet })
.select(select)
.group('users.id')
end
attr_reader :portfolio, :divisor
end
Related
Not sure if this is a bug, a feature, a missed doc or a bad config, however when I set all of the translation attributes to a null-sy value, then the translation record self-destructs.
I have the table-backend Mobility setup as follows:
schema.rb
create table "base_model", force: :cascade do |t|
t.boolean "is_animal"
t.boolean "is_vehicle"
...
end
create table "base_model_translation", force: :cascade do |t|
t.bigint "base_model_id", null: false
t.string "locale", null: false
t.boolean "is_animal_translation"
t.boolean "is_vehicle_translation"
...
end
base_model.rb
class BaseModel < ApplicationRecord
extend Mobility
has_many :translations,
class_name: "BaseModelTranslations",
autosave: true,
dependent: :destroy
translates :is_animal_translation
translates :is_vehicle_translation
end
base_model_translation.rb
class BaseModelTranslation < ApplicationRecord
belongs_to :base_model,
touch: true,
inverse_of: :translations
end
On the Rails console:
base_model = BaseModel.last
base_model.reload.translations # => []
# SET TO NULLSY
base_model.is_animal_translation = true
base_model.save
base_model.reload.translations
# => [
# <#BaseModel::Translation
# id: 1,
# is_animal_translation: true,
# is_vehicle_translation: nil,
# ...>]
base_model.is_animal_translation = false
base_model.save
base_model.reload.translations # => []
# SET TO PARTIAL NULLSY THEN FULL NULLSY
base_model.is_animal_translation = true
base_model.is_vehicle_translation = true
base_model.save
base_model.reload.translations
# => [
# <#BaseModel::Translation
# id: 1,
# is_animal_translation: true,
# is_vehicle_translation: true,
# ...>]
base_model.is_animal_translation = false
base_model.save
base_model.reload.translations
# => [
# <#BaseModel::Translation
# id: 1,
# is_animal_translation: false,
# is_vehicle_translation: true,
# ...>]
base_model.is_animal_translation = false
base_model.save
base_model.reload.translations # => []
Is this an intended behaviour, if so is there a bypass for this?
EDIT: Looks like this was an an intended behaviour (https://github.com/shioyama/mobility/blob/3cbeaeec8ef0a6d22ee05229141cd2b2bb33b17f/lib/mobility/backends/active_record/table.rb#L306), is there a bypass for this?
help me please to understand, what is going on here? I have profiles with has_and_belongs_to_many relation with tags. And I want to be able to filter profiles, that contains at least all tags, not any of them. How can I do it?
Im trying to do it this way:
Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
But here I have really wierd results:
array = ['212', '213', '214']
=> ["212", "213", "214"]
profile.tag_ids
=> [212, 214, 213]
array = ['212', '214']
=> ["212", "214"]
irb(main):051:0> Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
SQL (0.7ms) SELECT "profiles"."id" AS t0_r0, "profiles"."user_id" AS t0_r1, "profiles"."description" AS t0_r2, "profiles"."created_at" AS t0_r3, "profiles"."updated_at" AS t0_r4, "profiles"."first_name" AS t0_r5, "profiles"."last_name" AS t0_r6, "profiles"."date_of_birth" AS t0_r7, "profiles"."gender" AS t0_r8, "profiles"."short_time_price" AS t0_r9, "profiles"."long_time_price" AS t0_r10, "profiles"."city" AS t0_r11, "profiles"."line" AS t0_r12, "profiles"."instagram" AS t0_r13, "profiles"."facebook" AS t0_r14, "profiles"."whats_app" AS t0_r15, "profiles"."we_chat" AS t0_r16, "profiles"."other_contacts" AS t0_r17, "profiles"."geo_unit_id" AS t0_r18, "tags"."id" AS t1_r0, "tags"."body_en" AS t1_r1, "tags"."body_ru" AS t1_r2, "tags"."tags_group_id" AS t1_r3 FROM "profiles" LEFT OUTER JOIN "profiles_tags" ON "profiles_tags"."profile_id" = "profiles"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "profiles_tags"."tag_id" WHERE "tags"."id" IN ($1, $2) GROUP BY profiles.id, tags.id HAVING (COUNT(tags) >= 2) LIMIT $3 [["id", 212], ["id", 214], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
array = ['212', '213', '214']
=> ["212", "213", "214"]
irb(main):049:0> Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
SQL (0.8ms) SELECT "profiles"."id" AS t0_r0, "profiles"."user_id" AS t0_r1, "profiles"."description" AS t0_r2, "profiles"."created_at" AS t0_r3, "profiles"."updated_at" AS t0_r4, "profiles"."first_name" AS t0_r5, "profiles"."last_name" AS t0_r6, "profiles"."date_of_birth" AS t0_r7, "profiles"."gender" AS t0_r8, "profiles"."short_time_price" AS t0_r9, "profiles"."long_time_price" AS t0_r10, "profiles"."city" AS t0_r11, "profiles"."line" AS t0_r12, "profiles"."instagram" AS t0_r13, "profiles"."facebook" AS t0_r14, "profiles"."whats_app" AS t0_r15, "profiles"."we_chat" AS t0_r16, "profiles"."other_contacts" AS t0_r17, "profiles"."geo_unit_id" AS t0_r18, "tags"."id" AS t1_r0, "tags"."body_en" AS t1_r1, "tags"."body_ru" AS t1_r2, "tags"."tags_group_id" AS t1_r3 FROM "profiles" LEFT OUTER JOIN "profiles_tags" ON "profiles_tags"."profile_id" = "profiles"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "profiles_tags"."tag_id" WHERE "tags"."id" IN ($1, $2, $3) GROUP BY profiles.id, tags.id HAVING (COUNT(tags) >= 3) LIMIT $4 [["id", 212], ["id", 213], ["id", 214], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Profile id: 84, ...>]>
array = ['212', '213']
=> ["212", "213"]
irb(main):047:0> Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
=> #<ActiveRecord::Relation [#<Profile id: 84, ...>]>
array = ['214', '213']
=> ["214", "213"]
irb(main):039:0> Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
=> #<ActiveRecord::Relation [#<Profile id: 84, ...>]>
How is it possible? Maybe there is another way to got profiles, containing all provided tags?
db schema with middle table here:
create_table "profiles_tags", force: :cascade do |t|
t.bigint "profile_id"
t.bigint "tag_id"
t.index ["profile_id"], name: "index_profiles_tags_on_profile_id"
t.index ["tag_id"], name: "index_profiles_tags_on_tag_id"
end
migration with relation is here:
# frozen_string_literal: true
class CreateProfilesTags < ActiveRecord::Migration[6.0]
def change
create_table :profiles_tags do |t|
t.belongs_to :profile
t.belongs_to :tag
end
end
end
You are almost there:
Profile.joins(:tags) # <= use `joins` instead of `inclused`
.where(tags: {id: array })
.having('COUNT(tags) >= ?', array.count)
.group('profiles.id') # <= Do not use anything tags related here
It is important to note the difference between includes and joins. Whereas joins always makes a database join included only makes a join under certain conditions, sometimes it simply makes two queries. Use joins when Rails must make a database join to make the query work and use includes when you want to fix a n+1 issue (see Rails :include vs. :joins).
When you need both – a database join and a fix of the n+1 issue – then run the join query as a subquery:
# a new scope in the mode
scope :when_tags, ->(tags) {
joins(:tags)
.where(tags: {id: tags })
.having('COUNT(tags) >= ?', tags.count)
.group('profiles.id')
}
# the actual nested query with includes:
Profile.where(id: Profile.with_tags(array)).include(:tags)
Hello I'm creating an online retail store.
I have a Category model and a Sizes model. They are nested in a form. When I create a Category I also create sizes for that category.
Right now I can create a Category and sizes. However I can't update the sizes in the nested form.
So click edit category and change the name of one size then click update. I get the below error. On screen it just says "has already been taken".
How do I update sizes through this nested form?
/Users/Documents/Safsy/Website/Safsy/Safsy/app/controllers/categories_controller.rb # line 40 CategoriesController#update:
39: def update
=> 40: binding.pry
41: if #category.update(category_params)
42: redirect_to #category
43: flash[:success] = 'Category was successfully updated.'
44: else
45: render "edit"
46: end
47: end
[1] pry(#<CategoriesController>)>
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameters: _destroy, id
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
Unpermitted parameter: _destroy
(0.1ms) begin transaction
Category Load (0.1ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT 1 [["id", 64]]
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XSmall' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Small' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Medium' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Large' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XL' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XXL' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XXXL' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XSmall' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Small' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Medium' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'Large' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XL' LIMIT 1
CACHE (0.0ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XXL' LIMIT 1
Size Exists (0.1ms) SELECT 1 AS one FROM "sizes" WHERE "sizes"."title" = 'XXXL test' LIMIT 1
Category Exists (0.1ms) SELECT 1 AS one FROM "categories" WHERE ("categories"."name" = 'Shorts' AND "categories"."id" != 68) LIMIT 1
(0.1ms) rollback transaction
Category Model:
class Category < ActiveRecord::Base
has_ancestry
has_many :products
has_many :sizes
validates :name, presence: true, length: { maximum: 20 }, uniqueness: true
accepts_nested_attributes_for :sizes, allow_destroy: true
end
Sizes Model:
class Size < ActiveRecord::Base
validates :title, presence: true, length: { maximum: 15 }
validates :title, uniqueness: true
belongs_to :category
end
Category controller:
class CategoriesController < ApplicationController
before_action :set_category, only: [:show, :edit, :update]
before_action :admin_user, only: [:destroy, :index, :edit, :show]
def index
#categories = Category.all
end
def show
#tags = Product.where(category_id: #category.id).tag_counts_on(:tags)
if params[:tag]
#products = Product.tagged_with(params[:tag])
else
#products = Product.where(category_id: #category.id).order("created_at DESC")
end
end
def new
#category = Category.new
3.times do
#category.sizes.build
end
end
def edit
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #category
flash[:success] = "You have created a new category"
else
flash[:danger] = "Your category didn't save"
render "new"
end
end
def update
binding.pry
if #category.update(category_params)
redirect_to #category
flash[:success] = 'Category was successfully updated.'
else
render "edit"
end
end
def destroy
category = Category.find(params[:id])
category.sizes.destroy_all
category.destroy
flash[:success] = "Category deleted"
redirect_to categories_path
end
private
def set_category
#category = Category.find(params[:id])
end
def category_params
params.require(:category).permit(:name, :parent_id, size_ids: [], sizes_attributes: [:title])
end
end
Sizes Controller:
class SizesController < ApplicationController
before_action :logged_in_user, only: [:create, :index, :destroy, :update]
before_action :admin_user, only: [:create, :index, :destroy, :update]
def create
#size = Size.create(size_params)
end
def index
#sizes = Size.all
end
def destroy
Size.find(params[:id]).destroy
end
def update
#size.update_attributes(size_params)
end
private
def size_params
params.require(:size).permit(:title, :category_id)
end
end
Here are the params at Category Update method:
39: def update
=> 40: binding.pry
41: if #category.update(category_params)
42: redirect_to #category
43: flash[:success] = 'Category was successfully updated.'
44: else
45: render "edit"
46: end
47: end
[1] pry(#<CategoriesController>)> params
=> {"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"ZHuWURBwIctFJLgZ1HGNeKvGpK8LlgH9S6Mrh7No/CFdjtwNFoNtfi9NIVHBVhApYH/U5tuOzV0eqhSq/95SCw==",
"category"=>
{"name"=>"Shorts",
"parent_id"=>"64",
"sizes_attributes"=>
{"0"=>{"title"=>"XSmall", "_destroy"=>"false", "id"=>"21"},
"1"=>{"title"=>"Small", "_destroy"=>"false", "id"=>"22"},
"2"=>{"title"=>"Medium", "_destroy"=>"false", "id"=>"23"},
"3"=>{"title"=>"Large", "_destroy"=>"false", "id"=>"24"},
"4"=>{"title"=>"XL", "_destroy"=>"false", "id"=>"25"},
"5"=>{"title"=>"XXL", "_destroy"=>"false", "id"=>"26"},
"6"=>{"title"=>"XXXL test", "_destroy"=>"false", "id"=>"27"}}},
"commit"=>"Update Category",
"controller"=>"categories",
"action"=>"update",
"id"=>"68"}
Permit _destroy and id of nested form like this:-
def category_params
params.require(:category).permit(:name, :parent_id, size_ids: [], sizes_attributes: [:id, :title, :_destroy])
end
I have the following Models:
Moon -> Planet -> Star -> Galaxy -> Universe
Moon belongs_to Planet, Planet belongs_to Star and so on:
class Moon < ActiveRecord::Base
belongs_to :planet, inverse_of: :moons
has_one :star, through: :planet
has_one :galaxy, through: :star
has_one :universe, through: :galaxy
end
class Planet < ActiveRecord::Base
belongs_to :star, inverse_of: :planets
has_many :moons, inverse_of: :planet, dependent: :destroy
end
The Problem
I'm trying to get Rails to load a hierarchy of these objects to memory. I'm trying to achieve 2 things:
Load everything using one efficient query
Be able to use calls such as moon.galaxy and not moon.planet.star.galaxy without additional calls to the database.
I tried two approaches. The first (m1) is calling includes on Moon with all the relations flatten. This results in a very inefficient query but I can call m1.galaxy. The second (m2) is calling includes with the hierarchy of relations. This results in an efficient query, but I can call m2.galaxy without going to the database.
What is the proper way to do that?
Example
m1 - inefficient query, m2 - efficient query
irb(main):152:0* m1 = Moon.includes(:planet, :star, :galaxy).where(galaxies: {name: 'The Milky Way'}).first
SQL (1.9ms) SELECT "moons"."id" AS t0_r0, "moons"."name" AS t0_r1, "moons"."planet_id" AS t0_r2, "moons"."created_at" AS t0_r3, "moons"."updated_at" AS t0_r4, "planets"."id" AS t1_r0, "planets"."name" AS t1_r1, "planets"."star_id" AS t1_r2, "planets"."created_at" AS t1_r3, "planets"."updated_at" AS t1_r4, "stars"."id" AS t2_r0, "stars"."name" AS t2_r1, "stars"."galaxy_id" AS t2_r2, "stars"."created_at" AS t2_r3, "stars"."updated_at" AS t2_r4, "galaxies"."id" AS t3_r0, "galaxies"."name" AS t3_r1, "galaxies"."universe_id" AS t3_r2, "galaxies"."created_at" AS t3_r3, "galaxies"."updated_at" AS t3_r4 FROM "moons" LEFT OUTER JOIN "planets" ON "planets"."id" = "moons"."planet_id" LEFT OUTER JOIN "planets" "planets_moons_join" ON "planets_moons_join"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" ON "stars"."id" = "planets_moons_join"."star_id" LEFT OUTER JOIN "planets" "planets_moons_join_2" ON "planets_moons_join_2"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" "stars_moons_join" ON "stars_moons_join"."id" = "planets_moons_join_2"."star_id" LEFT OUTER JOIN "galaxies" ON "galaxies"."id" = "stars_moons_join"."galaxy_id" WHERE "galaxies"."name" = $1 ORDER BY "moons"."id" ASC LIMIT 1 [["name", "The Milky Way"]]
=> #<Moon id: 1, name: "The Moon", planet_id: 1, created_at: "2015-10-28 10:02:04", updated_at: "2015-10-28 10:02:04">
irb(main):153:0> m2 = Moon.includes(planet: {star: :galaxy}).where(galaxies: {name: 'The Milky Way'}).first
SQL (0.5ms) SELECT "moons"."id" AS t0_r0, "moons"."name" AS t0_r1, "moons"."planet_id" AS t0_r2, "moons"."created_at" AS t0_r3, "moons"."updated_at" AS t0_r4, "planets"."id" AS t1_r0, "planets"."name" AS t1_r1, "planets"."star_id" AS t1_r2, "planets"."created_at" AS t1_r3, "planets"."updated_at" AS t1_r4, "stars"."id" AS t2_r0, "stars"."name" AS t2_r1, "stars"."galaxy_id" AS t2_r2, "stars"."created_at" AS t2_r3, "stars"."updated_at" AS t2_r4, "galaxies"."id" AS t3_r0, "galaxies"."name" AS t3_r1, "galaxies"."universe_id" AS t3_r2, "galaxies"."created_at" AS t3_r3, "galaxies"."updated_at" AS t3_r4 FROM "moons" LEFT OUTER JOIN "planets" ON "planets"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" ON "stars"."id" = "planets"."star_id" LEFT OUTER JOIN "galaxies" ON "galaxies"."id" = "stars"."galaxy_id" WHERE "galaxies"."name" = $1 ORDER BY "moons"."id" ASC LIMIT 1 [["name", "The Milky Way"]]
=> #<Moon id: 1, name: "The Moon", planet_id: 1, created_at: "2015-10-28 10:02:04", updated_at: "2015-10-28 10:02:04">
m1 - I can call moon.galaxy.name in memory, m2 - moon.galaxy.name calls DB
irb(main):154:0> m1.galaxy.name
=> "The Milky Way"
irb(main):155:0> m2.galaxy.name
Galaxy Load (0.5ms) SELECT "galaxies".* FROM "galaxies" INNER JOIN "stars" ON "galaxies"."id" = "stars"."galaxy_id" INNER JOIN "planets" ON "stars"."id" = "planets"."star_id" WHERE "planets"."id" = $1 LIMIT 1 [["id", 1]]
=> "The Milky Way"
m1 - moon.planet.star.galaxy.name calls DB, m2 - I can call moon.planet.star.galaxy.name in memory
irb(main):156:0> m1.planet.star.galaxy.name
Star Load (3.3ms) SELECT "stars".* FROM "stars" WHERE "stars"."id" = $1 LIMIT 1 [["id", 1]]
Galaxy Load (0.3ms) SELECT "galaxies".* FROM "galaxies" WHERE "galaxies"."id" = $1 LIMIT 1 [["id", 1]]
=> "The Milky Way"
irb(main):157:0> m2.planet.star.galaxy.name
=> "The Milky Way"
A Diff of the Queries
Hm, I think your second way (m2) is correct. Bay also consider eager_load because includes may trigger several SQL queries instead of one for each association (planet, start etc).
I think your problem in these incorrect associations star, galaxy and universe.
class Moon < ActiveRecord::Base
belongs_to :planet, inverse_of: :moons
has_one :star, through: :planet
has_one :galaxy, through: :star
has_one :universe, through: :galaxy
end
Just remove them and try again.
Please notice you can't use has_one such way (example in documentation)
The illustration below is taken from my lecture slide. I've use both composition and inheritance to code a Student - Person class, I think both of them pretty reasonable.
(I know if One person can have many positions - 1:n , then inheritance doesn't work though, so I'm only taking about 1 person : 2 position relationship).
The code using Inheritance:
class Person
attr_accessor :name, :gender, :height, :weight
def initialize(name, gender, height, weight)
#name = name
#gender = gender
#height = height
#weight = weight
end
def cry
"woooo"
end
end
class Student < Person
attr_accessor :student_id
def initialize(name, gender, height, weight, student_id)
super(name, gender, height, weight)
#student_id = student_id
end
def study
"Student #{name} with #{student_id} is studying now"
end
end
s = Student.new("Lin", "Male", 173, 75, 666777)
puts s.cry()
puts s.study
Code using Composition:
class Person
attr_accessor :name, :gender, :height, :weight, :position
def initialize(name, gender, height, weight, position = nil)
#name = name
#gender = gender
#height = height
#weight = weight
#position = position
end
def cry
"woooo"
end
end
class Student
attr_accessor :student_id
def initialize(student_id)
#student_id = student_id
end
def study
"#{student_id} is studying now"
end
end
s = Student.new(666777)
p = Person.new("Lin", "Male", 173, 75, s)
puts p.cry()
puts p.position.study
And I found the code using composition has one bad side, I can't make student call he's name. I mean I can't make the study() method return something like the inheritance code does:
"Student #{name} with #{student_id} is studying now"