For the backend part of the coffeekzn.ru web app I decided to choose noSQL database MongoDB and Mongoid instead of ActiveRecord framework.
Let’s create sample Rails API project without ActiveRecord for example:
rails new coffeekzn_api --api --skip-bundle --skip-active-record --skip-test --skip-system-test
Great, let’s add mongoid and graphql gems and some other extensions:
gem 'mongoid', '~> 6.2.1'
gem 'bson_ext'
gem 'graphql'
gem 'graphql-batch'
gem 'graphql-preload'
gem 'mongoid_rails_migrations'
gem 'geocoder'
gem 'mongoid-slug'
gem 'mongoid-tags'
Great, now we need to create config/mongoid.yml file and can go forward.
development:
clients:
default:
database: coffeekzn_api_development
hosts:
- 127.0.0.1:27017
test:
clients:
default:
database: coffeekzn_api_test
hosts:
- 127.0.0.1:27017
options:
read:
mode: :primary
max_pool_size: 1
Let’s create shop.rb model:
class Shop
include Mongoid::Document
include Mongoid::Timestamps
include Geocoder::Model::Mongoid
include Mongoid::Slug
include Mongoid::Tags
field :email, type: String
field :title, type: String
field :description, type: String
field :address, type: String
field :preview_image, type: String
field :coordinates, type: Array
field :vk, type: String
field :facebook, type: String
field :twitter, type: String
field :instagram, type: String
field :website, type: String
field :working_hours, type: String
field :phone, type: String
field :espresso_price, type: String
field :cappuccino_price, type: String
field :roasting, type: String
field :features, type: String
field :coffee_machine, type: String
field :sell_in_beans, type: Boolean, default: false
field :alternate, type: Boolean, default: false
field :merchandise, type: Boolean, default: false
slug :title
geocoded_by :address
after_validation :geocode
scope :ordered, -> { order('created_at DESC') }
validates :title, presence: true
validates :slug, presence: true, uniqueness: true
end
Nice one. Now, we can create our models from rails console for example, as usual:
irb(main):015:0> Shop.new
=> #<Shop _id: 5a69c62cd7849c31286c9660, created_at: nil, updated_at: nil, tags: [], email: nil, title: nil, description: nil, address: nil, preview_image: nil, coordinates: nil, vk: nil, facebook: nil, twitter: nil, instagram: nil, website: nil, working_hours: nil, phone: nil, espresso_price: nil, cappuccino_price: nil, roasting: nil, features: nil, coffee_machine: nil, sell_in_beans: false, alternate: false, merchandise: false, _slugs: nil>
Great! Let’s move to our API implementation using GraphQL!
Nothing special here, you can read about basic GraphQL setup in on of my previous posts: GraphQL + Rail.
Let’s take a look into shop_type.rb implementation:
Types::ShopType = GraphQL::ObjectType.define do
name 'Shop'
field :id, !types.ID
field :email, types.String
field :instagram, types.String
field :vk, types.String
field :facebook, types.String
field :twitter, types.String
field :website, types.String
field :phone, types.String
field :title, !types.String
field :description, !types.String
field :working_hours, !types.String
field :address, !types.String
field :preview_image, types.String
field :espresso_price, types.String
field :cappuccino_price, types.String
field :roasting, types.String
field :features, types.String
field :coffee_machine, types.String
field :sell_in_beans, types.Boolean
field :alternate, types.Boolean
field :merchandise, types.Boolean
field :tags, types[types.String]
field :slug, types.String
field :location do
type Types::LocationType
resolve -> (obj, args, ctx) {
obj.coordinates
}
end
field :updated_at do
type types.Int
resolve -> (obj, args, ctx) {
obj.updated_at.to_i
}
end
field :created_at do
type types.Int
resolve -> (obj, args, ctx) {
obj.created_at.to_i
}
end
end
And the query_type.rb file:
Types::QueryType = GraphQL::ObjectType.define do
name "Query"
field :shops, !types[Types::ShopType] do
resolve -> (obj, args, ctx) {
Shop.order(title: 'asc')
}
end
field :shop do
type Types::ShopType
argument :id, !types.ID
description "Find a Shop by SLUG"
resolve ->(obj, args, ctx) { Shop.find(args["id"]) }
end
end
Great, let’s check how is our API working on all collection:
And getting one Shop by ID:
Enjoy :)