Skip to content

Instantly share code, notes, and snippets.

@inopinatus
Last active October 9, 2019 01:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inopinatus/d602207ca897994ffe799409c5907961 to your computer and use it in GitHub Desktop.
Save inopinatus/d602207ca897994ffe799409c5907961 to your computer and use it in GitHub Desktop.
Fun with relational composition in Rails
# frozen_string_literal: true
require "bundler/inline"
gemfile true, quiet: true do
source "https://rubygems.org"
gem "rails", "6.0.0"
gem "sqlite3"
gem "pry"
end
require "active_support/core_ext/kernel/concern"
require "active_record"
$VERBOSE = nil # shut up Ruby 2.7
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
ActiveRecord::Migration.verbose = false
ActiveRecord::Schema.define do
create_table :users, force: true do |t|
t.string :name
end
create_table :authors, force: true do |t|
t.string :name
end
create_table :languages, force: true do |t|
t.string :code
end
create_table :books, force: true do |t|
t.string :title
t.references :language
t.references :author
t.boolean :hidden
end
create_join_table :books, :users, force: true
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
scope :random_order, -> { order %<random()> }
private
def self.unique
-> { distinct }
end
end
module CollectionOwner
refine ActiveRecord::Associations::CollectionProxy do
private
def owner
proxy_association.owner
end
end
end
module Recommendation
using CollectionOwner
def recommend limit = 1
recommendable_to(owner).random_order.limit(limit)
end
end
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :author
belongs_to :language
has_and_belongs_to_many :users
scope :language, -> languages { where language: languages }
scope :not_hidden, -> { where hidden: false }
scope :recommendable_to, -> user { language(user.languages).not_hidden }
def to_s
"#{title} by #{author.name} (#{language.code})"
end
end
class Language < ApplicationRecord
has_many :books
end
class User < ApplicationRecord
has_and_belongs_to_many :books
has_many :authors, unique, through: :books
has_many :languages, unique, through: :books
has_many :books_by_followed_authors, unique, through: :authors, source: :books, extend: Recommendation
end
authors = Author.create! [
{name: "René"}, {name: "Amal"}, {name: "Ryō"}
]
languages = Language.create! [
{code: "fr"}, {code: "ar"}, {code: "jp"}
]
books = 30.times.map do |n|
Book.create! title: "Book #{n}", author: authors.sample, language: languages.sample, hidden: rand(2)
end
user = User.create! name: 'Valued Customer'
user.books << books.sample(3)
puts <<~HEAD, user.books_by_followed_authors.recommend(5)
#{user.name} likes:
books by #{user.authors.pluck(:name).to_sentence}; and
books in #{user.languages.pluck(:code).to_sentence}.
Recommendations:
HEAD
@inopinatus
Copy link
Author

Originally for this answer [reddit.com] and then I had a bit of play with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment