Skip to content

Instantly share code, notes, and snippets.

@nrr
Last active April 12, 2020 11:34
Show Gist options
  • Save nrr/0a6ae872b0e49538d180d4baeea9e2e5 to your computer and use it in GitHub Desktop.
Save nrr/0a6ae872b0e49538d180d4baeea9e2e5 to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
require "sqlite3"
require "active_record"
# @return [Object]
def set_up_schema
::ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
t.string :title
t.string :content
t.boolean :published
end
end
end
module Models
# @abstract
class ApplicationRecord < ::ActiveRecord::Base
self.abstract_class = true
end
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
# @return [::Domain::Post]
def to_domain
::Domain::Post.new(
id: id,
title: title,
content: content,
is_published: published,
)
end
class << self
# @param [::Domain::Post] post
# @return [::Models::Post]
def from_domain(post)
new(
id: post.id,
title: post.title,
content: post.content,
published: post.published?,
)
end
end
end
end
module Domain
class Post
# @return [Integer]
attr_reader :id
# @return [String]
attr_reader :title
# @return [String]
attr_reader :content
# @param [Integer] id
# @param [String] title
# @param [String] content
# @param [Boolean] is_published
def initialize(id: -1, title: "", content: "", is_published: false)
self.id = id
self.title = title
self.content = content
self.is_published = is_published
end
# @return [Boolean]
def published?
!!@is_published
end
private
# @param [Boolean] new_published_state
# @return [Boolean]
def is_published=(new_published_state)
@is_published = !!new_published_state
end
# @param [String] new_title
# @return [String]
# @raise [ArgumentError]
# @raise [VulgarTitleError]
def title=(new_title)
raise ArgumentError if new_title.empty? || new_title.nil?
raise ObsceneTitleError if new_title.downcase.include?("fuck")
@title = new_title
end
# @param [String] new_content
# @return [String]
# @raise [ArgumentError]
# @raise [VulgarContentError]
def content=(new_content)
raise ArgumentError if new_content.empty? || new_content.nil?
raise ObsceneContentError if new_content.downcase.include?("fuck")
@content = new_content
end
# @param [Integer] new_id
# @return [Integer]
def id=(new_id)
raise ArgumentError if new_id.negative? || new_id.zero? || new_id.nil?
@id = new_id
end
end
end
class ObsceneTitleError < StandardError; end
class ObsceneContentError < StandardError; end
class PostsRepositoryError < StandardError; end
# @abstract
module IPostsRepository
# @return [Array<::Domain::Post>]
def all
raise NotImplementedError
end
# @return [Array<::Domain::Post>]
def all_published
raise NotImplementedError
end
# @param [Integer] post_id
# @return [::Domain::Post]
def by_id(post_id)
raise NotImplementedError
end
# @param [::Domain::Post] post
# @return [Boolean, nil]
# @raise [PostsRepositoryError]
def store(post)
raise NotImplementedError
end
end
class ActiveRecordPostsRepository
include IPostsRepository
# @param [Class] active_record_model
def initialize(active_record_model)
@posts_model = active_record_model
end
# @todo Afford ourselves a way to paginate this data.
# @return [Array<::Domain::Post>]
def all
@posts_model.all.map { |m| m.to_domain }
end
# @todo Afford ourselves that same pagination thing as in #all.
# @return [Array<::Domain::Post>]
def all_published
@posts_model.published.map { |m| m.to_domain }
end
# @param [Integer] post_id
# @return [::Domain::Post]
def by_id(post_id)
@posts_model.find(post_id).to_domain
end
# @param [Array<::Domain::Post>] posts
# @return [Boolean]
# @raise [PostsRepositoryError]
def store(*posts)
# @todo This is likely horribly inefficient. I don't claim to understand
# Active Record all that well, but I'd also not recommend harping too much
# on the details in how I use AR here. The point is to decouple your
# application from it.
posts.each do |p|
::Models::Post.from_domain(p).save!
end
rescue => e
raise PostsRepositoryError.new(e)
else
true
end
end
class InMemoryPostsRepository
# @todo Implement the IPostsRepository interface for storing posts in a way
# that is strictly in-memory. Yes, I realize that I'm using :memory: with
# SQLite 3 on the Active Record side of things, but let's pretend for a
# moment that it's actually, say, PostgreSQL.
end
# @return [Integer]
def main
logger = Logger.new(STDOUT)
logger.level = Logger::DEBUG
::ActiveRecord::Base.logger = logger
# Set up a database that resides in RAM
::ActiveRecord::Base.establish_connection(
adapter: "sqlite3",
database: ":memory:",
)
set_up_schema
repository = ActiveRecordPostsRepository.new(::Models::Post)
post1 = ::Domain::Post.new(
id: 1337,
title: "A not-vulgar title",
content: "Some not-vulgar content",
)
begin
# Illustrate some business logic...
post2 = ::Domain::Post.new(
id: 2048,
title: "Fuck this noise",
content: "No, seriously, why the fuck did I do that?",
)
rescue
nil
end
post3 = ::Domain::Post.new(
id: 2005,
title: "Just another post",
content: "Yep, it's another post.",
is_published: true,
)
repository.store(post1, post3)
pp repository.by_id(1337)
pp repository.all.length
pp repository.all_published.length
0
end
if __FILE__ == $0
exit(main)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment