book_store.rb
1 require 'graphlyte' 2 require 'rest-client' 3 4 # The kind of thing that is represented in the schema 5 # Entities can be searched for and loaded from JSON responses. 6 module Entity 7 def assign(attr, value) 8 attr = attr.to_sym 9 if (klass = self.class.connections[attr]) 10 value = Mapper.new(klass).build_from(value) 11 elsif !self.class.record_fields.any? { _1 == attr || Array(_1).first == attr } 12 return 13 end 14 15 send(:"#{attr.downcase}=", value) 16 end 17 18 def assign_all(attrs) 19 attrs.each { |k, v| assign(k, v) } 20 end 21 22 def self.included(mod) 23 mod.define_singleton_method :mapper do 24 @mapper ||= Mapper.new(self) 25 end 26 end 27 end 28 29 # An author entity 30 class Author 31 include Entity 32 33 attr_accessor :name 34 35 def self.record_fields 36 %i[name] 37 end 38 39 def self.connections 40 {} 41 end 42 end 43 44 # A book entity 45 class Book 46 include Entity 47 48 attr_accessor :title, :author, :isbn, :published 49 50 def self.record_fields 51 [:title, :published, %i[isbn id]] 52 end 53 54 def self.connections 55 { author: Author } 56 end 57 end 58 59 # How to load an Entity from a response 60 class Mapper 61 def initialize(entity_class) 62 @entity_class = entity_class 63 end 64 65 def build_from(data) 66 return unless data 67 68 entity = @entity_class.new 69 entity.assign_all(data) 70 entity 71 end 72 end 73 74 class Store 75 def initialize(entity_class) 76 @entity_class = entity_class 77 end 78 79 def load(id) 80 query = Graphlyte.query do |q| 81 select_entity(q, @entity_class, id: id).alias(:_) 82 end 83 84 data = Http.new.post(query)['_'] 85 86 @entity_class.mapper.build_from(data) 87 end 88 89 private 90 91 def select_entity(node, entity_class, **args) 92 node.select!(entity_class.name, **args) do |child| 93 entity_class.record_fields.each do |field| 94 case field 95 when Array 96 child.select!(field.last).alias(field.first) 97 else 98 child.select!(field) 99 end 100 end 101 entity_class.connections.each do |name, klass| 102 select_entity(child, klass).alias(name) 103 end 104 end 105 end 106 end 107 108 Books = Store.new(Book) 109 Authors = Store.new(Author) 110 111 class Http 112 def initialize 113 @host = ENV.fetch('RC_HOST', 'localhost') 114 @port = ENV.fetch('RC_PORT', '3000') 115 @uri = "http://#{@host}:#{@port}/raw" 116 @headers = { 117 'Content-Type' => 'application/json', 118 'Accept' => 'application/json' 119 } 120 end 121 122 def post(query) 123 json = query.request_body 124 JSON.parse(RestClient.post(@uri, json, @headers))['data'] 125 rescue RestClient::ExceptionWithResponse => e 126 body = JSON.parse(e.response.body) 127 raise body.fetch('errors', [{ 'message' => 'boom' }]).map { _1['message'] }.join(', ') 128 end 129 end