ActiveRecord query for coaches a client has bookmarked











up vote
0
down vote

favorite












I'm trying to figure out the best way to set up an ActiveRecord query.





Models



There are three relevant models involved:



class Client < ActiveRecord::Base
has_many :bookmarks, dependent: :destroy
has_many :saved_coaches, through: :bookmarks, source: :bookmarkable, source_type: "Coach"
end

class Bookmark < ActiveRecord::Base
belongs_to :client
belongs_to :bookmarkable, polymorphic: true

scope :type, -> (type) { where(bookmarkable_type: type.to_s.capitalize) }
end

class Coach < ActiveRecord::Base
has_many :bookmarks, as: :bookmarkable, dependent: :destroy
belongs_to :user
end




The problem



I need a performant query to access to the coaches a client has bookmarked, the coach's user model, and the id of relevant bookmark.



I can think of two approaches:



@client = current_user.client

# option 1
@coach_bookmarks = @client.bookmarks.type(:coach).includes(bookmarkable: :user)

# option 2
@coaches = Coach.includes(:bookmarks, :user)
.where(bookmarks: { bookmarkable_type: "Coach", client: @client })


Option 1 seems clearer, but produces a collection of bookmarks, when most of the data I need is on the associated coach. Option 2 gives me a collection of coaches, but the query is a little less clear, and the SQL output is huge. (I've included that below.)



Is one of these solutions better than the other? Is there a third, better solution (maybe through @client.saved_coaches)?





SQL output



Option 1



Bookmark Load (1.3ms)  SELECT "bookmarks".* FROM "bookmarks" WHERE "bookmarks"."client_id" = $1 AND "bookmarks"."bookmarkable_type" = $2  [["client_id", 3], ["bookmarkable_type", "Coach"]]
Coach Load (2.3ms) SELECT "coaches".* FROM "coaches" WHERE "coaches"."id" IN (1)
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (3)


Option 2



SQL (6.3ms)  SELECT "coaches"."id" AS t0_r0, "coaches"."user_id" AS t0_r1,
"coaches"."questionnaire_submitted" AS t0_r2, "coaches"."validated" AS t0_r3,
"coaches"."verified" AS t0_r4, "coaches"."membership_type" AS t0_r5,
"coaches"."new_coach" AS t0_r6, "coaches"."created_at" AS t0_r7,
"coaches"."updated_at" AS t0_r8, "coaches"."title" AS t0_r9,
"coaches"."image_url" AS t0_r10, "coaches"."experience_years" AS t0_r11,
"coaches"."specialties" AS t0_r12, "coaches"."session_types" AS t0_r13,
"coaches"."session_type_other" AS t0_r14, "coaches"."meeting_types" AS t0_r15,
"coaches"."meeting_type_other" AS t0_r16, "coaches"."cancellation_policy" AS
t0_r17, "coaches"."bio" AS t0_r18, "coaches"."price_options" AS t0_r19,
"coaches"."quote" AS t0_r20, "coaches"."current_screen" AS t0_r21,
"coaches"."address_raw" AS t0_r22, "coaches"."business_phone" AS t0_r23,
"coaches"."address_city" AS t0_r24, "coaches"."address_state" AS t0_r25,
"coaches"."address_area" AS t0_r26, "coaches"."latitude" AS t0_r27,
"coaches"."longitude" AS t0_r28, "coaches"."categories" AS t0_r29,
"coaches"."keywords" AS t0_r30, "coaches"."ace_up_handles" AS t0_r31,
"coaches"."stripe_account_id" AS t0_r32, "coaches"."bank_account_info" AS
t0_r33, "coaches"."offer_free_consult" AS t0_r34,
"coaches"."free_consult_length" AS t0_r35, "coaches"."sessions_start_every" AS
t0_r36, "coaches"."max_daily_sessions" AS t0_r37,
"coaches"."minimum_scheduling_notice" AS t0_r38,
"coaches"."buffer_after_session" AS t0_r39, "coaches"."num_reviews" AS t0_r40,
"coaches"."onboard_professional_background" AS t0_r41,
"coaches"."onboard_certifications" AS t0_r42, "coaches"."onboard_challenges"
AS t0_r43, "coaches"."onboard_promoting" AS t0_r44,
"coaches"."onboard_referral_source" AS t0_r45, "coaches"."onboard_interests"
AS t0_r46, "coaches"."onboard_submitted" AS t0_r47, "coaches"."coach_url" AS
t0_r48, "coaches"."timezone" AS t0_r49, "coaches"."average_review" AS t0_r50,
"coaches"."coach_order" AS t0_r51, "coaches"."onboard_business" AS t0_r52,
"coaches"."onboard_services" AS t0_r53, "coaches"."videos" AS t0_r54,
"coaches"."card_highlights" AS t0_r55, "coaches"."edit_requested" AS t0_r56,
"coaches"."specialties_edit" AS t0_r57, "coaches"."session_types_edit" AS
t0_r58, "coaches"."meeting_types_edit" AS t0_r59,
"coaches"."cancellation_policy_edit" AS t0_r60, "coaches"."bio_edit" AS
t0_r61, "coaches"."quote_edit" AS t0_r62, "coaches"."keywords_career" AS
t0_r63, "coaches"."keywords_life" AS t0_r64, "coaches"."keywords_leadership"
AS t0_r65, "coaches"."keywords_business" AS t0_r66,
"coaches"."keywords_communication" AS t0_r67, "coaches"."keywords_executive"
AS t0_r68, "coaches"."qualifications" AS t0_r69, "coaches"."education" AS
t0_r70, "coaches"."target_clients" AS t0_r71, "coaches"."highlights" AS
t0_r72, "coaches"."client_review_link" AS t0_r73, "coaches"."consent" AS
t0_r74, "coaches"."qualifications_edit" AS t0_r75, "coaches"."education_edit"
AS t0_r76, "coaches"."target_clients_edit" AS t0_r77,
"coaches"."highlights_edit" AS t0_r78, "coaches"."text_notifications" AS
t0_r79, "coaches"."email_notifications" AS t0_r80, "coaches"."companies" AS
t0_r81, "coaches"."address_raw_edit" AS t0_r82, "coaches"."longitude_edit" AS
t0_r83, "coaches"."latitude_edit" AS t0_r84, "bookmarks"."id" AS t1_r0,
"bookmarks"."client_id" AS t1_r1, "bookmarks"."bookmarkable_id" AS t1_r2,
"bookmarks"."bookmarkable_type" AS t1_r3, "users"."id" AS t2_r0,
"users"."email" AS t2_r1, "users"."encrypted_password" AS t2_r2,
"users"."reset_password_token" AS t2_r3, "users"."reset_password_sent_at" AS
t2_r4, "users"."remember_created_at" AS t2_r5, "users"."sign_in_count" AS
t2_r6, "users"."current_sign_in_at" AS t2_r7, "users"."last_sign_in_at" AS
t2_r8, "users"."current_sign_in_ip" AS t2_r9, "users"."last_sign_in_ip" AS
t2_r10, "users"."confirmation_token" AS t2_r11, "users"."confirmed_at" AS
t2_r12, "users"."confirmation_sent_at" AS t2_r13, "users"."unconfirmed_email"
AS t2_r14, "users"."failed_attempts" AS t2_r15, "users"."unlock_token" AS
t2_r16, "users"."locked_at" AS t2_r17, "users"."encrypted_otp_secret" AS
t2_r18, "users"."encrypted_otp_secret_iv" AS t2_r19,
"users"."encrypted_otp_secret_salt" AS t2_r20, "users"."consumed_timestep" AS
t2_r21, "users"."otp_required_for_login" AS t2_r22, "users"."personal_phone"
AS t2_r23, "users"."name" AS t2_r24, "users"."created_at" AS t2_r25,
"users"."updated_at" AS t2_r26, "users"."google_token" AS t2_r27,
"users"."google_refresh_token" AS t2_r28, "users"."last_calendar_refresh" AS
t2_r29, "users"."google_expires_at" AS t2_r30,
"users"."google_calendar_refresh_tokens" AS t2_r31, "users"."acquisition" AS
t2_r32, "users"."icalendar_urls" AS t2_r33, "users"."admin" AS t2_r34,
"users"."banned" AS t2_r35, "users"."email_subscriber" AS t2_r36,
"users"."intermediate_reminder_sent" AS t2_r37, "users"."final_reminder_sent"
AS t2_r38 FROM "coaches" LEFT OUTER JOIN "bookmarks" ON
"bookmarks"."bookmarkable_id" = "coaches"."id" AND
"bookmarks"."bookmarkable_type" = $1 LEFT OUTER JOIN "users" ON "users"."id" =
"coaches"."user_id" WHERE "bookmarks"."bookmarkable_type" = $2 AND
"bookmarks"."client_id" = 3 [["bookmarkable_type", "Coach"],
["bookmarkable_type", "Coach"]]









share|improve this question




























    up vote
    0
    down vote

    favorite












    I'm trying to figure out the best way to set up an ActiveRecord query.





    Models



    There are three relevant models involved:



    class Client < ActiveRecord::Base
    has_many :bookmarks, dependent: :destroy
    has_many :saved_coaches, through: :bookmarks, source: :bookmarkable, source_type: "Coach"
    end

    class Bookmark < ActiveRecord::Base
    belongs_to :client
    belongs_to :bookmarkable, polymorphic: true

    scope :type, -> (type) { where(bookmarkable_type: type.to_s.capitalize) }
    end

    class Coach < ActiveRecord::Base
    has_many :bookmarks, as: :bookmarkable, dependent: :destroy
    belongs_to :user
    end




    The problem



    I need a performant query to access to the coaches a client has bookmarked, the coach's user model, and the id of relevant bookmark.



    I can think of two approaches:



    @client = current_user.client

    # option 1
    @coach_bookmarks = @client.bookmarks.type(:coach).includes(bookmarkable: :user)

    # option 2
    @coaches = Coach.includes(:bookmarks, :user)
    .where(bookmarks: { bookmarkable_type: "Coach", client: @client })


    Option 1 seems clearer, but produces a collection of bookmarks, when most of the data I need is on the associated coach. Option 2 gives me a collection of coaches, but the query is a little less clear, and the SQL output is huge. (I've included that below.)



    Is one of these solutions better than the other? Is there a third, better solution (maybe through @client.saved_coaches)?





    SQL output



    Option 1



    Bookmark Load (1.3ms)  SELECT "bookmarks".* FROM "bookmarks" WHERE "bookmarks"."client_id" = $1 AND "bookmarks"."bookmarkable_type" = $2  [["client_id", 3], ["bookmarkable_type", "Coach"]]
    Coach Load (2.3ms) SELECT "coaches".* FROM "coaches" WHERE "coaches"."id" IN (1)
    User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (3)


    Option 2



    SQL (6.3ms)  SELECT "coaches"."id" AS t0_r0, "coaches"."user_id" AS t0_r1,
    "coaches"."questionnaire_submitted" AS t0_r2, "coaches"."validated" AS t0_r3,
    "coaches"."verified" AS t0_r4, "coaches"."membership_type" AS t0_r5,
    "coaches"."new_coach" AS t0_r6, "coaches"."created_at" AS t0_r7,
    "coaches"."updated_at" AS t0_r8, "coaches"."title" AS t0_r9,
    "coaches"."image_url" AS t0_r10, "coaches"."experience_years" AS t0_r11,
    "coaches"."specialties" AS t0_r12, "coaches"."session_types" AS t0_r13,
    "coaches"."session_type_other" AS t0_r14, "coaches"."meeting_types" AS t0_r15,
    "coaches"."meeting_type_other" AS t0_r16, "coaches"."cancellation_policy" AS
    t0_r17, "coaches"."bio" AS t0_r18, "coaches"."price_options" AS t0_r19,
    "coaches"."quote" AS t0_r20, "coaches"."current_screen" AS t0_r21,
    "coaches"."address_raw" AS t0_r22, "coaches"."business_phone" AS t0_r23,
    "coaches"."address_city" AS t0_r24, "coaches"."address_state" AS t0_r25,
    "coaches"."address_area" AS t0_r26, "coaches"."latitude" AS t0_r27,
    "coaches"."longitude" AS t0_r28, "coaches"."categories" AS t0_r29,
    "coaches"."keywords" AS t0_r30, "coaches"."ace_up_handles" AS t0_r31,
    "coaches"."stripe_account_id" AS t0_r32, "coaches"."bank_account_info" AS
    t0_r33, "coaches"."offer_free_consult" AS t0_r34,
    "coaches"."free_consult_length" AS t0_r35, "coaches"."sessions_start_every" AS
    t0_r36, "coaches"."max_daily_sessions" AS t0_r37,
    "coaches"."minimum_scheduling_notice" AS t0_r38,
    "coaches"."buffer_after_session" AS t0_r39, "coaches"."num_reviews" AS t0_r40,
    "coaches"."onboard_professional_background" AS t0_r41,
    "coaches"."onboard_certifications" AS t0_r42, "coaches"."onboard_challenges"
    AS t0_r43, "coaches"."onboard_promoting" AS t0_r44,
    "coaches"."onboard_referral_source" AS t0_r45, "coaches"."onboard_interests"
    AS t0_r46, "coaches"."onboard_submitted" AS t0_r47, "coaches"."coach_url" AS
    t0_r48, "coaches"."timezone" AS t0_r49, "coaches"."average_review" AS t0_r50,
    "coaches"."coach_order" AS t0_r51, "coaches"."onboard_business" AS t0_r52,
    "coaches"."onboard_services" AS t0_r53, "coaches"."videos" AS t0_r54,
    "coaches"."card_highlights" AS t0_r55, "coaches"."edit_requested" AS t0_r56,
    "coaches"."specialties_edit" AS t0_r57, "coaches"."session_types_edit" AS
    t0_r58, "coaches"."meeting_types_edit" AS t0_r59,
    "coaches"."cancellation_policy_edit" AS t0_r60, "coaches"."bio_edit" AS
    t0_r61, "coaches"."quote_edit" AS t0_r62, "coaches"."keywords_career" AS
    t0_r63, "coaches"."keywords_life" AS t0_r64, "coaches"."keywords_leadership"
    AS t0_r65, "coaches"."keywords_business" AS t0_r66,
    "coaches"."keywords_communication" AS t0_r67, "coaches"."keywords_executive"
    AS t0_r68, "coaches"."qualifications" AS t0_r69, "coaches"."education" AS
    t0_r70, "coaches"."target_clients" AS t0_r71, "coaches"."highlights" AS
    t0_r72, "coaches"."client_review_link" AS t0_r73, "coaches"."consent" AS
    t0_r74, "coaches"."qualifications_edit" AS t0_r75, "coaches"."education_edit"
    AS t0_r76, "coaches"."target_clients_edit" AS t0_r77,
    "coaches"."highlights_edit" AS t0_r78, "coaches"."text_notifications" AS
    t0_r79, "coaches"."email_notifications" AS t0_r80, "coaches"."companies" AS
    t0_r81, "coaches"."address_raw_edit" AS t0_r82, "coaches"."longitude_edit" AS
    t0_r83, "coaches"."latitude_edit" AS t0_r84, "bookmarks"."id" AS t1_r0,
    "bookmarks"."client_id" AS t1_r1, "bookmarks"."bookmarkable_id" AS t1_r2,
    "bookmarks"."bookmarkable_type" AS t1_r3, "users"."id" AS t2_r0,
    "users"."email" AS t2_r1, "users"."encrypted_password" AS t2_r2,
    "users"."reset_password_token" AS t2_r3, "users"."reset_password_sent_at" AS
    t2_r4, "users"."remember_created_at" AS t2_r5, "users"."sign_in_count" AS
    t2_r6, "users"."current_sign_in_at" AS t2_r7, "users"."last_sign_in_at" AS
    t2_r8, "users"."current_sign_in_ip" AS t2_r9, "users"."last_sign_in_ip" AS
    t2_r10, "users"."confirmation_token" AS t2_r11, "users"."confirmed_at" AS
    t2_r12, "users"."confirmation_sent_at" AS t2_r13, "users"."unconfirmed_email"
    AS t2_r14, "users"."failed_attempts" AS t2_r15, "users"."unlock_token" AS
    t2_r16, "users"."locked_at" AS t2_r17, "users"."encrypted_otp_secret" AS
    t2_r18, "users"."encrypted_otp_secret_iv" AS t2_r19,
    "users"."encrypted_otp_secret_salt" AS t2_r20, "users"."consumed_timestep" AS
    t2_r21, "users"."otp_required_for_login" AS t2_r22, "users"."personal_phone"
    AS t2_r23, "users"."name" AS t2_r24, "users"."created_at" AS t2_r25,
    "users"."updated_at" AS t2_r26, "users"."google_token" AS t2_r27,
    "users"."google_refresh_token" AS t2_r28, "users"."last_calendar_refresh" AS
    t2_r29, "users"."google_expires_at" AS t2_r30,
    "users"."google_calendar_refresh_tokens" AS t2_r31, "users"."acquisition" AS
    t2_r32, "users"."icalendar_urls" AS t2_r33, "users"."admin" AS t2_r34,
    "users"."banned" AS t2_r35, "users"."email_subscriber" AS t2_r36,
    "users"."intermediate_reminder_sent" AS t2_r37, "users"."final_reminder_sent"
    AS t2_r38 FROM "coaches" LEFT OUTER JOIN "bookmarks" ON
    "bookmarks"."bookmarkable_id" = "coaches"."id" AND
    "bookmarks"."bookmarkable_type" = $1 LEFT OUTER JOIN "users" ON "users"."id" =
    "coaches"."user_id" WHERE "bookmarks"."bookmarkable_type" = $2 AND
    "bookmarks"."client_id" = 3 [["bookmarkable_type", "Coach"],
    ["bookmarkable_type", "Coach"]]









    share|improve this question


























      up vote
      0
      down vote

      favorite









      up vote
      0
      down vote

      favorite











      I'm trying to figure out the best way to set up an ActiveRecord query.





      Models



      There are three relevant models involved:



      class Client < ActiveRecord::Base
      has_many :bookmarks, dependent: :destroy
      has_many :saved_coaches, through: :bookmarks, source: :bookmarkable, source_type: "Coach"
      end

      class Bookmark < ActiveRecord::Base
      belongs_to :client
      belongs_to :bookmarkable, polymorphic: true

      scope :type, -> (type) { where(bookmarkable_type: type.to_s.capitalize) }
      end

      class Coach < ActiveRecord::Base
      has_many :bookmarks, as: :bookmarkable, dependent: :destroy
      belongs_to :user
      end




      The problem



      I need a performant query to access to the coaches a client has bookmarked, the coach's user model, and the id of relevant bookmark.



      I can think of two approaches:



      @client = current_user.client

      # option 1
      @coach_bookmarks = @client.bookmarks.type(:coach).includes(bookmarkable: :user)

      # option 2
      @coaches = Coach.includes(:bookmarks, :user)
      .where(bookmarks: { bookmarkable_type: "Coach", client: @client })


      Option 1 seems clearer, but produces a collection of bookmarks, when most of the data I need is on the associated coach. Option 2 gives me a collection of coaches, but the query is a little less clear, and the SQL output is huge. (I've included that below.)



      Is one of these solutions better than the other? Is there a third, better solution (maybe through @client.saved_coaches)?





      SQL output



      Option 1



      Bookmark Load (1.3ms)  SELECT "bookmarks".* FROM "bookmarks" WHERE "bookmarks"."client_id" = $1 AND "bookmarks"."bookmarkable_type" = $2  [["client_id", 3], ["bookmarkable_type", "Coach"]]
      Coach Load (2.3ms) SELECT "coaches".* FROM "coaches" WHERE "coaches"."id" IN (1)
      User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (3)


      Option 2



      SQL (6.3ms)  SELECT "coaches"."id" AS t0_r0, "coaches"."user_id" AS t0_r1,
      "coaches"."questionnaire_submitted" AS t0_r2, "coaches"."validated" AS t0_r3,
      "coaches"."verified" AS t0_r4, "coaches"."membership_type" AS t0_r5,
      "coaches"."new_coach" AS t0_r6, "coaches"."created_at" AS t0_r7,
      "coaches"."updated_at" AS t0_r8, "coaches"."title" AS t0_r9,
      "coaches"."image_url" AS t0_r10, "coaches"."experience_years" AS t0_r11,
      "coaches"."specialties" AS t0_r12, "coaches"."session_types" AS t0_r13,
      "coaches"."session_type_other" AS t0_r14, "coaches"."meeting_types" AS t0_r15,
      "coaches"."meeting_type_other" AS t0_r16, "coaches"."cancellation_policy" AS
      t0_r17, "coaches"."bio" AS t0_r18, "coaches"."price_options" AS t0_r19,
      "coaches"."quote" AS t0_r20, "coaches"."current_screen" AS t0_r21,
      "coaches"."address_raw" AS t0_r22, "coaches"."business_phone" AS t0_r23,
      "coaches"."address_city" AS t0_r24, "coaches"."address_state" AS t0_r25,
      "coaches"."address_area" AS t0_r26, "coaches"."latitude" AS t0_r27,
      "coaches"."longitude" AS t0_r28, "coaches"."categories" AS t0_r29,
      "coaches"."keywords" AS t0_r30, "coaches"."ace_up_handles" AS t0_r31,
      "coaches"."stripe_account_id" AS t0_r32, "coaches"."bank_account_info" AS
      t0_r33, "coaches"."offer_free_consult" AS t0_r34,
      "coaches"."free_consult_length" AS t0_r35, "coaches"."sessions_start_every" AS
      t0_r36, "coaches"."max_daily_sessions" AS t0_r37,
      "coaches"."minimum_scheduling_notice" AS t0_r38,
      "coaches"."buffer_after_session" AS t0_r39, "coaches"."num_reviews" AS t0_r40,
      "coaches"."onboard_professional_background" AS t0_r41,
      "coaches"."onboard_certifications" AS t0_r42, "coaches"."onboard_challenges"
      AS t0_r43, "coaches"."onboard_promoting" AS t0_r44,
      "coaches"."onboard_referral_source" AS t0_r45, "coaches"."onboard_interests"
      AS t0_r46, "coaches"."onboard_submitted" AS t0_r47, "coaches"."coach_url" AS
      t0_r48, "coaches"."timezone" AS t0_r49, "coaches"."average_review" AS t0_r50,
      "coaches"."coach_order" AS t0_r51, "coaches"."onboard_business" AS t0_r52,
      "coaches"."onboard_services" AS t0_r53, "coaches"."videos" AS t0_r54,
      "coaches"."card_highlights" AS t0_r55, "coaches"."edit_requested" AS t0_r56,
      "coaches"."specialties_edit" AS t0_r57, "coaches"."session_types_edit" AS
      t0_r58, "coaches"."meeting_types_edit" AS t0_r59,
      "coaches"."cancellation_policy_edit" AS t0_r60, "coaches"."bio_edit" AS
      t0_r61, "coaches"."quote_edit" AS t0_r62, "coaches"."keywords_career" AS
      t0_r63, "coaches"."keywords_life" AS t0_r64, "coaches"."keywords_leadership"
      AS t0_r65, "coaches"."keywords_business" AS t0_r66,
      "coaches"."keywords_communication" AS t0_r67, "coaches"."keywords_executive"
      AS t0_r68, "coaches"."qualifications" AS t0_r69, "coaches"."education" AS
      t0_r70, "coaches"."target_clients" AS t0_r71, "coaches"."highlights" AS
      t0_r72, "coaches"."client_review_link" AS t0_r73, "coaches"."consent" AS
      t0_r74, "coaches"."qualifications_edit" AS t0_r75, "coaches"."education_edit"
      AS t0_r76, "coaches"."target_clients_edit" AS t0_r77,
      "coaches"."highlights_edit" AS t0_r78, "coaches"."text_notifications" AS
      t0_r79, "coaches"."email_notifications" AS t0_r80, "coaches"."companies" AS
      t0_r81, "coaches"."address_raw_edit" AS t0_r82, "coaches"."longitude_edit" AS
      t0_r83, "coaches"."latitude_edit" AS t0_r84, "bookmarks"."id" AS t1_r0,
      "bookmarks"."client_id" AS t1_r1, "bookmarks"."bookmarkable_id" AS t1_r2,
      "bookmarks"."bookmarkable_type" AS t1_r3, "users"."id" AS t2_r0,
      "users"."email" AS t2_r1, "users"."encrypted_password" AS t2_r2,
      "users"."reset_password_token" AS t2_r3, "users"."reset_password_sent_at" AS
      t2_r4, "users"."remember_created_at" AS t2_r5, "users"."sign_in_count" AS
      t2_r6, "users"."current_sign_in_at" AS t2_r7, "users"."last_sign_in_at" AS
      t2_r8, "users"."current_sign_in_ip" AS t2_r9, "users"."last_sign_in_ip" AS
      t2_r10, "users"."confirmation_token" AS t2_r11, "users"."confirmed_at" AS
      t2_r12, "users"."confirmation_sent_at" AS t2_r13, "users"."unconfirmed_email"
      AS t2_r14, "users"."failed_attempts" AS t2_r15, "users"."unlock_token" AS
      t2_r16, "users"."locked_at" AS t2_r17, "users"."encrypted_otp_secret" AS
      t2_r18, "users"."encrypted_otp_secret_iv" AS t2_r19,
      "users"."encrypted_otp_secret_salt" AS t2_r20, "users"."consumed_timestep" AS
      t2_r21, "users"."otp_required_for_login" AS t2_r22, "users"."personal_phone"
      AS t2_r23, "users"."name" AS t2_r24, "users"."created_at" AS t2_r25,
      "users"."updated_at" AS t2_r26, "users"."google_token" AS t2_r27,
      "users"."google_refresh_token" AS t2_r28, "users"."last_calendar_refresh" AS
      t2_r29, "users"."google_expires_at" AS t2_r30,
      "users"."google_calendar_refresh_tokens" AS t2_r31, "users"."acquisition" AS
      t2_r32, "users"."icalendar_urls" AS t2_r33, "users"."admin" AS t2_r34,
      "users"."banned" AS t2_r35, "users"."email_subscriber" AS t2_r36,
      "users"."intermediate_reminder_sent" AS t2_r37, "users"."final_reminder_sent"
      AS t2_r38 FROM "coaches" LEFT OUTER JOIN "bookmarks" ON
      "bookmarks"."bookmarkable_id" = "coaches"."id" AND
      "bookmarks"."bookmarkable_type" = $1 LEFT OUTER JOIN "users" ON "users"."id" =
      "coaches"."user_id" WHERE "bookmarks"."bookmarkable_type" = $2 AND
      "bookmarks"."client_id" = 3 [["bookmarkable_type", "Coach"],
      ["bookmarkable_type", "Coach"]]









      share|improve this question















      I'm trying to figure out the best way to set up an ActiveRecord query.





      Models



      There are three relevant models involved:



      class Client < ActiveRecord::Base
      has_many :bookmarks, dependent: :destroy
      has_many :saved_coaches, through: :bookmarks, source: :bookmarkable, source_type: "Coach"
      end

      class Bookmark < ActiveRecord::Base
      belongs_to :client
      belongs_to :bookmarkable, polymorphic: true

      scope :type, -> (type) { where(bookmarkable_type: type.to_s.capitalize) }
      end

      class Coach < ActiveRecord::Base
      has_many :bookmarks, as: :bookmarkable, dependent: :destroy
      belongs_to :user
      end




      The problem



      I need a performant query to access to the coaches a client has bookmarked, the coach's user model, and the id of relevant bookmark.



      I can think of two approaches:



      @client = current_user.client

      # option 1
      @coach_bookmarks = @client.bookmarks.type(:coach).includes(bookmarkable: :user)

      # option 2
      @coaches = Coach.includes(:bookmarks, :user)
      .where(bookmarks: { bookmarkable_type: "Coach", client: @client })


      Option 1 seems clearer, but produces a collection of bookmarks, when most of the data I need is on the associated coach. Option 2 gives me a collection of coaches, but the query is a little less clear, and the SQL output is huge. (I've included that below.)



      Is one of these solutions better than the other? Is there a third, better solution (maybe through @client.saved_coaches)?





      SQL output



      Option 1



      Bookmark Load (1.3ms)  SELECT "bookmarks".* FROM "bookmarks" WHERE "bookmarks"."client_id" = $1 AND "bookmarks"."bookmarkable_type" = $2  [["client_id", 3], ["bookmarkable_type", "Coach"]]
      Coach Load (2.3ms) SELECT "coaches".* FROM "coaches" WHERE "coaches"."id" IN (1)
      User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (3)


      Option 2



      SQL (6.3ms)  SELECT "coaches"."id" AS t0_r0, "coaches"."user_id" AS t0_r1,
      "coaches"."questionnaire_submitted" AS t0_r2, "coaches"."validated" AS t0_r3,
      "coaches"."verified" AS t0_r4, "coaches"."membership_type" AS t0_r5,
      "coaches"."new_coach" AS t0_r6, "coaches"."created_at" AS t0_r7,
      "coaches"."updated_at" AS t0_r8, "coaches"."title" AS t0_r9,
      "coaches"."image_url" AS t0_r10, "coaches"."experience_years" AS t0_r11,
      "coaches"."specialties" AS t0_r12, "coaches"."session_types" AS t0_r13,
      "coaches"."session_type_other" AS t0_r14, "coaches"."meeting_types" AS t0_r15,
      "coaches"."meeting_type_other" AS t0_r16, "coaches"."cancellation_policy" AS
      t0_r17, "coaches"."bio" AS t0_r18, "coaches"."price_options" AS t0_r19,
      "coaches"."quote" AS t0_r20, "coaches"."current_screen" AS t0_r21,
      "coaches"."address_raw" AS t0_r22, "coaches"."business_phone" AS t0_r23,
      "coaches"."address_city" AS t0_r24, "coaches"."address_state" AS t0_r25,
      "coaches"."address_area" AS t0_r26, "coaches"."latitude" AS t0_r27,
      "coaches"."longitude" AS t0_r28, "coaches"."categories" AS t0_r29,
      "coaches"."keywords" AS t0_r30, "coaches"."ace_up_handles" AS t0_r31,
      "coaches"."stripe_account_id" AS t0_r32, "coaches"."bank_account_info" AS
      t0_r33, "coaches"."offer_free_consult" AS t0_r34,
      "coaches"."free_consult_length" AS t0_r35, "coaches"."sessions_start_every" AS
      t0_r36, "coaches"."max_daily_sessions" AS t0_r37,
      "coaches"."minimum_scheduling_notice" AS t0_r38,
      "coaches"."buffer_after_session" AS t0_r39, "coaches"."num_reviews" AS t0_r40,
      "coaches"."onboard_professional_background" AS t0_r41,
      "coaches"."onboard_certifications" AS t0_r42, "coaches"."onboard_challenges"
      AS t0_r43, "coaches"."onboard_promoting" AS t0_r44,
      "coaches"."onboard_referral_source" AS t0_r45, "coaches"."onboard_interests"
      AS t0_r46, "coaches"."onboard_submitted" AS t0_r47, "coaches"."coach_url" AS
      t0_r48, "coaches"."timezone" AS t0_r49, "coaches"."average_review" AS t0_r50,
      "coaches"."coach_order" AS t0_r51, "coaches"."onboard_business" AS t0_r52,
      "coaches"."onboard_services" AS t0_r53, "coaches"."videos" AS t0_r54,
      "coaches"."card_highlights" AS t0_r55, "coaches"."edit_requested" AS t0_r56,
      "coaches"."specialties_edit" AS t0_r57, "coaches"."session_types_edit" AS
      t0_r58, "coaches"."meeting_types_edit" AS t0_r59,
      "coaches"."cancellation_policy_edit" AS t0_r60, "coaches"."bio_edit" AS
      t0_r61, "coaches"."quote_edit" AS t0_r62, "coaches"."keywords_career" AS
      t0_r63, "coaches"."keywords_life" AS t0_r64, "coaches"."keywords_leadership"
      AS t0_r65, "coaches"."keywords_business" AS t0_r66,
      "coaches"."keywords_communication" AS t0_r67, "coaches"."keywords_executive"
      AS t0_r68, "coaches"."qualifications" AS t0_r69, "coaches"."education" AS
      t0_r70, "coaches"."target_clients" AS t0_r71, "coaches"."highlights" AS
      t0_r72, "coaches"."client_review_link" AS t0_r73, "coaches"."consent" AS
      t0_r74, "coaches"."qualifications_edit" AS t0_r75, "coaches"."education_edit"
      AS t0_r76, "coaches"."target_clients_edit" AS t0_r77,
      "coaches"."highlights_edit" AS t0_r78, "coaches"."text_notifications" AS
      t0_r79, "coaches"."email_notifications" AS t0_r80, "coaches"."companies" AS
      t0_r81, "coaches"."address_raw_edit" AS t0_r82, "coaches"."longitude_edit" AS
      t0_r83, "coaches"."latitude_edit" AS t0_r84, "bookmarks"."id" AS t1_r0,
      "bookmarks"."client_id" AS t1_r1, "bookmarks"."bookmarkable_id" AS t1_r2,
      "bookmarks"."bookmarkable_type" AS t1_r3, "users"."id" AS t2_r0,
      "users"."email" AS t2_r1, "users"."encrypted_password" AS t2_r2,
      "users"."reset_password_token" AS t2_r3, "users"."reset_password_sent_at" AS
      t2_r4, "users"."remember_created_at" AS t2_r5, "users"."sign_in_count" AS
      t2_r6, "users"."current_sign_in_at" AS t2_r7, "users"."last_sign_in_at" AS
      t2_r8, "users"."current_sign_in_ip" AS t2_r9, "users"."last_sign_in_ip" AS
      t2_r10, "users"."confirmation_token" AS t2_r11, "users"."confirmed_at" AS
      t2_r12, "users"."confirmation_sent_at" AS t2_r13, "users"."unconfirmed_email"
      AS t2_r14, "users"."failed_attempts" AS t2_r15, "users"."unlock_token" AS
      t2_r16, "users"."locked_at" AS t2_r17, "users"."encrypted_otp_secret" AS
      t2_r18, "users"."encrypted_otp_secret_iv" AS t2_r19,
      "users"."encrypted_otp_secret_salt" AS t2_r20, "users"."consumed_timestep" AS
      t2_r21, "users"."otp_required_for_login" AS t2_r22, "users"."personal_phone"
      AS t2_r23, "users"."name" AS t2_r24, "users"."created_at" AS t2_r25,
      "users"."updated_at" AS t2_r26, "users"."google_token" AS t2_r27,
      "users"."google_refresh_token" AS t2_r28, "users"."last_calendar_refresh" AS
      t2_r29, "users"."google_expires_at" AS t2_r30,
      "users"."google_calendar_refresh_tokens" AS t2_r31, "users"."acquisition" AS
      t2_r32, "users"."icalendar_urls" AS t2_r33, "users"."admin" AS t2_r34,
      "users"."banned" AS t2_r35, "users"."email_subscriber" AS t2_r36,
      "users"."intermediate_reminder_sent" AS t2_r37, "users"."final_reminder_sent"
      AS t2_r38 FROM "coaches" LEFT OUTER JOIN "bookmarks" ON
      "bookmarks"."bookmarkable_id" = "coaches"."id" AND
      "bookmarks"."bookmarkable_type" = $1 LEFT OUTER JOIN "users" ON "users"."id" =
      "coaches"."user_id" WHERE "bookmarks"."bookmarkable_type" = $2 AND
      "bookmarks"."client_id" = 3 [["bookmarkable_type", "Coach"],
      ["bookmarkable_type", "Coach"]]






      performance ruby ruby-on-rails comparative-review active-record






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Aug 23 '17 at 0:14









      200_success

      127k15148411




      127k15148411










      asked Aug 22 '17 at 22:39









      Phillip Longman

      1113




      1113






















          2 Answers
          2






          active

          oldest

          votes

















          up vote
          2
          down vote













          if you don't need the bookmarks instances then I would be inclined to go for something like:



          @coaches = Coach
          .joins(:bookmarks).includes(:user)
          .where(bookmarks: { bookmarkable_type: "Coach", client: @client })


          though I would wrap that in a scope. You could probably also get away with excluding the bookmarkable_type: "Coach" condition since the join will enforce that.






          share|improve this answer

















          • 1




            Unfortunately, I need their IDs. I didn't realize I could drop the "bookmarkable_type," though, and wrapping it into a scope was a good idea. I created a Bookmarkable concern to hold the has_many :bookmarks association and a new bookmarked_by scope.
            – Phillip Longman
            Aug 24 '17 at 19:49


















          up vote
          0
          down vote













          @coaches = Coach
          .includes(:user)
          .joins(:bookmarks)
          .where(bookmarks: { bookmarkable_type: "Coach", client: @client })
          .select("coaches.*, bookmarks.id AS bookmark_id")


          After this query you can do the following:



          @coaches.first.id # returns coach id
          @coaches.first.bookmark_id # returns the relevant bookmark_id
          @coaches.first.user.name # returns the user's name of that coach.


          This approach has one advantage over including the collection of bookmarks that it won't load the whole objects of bookmarks into memory when you need only bookmark_id.






          share|improve this answer








          New contributor




          KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
          Check out our Code of Conduct.


















            Your Answer





            StackExchange.ifUsing("editor", function () {
            return StackExchange.using("mathjaxEditing", function () {
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            });
            });
            }, "mathjax-editing");

            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "196"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














             

            draft saved


            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f173712%2factiverecord-query-for-coaches-a-client-has-bookmarked%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            2
            down vote













            if you don't need the bookmarks instances then I would be inclined to go for something like:



            @coaches = Coach
            .joins(:bookmarks).includes(:user)
            .where(bookmarks: { bookmarkable_type: "Coach", client: @client })


            though I would wrap that in a scope. You could probably also get away with excluding the bookmarkable_type: "Coach" condition since the join will enforce that.






            share|improve this answer

















            • 1




              Unfortunately, I need their IDs. I didn't realize I could drop the "bookmarkable_type," though, and wrapping it into a scope was a good idea. I created a Bookmarkable concern to hold the has_many :bookmarks association and a new bookmarked_by scope.
              – Phillip Longman
              Aug 24 '17 at 19:49















            up vote
            2
            down vote













            if you don't need the bookmarks instances then I would be inclined to go for something like:



            @coaches = Coach
            .joins(:bookmarks).includes(:user)
            .where(bookmarks: { bookmarkable_type: "Coach", client: @client })


            though I would wrap that in a scope. You could probably also get away with excluding the bookmarkable_type: "Coach" condition since the join will enforce that.






            share|improve this answer

















            • 1




              Unfortunately, I need their IDs. I didn't realize I could drop the "bookmarkable_type," though, and wrapping it into a scope was a good idea. I created a Bookmarkable concern to hold the has_many :bookmarks association and a new bookmarked_by scope.
              – Phillip Longman
              Aug 24 '17 at 19:49













            up vote
            2
            down vote










            up vote
            2
            down vote









            if you don't need the bookmarks instances then I would be inclined to go for something like:



            @coaches = Coach
            .joins(:bookmarks).includes(:user)
            .where(bookmarks: { bookmarkable_type: "Coach", client: @client })


            though I would wrap that in a scope. You could probably also get away with excluding the bookmarkable_type: "Coach" condition since the join will enforce that.






            share|improve this answer












            if you don't need the bookmarks instances then I would be inclined to go for something like:



            @coaches = Coach
            .joins(:bookmarks).includes(:user)
            .where(bookmarks: { bookmarkable_type: "Coach", client: @client })


            though I would wrap that in a scope. You could probably also get away with excluding the bookmarkable_type: "Coach" condition since the join will enforce that.







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Aug 23 '17 at 14:35









            Marc Rohloff

            2,93236




            2,93236








            • 1




              Unfortunately, I need their IDs. I didn't realize I could drop the "bookmarkable_type," though, and wrapping it into a scope was a good idea. I created a Bookmarkable concern to hold the has_many :bookmarks association and a new bookmarked_by scope.
              – Phillip Longman
              Aug 24 '17 at 19:49














            • 1




              Unfortunately, I need their IDs. I didn't realize I could drop the "bookmarkable_type," though, and wrapping it into a scope was a good idea. I created a Bookmarkable concern to hold the has_many :bookmarks association and a new bookmarked_by scope.
              – Phillip Longman
              Aug 24 '17 at 19:49








            1




            1




            Unfortunately, I need their IDs. I didn't realize I could drop the "bookmarkable_type," though, and wrapping it into a scope was a good idea. I created a Bookmarkable concern to hold the has_many :bookmarks association and a new bookmarked_by scope.
            – Phillip Longman
            Aug 24 '17 at 19:49




            Unfortunately, I need their IDs. I didn't realize I could drop the "bookmarkable_type," though, and wrapping it into a scope was a good idea. I created a Bookmarkable concern to hold the has_many :bookmarks association and a new bookmarked_by scope.
            – Phillip Longman
            Aug 24 '17 at 19:49












            up vote
            0
            down vote













            @coaches = Coach
            .includes(:user)
            .joins(:bookmarks)
            .where(bookmarks: { bookmarkable_type: "Coach", client: @client })
            .select("coaches.*, bookmarks.id AS bookmark_id")


            After this query you can do the following:



            @coaches.first.id # returns coach id
            @coaches.first.bookmark_id # returns the relevant bookmark_id
            @coaches.first.user.name # returns the user's name of that coach.


            This approach has one advantage over including the collection of bookmarks that it won't load the whole objects of bookmarks into memory when you need only bookmark_id.






            share|improve this answer








            New contributor




            KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
            Check out our Code of Conduct.






















              up vote
              0
              down vote













              @coaches = Coach
              .includes(:user)
              .joins(:bookmarks)
              .where(bookmarks: { bookmarkable_type: "Coach", client: @client })
              .select("coaches.*, bookmarks.id AS bookmark_id")


              After this query you can do the following:



              @coaches.first.id # returns coach id
              @coaches.first.bookmark_id # returns the relevant bookmark_id
              @coaches.first.user.name # returns the user's name of that coach.


              This approach has one advantage over including the collection of bookmarks that it won't load the whole objects of bookmarks into memory when you need only bookmark_id.






              share|improve this answer








              New contributor




              KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
              Check out our Code of Conduct.




















                up vote
                0
                down vote










                up vote
                0
                down vote









                @coaches = Coach
                .includes(:user)
                .joins(:bookmarks)
                .where(bookmarks: { bookmarkable_type: "Coach", client: @client })
                .select("coaches.*, bookmarks.id AS bookmark_id")


                After this query you can do the following:



                @coaches.first.id # returns coach id
                @coaches.first.bookmark_id # returns the relevant bookmark_id
                @coaches.first.user.name # returns the user's name of that coach.


                This approach has one advantage over including the collection of bookmarks that it won't load the whole objects of bookmarks into memory when you need only bookmark_id.






                share|improve this answer








                New contributor




                KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.









                @coaches = Coach
                .includes(:user)
                .joins(:bookmarks)
                .where(bookmarks: { bookmarkable_type: "Coach", client: @client })
                .select("coaches.*, bookmarks.id AS bookmark_id")


                After this query you can do the following:



                @coaches.first.id # returns coach id
                @coaches.first.bookmark_id # returns the relevant bookmark_id
                @coaches.first.user.name # returns the user's name of that coach.


                This approach has one advantage over including the collection of bookmarks that it won't load the whole objects of bookmarks into memory when you need only bookmark_id.







                share|improve this answer








                New contributor




                KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.









                share|improve this answer



                share|improve this answer






                New contributor




                KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.









                answered 20 hours ago









                KULKING

                1214




                1214




                New contributor




                KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.





                New contributor





                KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.






                KULKING is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.






























                     

                    draft saved


                    draft discarded



















































                     


                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f173712%2factiverecord-query-for-coaches-a-client-has-bookmarked%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Costa Masnaga

                    Fotorealismo

                    Sidney Franklin