Querying multiple related records in Rails











up vote
4
down vote

favorite
1












I have an Account, User and Group model. All User and Group records belong to an Account, so it is possible for both users and groups to be related to various accounts.



Basically, I have some really hacky, pieced together SQL queries that lookup all Account records related to a given User or Group record through various
associations.



Even though it's fast and only requires a single query for everything to work, I really hate the way it's written.



I was wondering if there is any way I could do this more programatically with something like arel, or if there was a better approach. I also have some security concerns about the code.



class Account < ActiveRecord::Base

belongs_to :accountable, touch: true, polymorphic: true

CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

def self.related_to_user(user)
groups = user.groups.select('"groups".id')
friends = user.following_friendships.select('"friendships".following_id')

queries =
queries.push ['Group', groups.to_sql]
queries.push ['User', friends.to_sql]
queries.push ['User', user.id]

self.related_to(queries)
end

def self.related_to_group(group)
queries = [
['User', group.members.select('"users".id').to_sql]
]

self.related_to(queries)
end

def self.related_to(queries)
clauses = queries.map do |clause|
sprintf(CLAUSE_FOR_RELATED, clause[0], clause[1])
end

self.where(clauses.join(" OR "))
end
end









share|improve this question
















bumped to the homepage by Community 6 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.















  • Is the SQL tag properly used here? Was looking for SQL to review in your code but found only tiny snippets. No bad karma just seems a bit misleading.
    – Phrancis
    May 21 '14 at 4:31















up vote
4
down vote

favorite
1












I have an Account, User and Group model. All User and Group records belong to an Account, so it is possible for both users and groups to be related to various accounts.



Basically, I have some really hacky, pieced together SQL queries that lookup all Account records related to a given User or Group record through various
associations.



Even though it's fast and only requires a single query for everything to work, I really hate the way it's written.



I was wondering if there is any way I could do this more programatically with something like arel, or if there was a better approach. I also have some security concerns about the code.



class Account < ActiveRecord::Base

belongs_to :accountable, touch: true, polymorphic: true

CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

def self.related_to_user(user)
groups = user.groups.select('"groups".id')
friends = user.following_friendships.select('"friendships".following_id')

queries =
queries.push ['Group', groups.to_sql]
queries.push ['User', friends.to_sql]
queries.push ['User', user.id]

self.related_to(queries)
end

def self.related_to_group(group)
queries = [
['User', group.members.select('"users".id').to_sql]
]

self.related_to(queries)
end

def self.related_to(queries)
clauses = queries.map do |clause|
sprintf(CLAUSE_FOR_RELATED, clause[0], clause[1])
end

self.where(clauses.join(" OR "))
end
end









share|improve this question
















bumped to the homepage by Community 6 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.















  • Is the SQL tag properly used here? Was looking for SQL to review in your code but found only tiny snippets. No bad karma just seems a bit misleading.
    – Phrancis
    May 21 '14 at 4:31













up vote
4
down vote

favorite
1









up vote
4
down vote

favorite
1






1





I have an Account, User and Group model. All User and Group records belong to an Account, so it is possible for both users and groups to be related to various accounts.



Basically, I have some really hacky, pieced together SQL queries that lookup all Account records related to a given User or Group record through various
associations.



Even though it's fast and only requires a single query for everything to work, I really hate the way it's written.



I was wondering if there is any way I could do this more programatically with something like arel, or if there was a better approach. I also have some security concerns about the code.



class Account < ActiveRecord::Base

belongs_to :accountable, touch: true, polymorphic: true

CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

def self.related_to_user(user)
groups = user.groups.select('"groups".id')
friends = user.following_friendships.select('"friendships".following_id')

queries =
queries.push ['Group', groups.to_sql]
queries.push ['User', friends.to_sql]
queries.push ['User', user.id]

self.related_to(queries)
end

def self.related_to_group(group)
queries = [
['User', group.members.select('"users".id').to_sql]
]

self.related_to(queries)
end

def self.related_to(queries)
clauses = queries.map do |clause|
sprintf(CLAUSE_FOR_RELATED, clause[0], clause[1])
end

self.where(clauses.join(" OR "))
end
end









share|improve this question















I have an Account, User and Group model. All User and Group records belong to an Account, so it is possible for both users and groups to be related to various accounts.



Basically, I have some really hacky, pieced together SQL queries that lookup all Account records related to a given User or Group record through various
associations.



Even though it's fast and only requires a single query for everything to work, I really hate the way it's written.



I was wondering if there is any way I could do this more programatically with something like arel, or if there was a better approach. I also have some security concerns about the code.



class Account < ActiveRecord::Base

belongs_to :accountable, touch: true, polymorphic: true

CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

def self.related_to_user(user)
groups = user.groups.select('"groups".id')
friends = user.following_friendships.select('"friendships".following_id')

queries =
queries.push ['Group', groups.to_sql]
queries.push ['User', friends.to_sql]
queries.push ['User', user.id]

self.related_to(queries)
end

def self.related_to_group(group)
queries = [
['User', group.members.select('"users".id').to_sql]
]

self.related_to(queries)
end

def self.related_to(queries)
clauses = queries.map do |clause|
sprintf(CLAUSE_FOR_RELATED, clause[0], clause[1])
end

self.where(clauses.join(" OR "))
end
end






ruby ruby-on-rails






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 26 '16 at 21:20









Jamal

30.2k11115226




30.2k11115226










asked Apr 3 '14 at 4:05









bschaeffer

1213




1213





bumped to the homepage by Community 6 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.







bumped to the homepage by Community 6 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.














  • Is the SQL tag properly used here? Was looking for SQL to review in your code but found only tiny snippets. No bad karma just seems a bit misleading.
    – Phrancis
    May 21 '14 at 4:31


















  • Is the SQL tag properly used here? Was looking for SQL to review in your code but found only tiny snippets. No bad karma just seems a bit misleading.
    – Phrancis
    May 21 '14 at 4:31
















Is the SQL tag properly used here? Was looking for SQL to review in your code but found only tiny snippets. No bad karma just seems a bit misleading.
– Phrancis
May 21 '14 at 4:31




Is the SQL tag properly used here? Was looking for SQL to review in your code but found only tiny snippets. No bad karma just seems a bit misleading.
– Phrancis
May 21 '14 at 4:31










1 Answer
1






active

oldest

votes

















up vote
0
down vote













I would try to use as much as possible what is provided by ActiveRecord. I was thinking to something like:



class Account < ActiveRecord::Base
belongs_to :accountable, touch: true, polymorphic: true
CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

class << self
def related_to_user(user)
group_ids = user.groups.pluck('groups.id')
friend_ids = user.following_friendships.pluck('friendships.following_id')

query = [
compose_clause('Group', group_ids.join(', ')),
compose_clause('User', [user.id, *friend_ids].join(', '))
].join(' or ')

where(query)
end

def related_to_group(group)
member_ids = group.members.pluck('users.id')
where(compose_clause('User', member_ids.join(', ')))
end

private
def compose_clause(accountable_type, ids)
sprintf(CLAUSE_FOR_RELATED, accountable_type, ids)
end
end
end


Now in Rails 5 you could even write it like:



class Account < ActiveRecord::Base
belongs_to :accountable, touch: true, polymorphic: true

class << self
def related_to_user(user)
group_ids = user.groups.pluck('groups.id')
friend_ids = user.following_friendships.pluck('friendships.following_id')

where(accountable_type: 'Group', accountable_id: group_ids).or(
Account.where(accountable_type: 'User', accountable_id: [user.id, *friend_ids])
)
end

def related_to_group(group)
member_ids = group.members.pluck('users.id')
where(accountable_type: 'User', accountable_id: member_ids)
end
end
end





share|improve this answer























    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%2f46139%2fquerying-multiple-related-records-in-rails%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    0
    down vote













    I would try to use as much as possible what is provided by ActiveRecord. I was thinking to something like:



    class Account < ActiveRecord::Base
    belongs_to :accountable, touch: true, polymorphic: true
    CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

    class << self
    def related_to_user(user)
    group_ids = user.groups.pluck('groups.id')
    friend_ids = user.following_friendships.pluck('friendships.following_id')

    query = [
    compose_clause('Group', group_ids.join(', ')),
    compose_clause('User', [user.id, *friend_ids].join(', '))
    ].join(' or ')

    where(query)
    end

    def related_to_group(group)
    member_ids = group.members.pluck('users.id')
    where(compose_clause('User', member_ids.join(', ')))
    end

    private
    def compose_clause(accountable_type, ids)
    sprintf(CLAUSE_FOR_RELATED, accountable_type, ids)
    end
    end
    end


    Now in Rails 5 you could even write it like:



    class Account < ActiveRecord::Base
    belongs_to :accountable, touch: true, polymorphic: true

    class << self
    def related_to_user(user)
    group_ids = user.groups.pluck('groups.id')
    friend_ids = user.following_friendships.pluck('friendships.following_id')

    where(accountable_type: 'Group', accountable_id: group_ids).or(
    Account.where(accountable_type: 'User', accountable_id: [user.id, *friend_ids])
    )
    end

    def related_to_group(group)
    member_ids = group.members.pluck('users.id')
    where(accountable_type: 'User', accountable_id: member_ids)
    end
    end
    end





    share|improve this answer



























      up vote
      0
      down vote













      I would try to use as much as possible what is provided by ActiveRecord. I was thinking to something like:



      class Account < ActiveRecord::Base
      belongs_to :accountable, touch: true, polymorphic: true
      CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

      class << self
      def related_to_user(user)
      group_ids = user.groups.pluck('groups.id')
      friend_ids = user.following_friendships.pluck('friendships.following_id')

      query = [
      compose_clause('Group', group_ids.join(', ')),
      compose_clause('User', [user.id, *friend_ids].join(', '))
      ].join(' or ')

      where(query)
      end

      def related_to_group(group)
      member_ids = group.members.pluck('users.id')
      where(compose_clause('User', member_ids.join(', ')))
      end

      private
      def compose_clause(accountable_type, ids)
      sprintf(CLAUSE_FOR_RELATED, accountable_type, ids)
      end
      end
      end


      Now in Rails 5 you could even write it like:



      class Account < ActiveRecord::Base
      belongs_to :accountable, touch: true, polymorphic: true

      class << self
      def related_to_user(user)
      group_ids = user.groups.pluck('groups.id')
      friend_ids = user.following_friendships.pluck('friendships.following_id')

      where(accountable_type: 'Group', accountable_id: group_ids).or(
      Account.where(accountable_type: 'User', accountable_id: [user.id, *friend_ids])
      )
      end

      def related_to_group(group)
      member_ids = group.members.pluck('users.id')
      where(accountable_type: 'User', accountable_id: member_ids)
      end
      end
      end





      share|improve this answer

























        up vote
        0
        down vote










        up vote
        0
        down vote









        I would try to use as much as possible what is provided by ActiveRecord. I was thinking to something like:



        class Account < ActiveRecord::Base
        belongs_to :accountable, touch: true, polymorphic: true
        CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

        class << self
        def related_to_user(user)
        group_ids = user.groups.pluck('groups.id')
        friend_ids = user.following_friendships.pluck('friendships.following_id')

        query = [
        compose_clause('Group', group_ids.join(', ')),
        compose_clause('User', [user.id, *friend_ids].join(', '))
        ].join(' or ')

        where(query)
        end

        def related_to_group(group)
        member_ids = group.members.pluck('users.id')
        where(compose_clause('User', member_ids.join(', ')))
        end

        private
        def compose_clause(accountable_type, ids)
        sprintf(CLAUSE_FOR_RELATED, accountable_type, ids)
        end
        end
        end


        Now in Rails 5 you could even write it like:



        class Account < ActiveRecord::Base
        belongs_to :accountable, touch: true, polymorphic: true

        class << self
        def related_to_user(user)
        group_ids = user.groups.pluck('groups.id')
        friend_ids = user.following_friendships.pluck('friendships.following_id')

        where(accountable_type: 'Group', accountable_id: group_ids).or(
        Account.where(accountable_type: 'User', accountable_id: [user.id, *friend_ids])
        )
        end

        def related_to_group(group)
        member_ids = group.members.pluck('users.id')
        where(accountable_type: 'User', accountable_id: member_ids)
        end
        end
        end





        share|improve this answer














        I would try to use as much as possible what is provided by ActiveRecord. I was thinking to something like:



        class Account < ActiveRecord::Base
        belongs_to :accountable, touch: true, polymorphic: true
        CLAUSE_FOR_RELATED = '"accounts"."accountable_type" = '%s' AND "accounts"."accountable_id" IN (%s)'.freeze

        class << self
        def related_to_user(user)
        group_ids = user.groups.pluck('groups.id')
        friend_ids = user.following_friendships.pluck('friendships.following_id')

        query = [
        compose_clause('Group', group_ids.join(', ')),
        compose_clause('User', [user.id, *friend_ids].join(', '))
        ].join(' or ')

        where(query)
        end

        def related_to_group(group)
        member_ids = group.members.pluck('users.id')
        where(compose_clause('User', member_ids.join(', ')))
        end

        private
        def compose_clause(accountable_type, ids)
        sprintf(CLAUSE_FOR_RELATED, accountable_type, ids)
        end
        end
        end


        Now in Rails 5 you could even write it like:



        class Account < ActiveRecord::Base
        belongs_to :accountable, touch: true, polymorphic: true

        class << self
        def related_to_user(user)
        group_ids = user.groups.pluck('groups.id')
        friend_ids = user.following_friendships.pluck('friendships.following_id')

        where(accountable_type: 'Group', accountable_id: group_ids).or(
        Account.where(accountable_type: 'User', accountable_id: [user.id, *friend_ids])
        )
        end

        def related_to_group(group)
        member_ids = group.members.pluck('users.id')
        where(accountable_type: 'User', accountable_id: member_ids)
        end
        end
        end






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Sep 27 '17 at 21:16

























        answered Sep 27 '17 at 21:09









        mabe02

        1363




        1363






























             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f46139%2fquerying-multiple-related-records-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







            Popular posts from this blog

            404 Error Contact Form 7 ajax form submitting

            How to know if a Active Directory user can login interactively

            Refactoring coordinates for Minecraft Pi buildings written in Python