Module: MostRelated

Defined in:
lib/most_related.rb,
lib/most_related/version.rb

Constant Summary collapse

VERSION =
'0.1.0'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.join_table_values(many_to_many_assocation) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/most_related.rb', line 76

def self.join_table_values(many_to_many_assocation)
  if many_to_many_assocation.macro == :has_and_belongs_to_many
    join_table = many_to_many_assocation.join_table
    foreign_key = many_to_many_assocation.foreign_key
    association_foreign_key = many_to_many_assocation.association_foreign_key
  elsif many_to_many_assocation.macro == :has_many
    join_table = many_to_many_assocation.through_reflection.table_name
    foreign_key = many_to_many_assocation.through_reflection.foreign_key
    association_foreign_key = many_to_many_assocation.foreign_key
  end
  [join_table, foreign_key, association_foreign_key]
end

Instance Method Details

‘most_related` returns those models that have the most

associated models in common

Post example:

class Post < ActiveRecord::Base
  has_most_related :authors
  has_most_related :tags, as: :most_related_by_tags
  has_most_related :authors, :tags, as: :most_related_by_author_or_tag

  has_many :author_posts
  has_many :authors, through: :author_posts
end

class Author < ActiveRecord::Base
  has_many :author_posts
end

class AuthorPosts < ActiveRecord::Base
  belongs_to :author
  belongs_to :post
end

To return the posts with the most authors in common with ‘post`, in descending order:

post.most_related

To return the posts with the most tags in common with ‘post`, in descending order:

post.most_related_by_tag

To return the posts with the most authors and tags in common with ‘post`, in descending order:

post.most_related_by_author_or_tag

The count of the associated models in common is accessible on each returned model

eg post.most_related_count, post.most_related_by_tag_count and post.most_related_by_author_or_tag_count


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/most_related.rb', line 40

def has_most_related(*many_to_many_associations, as: :most_related)

  # defaults to 'def most_related'
  define_method as do
    table_name = self.class.table_name
    association_scopes = []

    many_to_many_associations.each do |many_to_many_association|
      assocation = self.class.reflect_on_association(many_to_many_association)
      join_table, foreign_key, association_foreign_key = self.class.join_table_values(assocation)

      association_scopes << self.class.where("#{join_table}.#{association_foreign_key} IN " +
        "(select #{join_table}.#{association_foreign_key} from #{join_table} " +
        "where #{join_table}.#{foreign_key} = :foreign_key)", foreign_key: self.id).
        joins("INNER JOIN #{join_table} ON #{join_table}.#{foreign_key} = #{table_name}.id")
    end

    scope = self.class.select("#{table_name}.*, count(#{table_name}.id) AS #{as}_count").
      where.not(id: self.id).order("#{as}_count DESC")
    group_by_clause = 'id'

    # if there is only one many-to-many association no need to use UNION sql syntax
    if association_scopes.one?
      scope.merge(association_scopes.first).group(group_by_clause)
    else
      # with postgres the group by clause has to be different
      # http://dba.stackexchange.com/questions/88988/postgres-error-column-must-appear-in-the-group-by-clause-or-be-used-in-an-aggre
      if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
        group_by_clause = self.class.column_names.join(', ')
      end

      # see http://blog.ubersense.com/2013/09/27/tech-talk-unioning-scoped-queries-in-rails/
      scope.from("((#{association_scopes.map(&:to_sql).join(') UNION ALL (')})) AS #{table_name}").group(group_by_clause)
    end
  end

  def self.join_table_values(many_to_many_assocation)
    if many_to_many_assocation.macro == :has_and_belongs_to_many
      join_table = many_to_many_assocation.join_table
      foreign_key = many_to_many_assocation.foreign_key
      association_foreign_key = many_to_many_assocation.association_foreign_key
    elsif many_to_many_assocation.macro == :has_many
      join_table = many_to_many_assocation.through_reflection.table_name
      foreign_key = many_to_many_assocation.through_reflection.foreign_key
      association_foreign_key = many_to_many_assocation.foreign_key
    end
    [join_table, foreign_key, association_foreign_key]
  end
end