merging two relations in Rails











up vote
0
down vote

favorite












This should be easy I think...



A Organisation has an Owner and Members - both are Users.



This is setup by the organisation using class_name: "User", like so:



class Organisation < ApplicationRecord
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
has_many :organisations_users
has_many :members, class_name: "User", through: :organisations_users, source: :user
end


This works but I need an all_members function (or scope) so I can get back both owner and members in one array (or ActiveRecord object). I thought this would be trivial but it's actually not.



I've tried:



def all_members
members << owner
end


this of course is not what I want at all... this adds the owner to the staff every time I call it.



def all_members
[owner, members]
end


this sort of works but returns a nested array which is hard to access properly.



  scope :all_members, joins(:members).merge(:owner)


this doesn't work at all. Probably nonsense.



  def all_members
members_array = members.dup
members_array << owner
end


This still permanently alters the members to include the owner?!!



Help! (Thanks)










share|improve this question


















  • 1




    You can write your method as [*members, owner] by using splat operator (*). It would be best to have a relation that will return all members with one query though.
    – Marcin Kołodziej
    Nov 7 at 15:38










  • hey, that worked! I think that might be the smallest change ever required on Stack Overflow
    – Paul Harker
    Nov 7 at 15:44






  • 1




    It still does 2 selects, which I'd aim to optimize. If you add your relations between organisation_users and members to the question, you should be able to get an acceptable answer.
    – Marcin Kołodziej
    Nov 7 at 15:49










  • concat (<<) permanently alters an object try using + instead
    – engineerDave
    Nov 7 at 22:04















up vote
0
down vote

favorite












This should be easy I think...



A Organisation has an Owner and Members - both are Users.



This is setup by the organisation using class_name: "User", like so:



class Organisation < ApplicationRecord
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
has_many :organisations_users
has_many :members, class_name: "User", through: :organisations_users, source: :user
end


This works but I need an all_members function (or scope) so I can get back both owner and members in one array (or ActiveRecord object). I thought this would be trivial but it's actually not.



I've tried:



def all_members
members << owner
end


this of course is not what I want at all... this adds the owner to the staff every time I call it.



def all_members
[owner, members]
end


this sort of works but returns a nested array which is hard to access properly.



  scope :all_members, joins(:members).merge(:owner)


this doesn't work at all. Probably nonsense.



  def all_members
members_array = members.dup
members_array << owner
end


This still permanently alters the members to include the owner?!!



Help! (Thanks)










share|improve this question


















  • 1




    You can write your method as [*members, owner] by using splat operator (*). It would be best to have a relation that will return all members with one query though.
    – Marcin Kołodziej
    Nov 7 at 15:38










  • hey, that worked! I think that might be the smallest change ever required on Stack Overflow
    – Paul Harker
    Nov 7 at 15:44






  • 1




    It still does 2 selects, which I'd aim to optimize. If you add your relations between organisation_users and members to the question, you should be able to get an acceptable answer.
    – Marcin Kołodziej
    Nov 7 at 15:49










  • concat (<<) permanently alters an object try using + instead
    – engineerDave
    Nov 7 at 22:04













up vote
0
down vote

favorite









up vote
0
down vote

favorite











This should be easy I think...



A Organisation has an Owner and Members - both are Users.



This is setup by the organisation using class_name: "User", like so:



class Organisation < ApplicationRecord
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
has_many :organisations_users
has_many :members, class_name: "User", through: :organisations_users, source: :user
end


This works but I need an all_members function (or scope) so I can get back both owner and members in one array (or ActiveRecord object). I thought this would be trivial but it's actually not.



I've tried:



def all_members
members << owner
end


this of course is not what I want at all... this adds the owner to the staff every time I call it.



def all_members
[owner, members]
end


this sort of works but returns a nested array which is hard to access properly.



  scope :all_members, joins(:members).merge(:owner)


this doesn't work at all. Probably nonsense.



  def all_members
members_array = members.dup
members_array << owner
end


This still permanently alters the members to include the owner?!!



Help! (Thanks)










share|improve this question













This should be easy I think...



A Organisation has an Owner and Members - both are Users.



This is setup by the organisation using class_name: "User", like so:



class Organisation < ApplicationRecord
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
has_many :organisations_users
has_many :members, class_name: "User", through: :organisations_users, source: :user
end


This works but I need an all_members function (or scope) so I can get back both owner and members in one array (or ActiveRecord object). I thought this would be trivial but it's actually not.



I've tried:



def all_members
members << owner
end


this of course is not what I want at all... this adds the owner to the staff every time I call it.



def all_members
[owner, members]
end


this sort of works but returns a nested array which is hard to access properly.



  scope :all_members, joins(:members).merge(:owner)


this doesn't work at all. Probably nonsense.



  def all_members
members_array = members.dup
members_array << owner
end


This still permanently alters the members to include the owner?!!



Help! (Thanks)







ruby-on-rails ruby activerecord






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 7 at 15:30









Paul Harker

548




548








  • 1




    You can write your method as [*members, owner] by using splat operator (*). It would be best to have a relation that will return all members with one query though.
    – Marcin Kołodziej
    Nov 7 at 15:38










  • hey, that worked! I think that might be the smallest change ever required on Stack Overflow
    – Paul Harker
    Nov 7 at 15:44






  • 1




    It still does 2 selects, which I'd aim to optimize. If you add your relations between organisation_users and members to the question, you should be able to get an acceptable answer.
    – Marcin Kołodziej
    Nov 7 at 15:49










  • concat (<<) permanently alters an object try using + instead
    – engineerDave
    Nov 7 at 22:04














  • 1




    You can write your method as [*members, owner] by using splat operator (*). It would be best to have a relation that will return all members with one query though.
    – Marcin Kołodziej
    Nov 7 at 15:38










  • hey, that worked! I think that might be the smallest change ever required on Stack Overflow
    – Paul Harker
    Nov 7 at 15:44






  • 1




    It still does 2 selects, which I'd aim to optimize. If you add your relations between organisation_users and members to the question, you should be able to get an acceptable answer.
    – Marcin Kołodziej
    Nov 7 at 15:49










  • concat (<<) permanently alters an object try using + instead
    – engineerDave
    Nov 7 at 22:04








1




1




You can write your method as [*members, owner] by using splat operator (*). It would be best to have a relation that will return all members with one query though.
– Marcin Kołodziej
Nov 7 at 15:38




You can write your method as [*members, owner] by using splat operator (*). It would be best to have a relation that will return all members with one query though.
– Marcin Kołodziej
Nov 7 at 15:38












hey, that worked! I think that might be the smallest change ever required on Stack Overflow
– Paul Harker
Nov 7 at 15:44




hey, that worked! I think that might be the smallest change ever required on Stack Overflow
– Paul Harker
Nov 7 at 15:44




1




1




It still does 2 selects, which I'd aim to optimize. If you add your relations between organisation_users and members to the question, you should be able to get an acceptable answer.
– Marcin Kołodziej
Nov 7 at 15:49




It still does 2 selects, which I'd aim to optimize. If you add your relations between organisation_users and members to the question, you should be able to get an acceptable answer.
– Marcin Kołodziej
Nov 7 at 15:49












concat (<<) permanently alters an object try using + instead
– engineerDave
Nov 7 at 22:04




concat (<<) permanently alters an object try using + instead
– engineerDave
Nov 7 at 22:04












2 Answers
2






active

oldest

votes

















up vote
1
down vote



accepted










If the order is not necessarily important then we can get this into a single query like so:



If Rails 5



def all_members
# not sure if you could just do
# owner.or(members) since they are both User
# but it seems unlikely due to the join table
User.where(id: self.owner_id).or(
User.where(id: organisation_users.select(:user_id))
)
end


All versions of Rails (less than and including Rails 5) we can use Arel to build the more complex query



def all_members
user_table = User.arel_table
User.where(
users_table[:id].eq(self.owner_id).or(
users_table[:id].in(
organisation_users.select(:user_id).arel
)
)
)
end


these will both result in the following SQL



SELECT users.* 
FROM users
WHERE
(users.id = [YOUR_OWNER_ID] OR
users.id IN (
SELECT organisations_users.user_id
FROM organisations_users
WHERE organisations_users.organisation_id = [YOUR_ORGANISATION_ID]
))


the end result will be an ActiveRecord::Relation of User objects.






share|improve this answer






























    up vote
    1
    down vote













    If getting an Array back is sufficient, then you can simply use:



    def all_members
    [owner] + members
    end


    If you need a relation, then the simplest (but not most efficient) approach would be:



    def all_members
    User.where(id: [owner] + members.ids)
    end


    It's not the most efficient as the SQL that gets generated may include a fairly large IN statement that cannot be efficiently cached by the database parser. Still, if the numbers are in the range of a few dozens, it would be enough even that relation trick.






    share|improve this answer























    • The second case should be User.where not just where and while I haven't tested this theory I might assume that this should be [owner.id] + members.ids which would now result in 3 queries (fetch owner, fetch member ids, fetch Users where.)
      – engineersmnky
      Nov 7 at 21:28












    • Correct, it will result in 3 queries. Reason why I mentioned it's not extremely efficient.
      – Simone Carletti
      Nov 8 at 22:23











    Your Answer






    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: "1"
    };
    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: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    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%2fstackoverflow.com%2fquestions%2f53192611%2fmerging-two-relations-in-rails%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
    1
    down vote



    accepted










    If the order is not necessarily important then we can get this into a single query like so:



    If Rails 5



    def all_members
    # not sure if you could just do
    # owner.or(members) since they are both User
    # but it seems unlikely due to the join table
    User.where(id: self.owner_id).or(
    User.where(id: organisation_users.select(:user_id))
    )
    end


    All versions of Rails (less than and including Rails 5) we can use Arel to build the more complex query



    def all_members
    user_table = User.arel_table
    User.where(
    users_table[:id].eq(self.owner_id).or(
    users_table[:id].in(
    organisation_users.select(:user_id).arel
    )
    )
    )
    end


    these will both result in the following SQL



    SELECT users.* 
    FROM users
    WHERE
    (users.id = [YOUR_OWNER_ID] OR
    users.id IN (
    SELECT organisations_users.user_id
    FROM organisations_users
    WHERE organisations_users.organisation_id = [YOUR_ORGANISATION_ID]
    ))


    the end result will be an ActiveRecord::Relation of User objects.






    share|improve this answer



























      up vote
      1
      down vote



      accepted










      If the order is not necessarily important then we can get this into a single query like so:



      If Rails 5



      def all_members
      # not sure if you could just do
      # owner.or(members) since they are both User
      # but it seems unlikely due to the join table
      User.where(id: self.owner_id).or(
      User.where(id: organisation_users.select(:user_id))
      )
      end


      All versions of Rails (less than and including Rails 5) we can use Arel to build the more complex query



      def all_members
      user_table = User.arel_table
      User.where(
      users_table[:id].eq(self.owner_id).or(
      users_table[:id].in(
      organisation_users.select(:user_id).arel
      )
      )
      )
      end


      these will both result in the following SQL



      SELECT users.* 
      FROM users
      WHERE
      (users.id = [YOUR_OWNER_ID] OR
      users.id IN (
      SELECT organisations_users.user_id
      FROM organisations_users
      WHERE organisations_users.organisation_id = [YOUR_ORGANISATION_ID]
      ))


      the end result will be an ActiveRecord::Relation of User objects.






      share|improve this answer

























        up vote
        1
        down vote



        accepted







        up vote
        1
        down vote



        accepted






        If the order is not necessarily important then we can get this into a single query like so:



        If Rails 5



        def all_members
        # not sure if you could just do
        # owner.or(members) since they are both User
        # but it seems unlikely due to the join table
        User.where(id: self.owner_id).or(
        User.where(id: organisation_users.select(:user_id))
        )
        end


        All versions of Rails (less than and including Rails 5) we can use Arel to build the more complex query



        def all_members
        user_table = User.arel_table
        User.where(
        users_table[:id].eq(self.owner_id).or(
        users_table[:id].in(
        organisation_users.select(:user_id).arel
        )
        )
        )
        end


        these will both result in the following SQL



        SELECT users.* 
        FROM users
        WHERE
        (users.id = [YOUR_OWNER_ID] OR
        users.id IN (
        SELECT organisations_users.user_id
        FROM organisations_users
        WHERE organisations_users.organisation_id = [YOUR_ORGANISATION_ID]
        ))


        the end result will be an ActiveRecord::Relation of User objects.






        share|improve this answer














        If the order is not necessarily important then we can get this into a single query like so:



        If Rails 5



        def all_members
        # not sure if you could just do
        # owner.or(members) since they are both User
        # but it seems unlikely due to the join table
        User.where(id: self.owner_id).or(
        User.where(id: organisation_users.select(:user_id))
        )
        end


        All versions of Rails (less than and including Rails 5) we can use Arel to build the more complex query



        def all_members
        user_table = User.arel_table
        User.where(
        users_table[:id].eq(self.owner_id).or(
        users_table[:id].in(
        organisation_users.select(:user_id).arel
        )
        )
        )
        end


        these will both result in the following SQL



        SELECT users.* 
        FROM users
        WHERE
        (users.id = [YOUR_OWNER_ID] OR
        users.id IN (
        SELECT organisations_users.user_id
        FROM organisations_users
        WHERE organisations_users.organisation_id = [YOUR_ORGANISATION_ID]
        ))


        the end result will be an ActiveRecord::Relation of User objects.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 7 at 19:27

























        answered Nov 7 at 19:17









        engineersmnky

        12.8k12037




        12.8k12037
























            up vote
            1
            down vote













            If getting an Array back is sufficient, then you can simply use:



            def all_members
            [owner] + members
            end


            If you need a relation, then the simplest (but not most efficient) approach would be:



            def all_members
            User.where(id: [owner] + members.ids)
            end


            It's not the most efficient as the SQL that gets generated may include a fairly large IN statement that cannot be efficiently cached by the database parser. Still, if the numbers are in the range of a few dozens, it would be enough even that relation trick.






            share|improve this answer























            • The second case should be User.where not just where and while I haven't tested this theory I might assume that this should be [owner.id] + members.ids which would now result in 3 queries (fetch owner, fetch member ids, fetch Users where.)
              – engineersmnky
              Nov 7 at 21:28












            • Correct, it will result in 3 queries. Reason why I mentioned it's not extremely efficient.
              – Simone Carletti
              Nov 8 at 22:23















            up vote
            1
            down vote













            If getting an Array back is sufficient, then you can simply use:



            def all_members
            [owner] + members
            end


            If you need a relation, then the simplest (but not most efficient) approach would be:



            def all_members
            User.where(id: [owner] + members.ids)
            end


            It's not the most efficient as the SQL that gets generated may include a fairly large IN statement that cannot be efficiently cached by the database parser. Still, if the numbers are in the range of a few dozens, it would be enough even that relation trick.






            share|improve this answer























            • The second case should be User.where not just where and while I haven't tested this theory I might assume that this should be [owner.id] + members.ids which would now result in 3 queries (fetch owner, fetch member ids, fetch Users where.)
              – engineersmnky
              Nov 7 at 21:28












            • Correct, it will result in 3 queries. Reason why I mentioned it's not extremely efficient.
              – Simone Carletti
              Nov 8 at 22:23













            up vote
            1
            down vote










            up vote
            1
            down vote









            If getting an Array back is sufficient, then you can simply use:



            def all_members
            [owner] + members
            end


            If you need a relation, then the simplest (but not most efficient) approach would be:



            def all_members
            User.where(id: [owner] + members.ids)
            end


            It's not the most efficient as the SQL that gets generated may include a fairly large IN statement that cannot be efficiently cached by the database parser. Still, if the numbers are in the range of a few dozens, it would be enough even that relation trick.






            share|improve this answer














            If getting an Array back is sufficient, then you can simply use:



            def all_members
            [owner] + members
            end


            If you need a relation, then the simplest (but not most efficient) approach would be:



            def all_members
            User.where(id: [owner] + members.ids)
            end


            It's not the most efficient as the SQL that gets generated may include a fairly large IN statement that cannot be efficiently cached by the database parser. Still, if the numbers are in the range of a few dozens, it would be enough even that relation trick.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Nov 8 at 22:22

























            answered Nov 7 at 20:23









            Simone Carletti

            144k34308331




            144k34308331












            • The second case should be User.where not just where and while I haven't tested this theory I might assume that this should be [owner.id] + members.ids which would now result in 3 queries (fetch owner, fetch member ids, fetch Users where.)
              – engineersmnky
              Nov 7 at 21:28












            • Correct, it will result in 3 queries. Reason why I mentioned it's not extremely efficient.
              – Simone Carletti
              Nov 8 at 22:23


















            • The second case should be User.where not just where and while I haven't tested this theory I might assume that this should be [owner.id] + members.ids which would now result in 3 queries (fetch owner, fetch member ids, fetch Users where.)
              – engineersmnky
              Nov 7 at 21:28












            • Correct, it will result in 3 queries. Reason why I mentioned it's not extremely efficient.
              – Simone Carletti
              Nov 8 at 22:23
















            The second case should be User.where not just where and while I haven't tested this theory I might assume that this should be [owner.id] + members.ids which would now result in 3 queries (fetch owner, fetch member ids, fetch Users where.)
            – engineersmnky
            Nov 7 at 21:28






            The second case should be User.where not just where and while I haven't tested this theory I might assume that this should be [owner.id] + members.ids which would now result in 3 queries (fetch owner, fetch member ids, fetch Users where.)
            – engineersmnky
            Nov 7 at 21:28














            Correct, it will result in 3 queries. Reason why I mentioned it's not extremely efficient.
            – Simone Carletti
            Nov 8 at 22:23




            Correct, it will result in 3 queries. Reason why I mentioned it's not extremely efficient.
            – Simone Carletti
            Nov 8 at 22:23


















             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53192611%2fmerging-two-relations-in-rails%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







            這個網誌中的熱門文章

            Xamarin.form Move up view when keyboard appear

            Post-Redirect-Get with Spring WebFlux and Thymeleaf

            Anylogic : not able to use stopDelay()