/ examples / book_store.rb
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