-
-
Save nrr/0a6ae872b0e49538d180d4baeea9e2e5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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