What's the best practice to achieve dependency injection with ruby-graphql? - ruby

I want to use dependency injection with graphql-ruby.
I.e.
module CustomerCredits
module Types
class QueryType < GraphQL::Schema::Object
description 'The query root of this schema'
field :filter_users, [Types::UserType], null: false do
argument :parameters, InputTypes::UserFilterParameters, required: true
end
# resolvers
def filter_users(arguments)
repo = UserRepository.new(complex_arguments) # I want to inject the dependency UserRepository
repo.filtered_users(**arguments[:parameters])
end
end
end
end
Using dependency injection in initialize is not possible, because QueryType is instantiated by graphql-ruby.

As you've mentioned, injection through the initializer might not be super straight forward, so if you want to go fully into dependency injection and inversion of control, you could leverage an IOC Container library like Dry Auto Inject. I know it might be a full blown solution, and it could possibly be too heavy handed for your use case (not sure), but since you're already using repositories in Ruby, it might not be.

Following the schema definition from graphql-ruby, one solution I thought of for this problem was to inject your database reference into a controller class, then when your controller is hit, you pass the database reference as part of the context.
# app/controllers/graphql_controller.rb
result = MySchema.execute(
params[:query],
variables: params[:variables],
context: {
current_user: current_user,
db: my_database_ref # inject database ref here
},
)
render json: result
Then in your Query Type definiton you can pull the db from the context.
class QueryType < GraphQL::Schema::Object
description "The query root of this schema"
field :post, PostType, "Find a post by ID" do
argument :id, ID
end
def post(id:)
db = context[:db] # pull db reference from context here
db[:posts].where(id:).first
end
end

Related

Resolvers in GraphQL do not seem to get context

I am using resolvers inside query_type:
module Types
class QueryType < Types::BaseObject
...
field :gather_things,
resolver: Resolvers::GatherThings,
null: true do
argument :scope, String, required: false
argument :scope_id, ID, required: false
...
And in my resolvers folder there is a base file:
module Resolvers
class Base < GraphQL::Schema::Resolver
def current_user_id
#current_user_id ||= context[:current_user].id
end
end
end
However, when I try to use current_user_id inside the resolver code, it breaks:
module Resolvers
class GatherThings < Resolvers::Base
...
def things_resolver(scope, scope_id)
if scope.nil?
Thing.from_user(current_user_id)
...
end
end
...
Saying : "undefined method 'id' for nil:NilClass"
Any clue?
I later found out that something was nullifying my session upstream.
This was due to the use of JWTs for session management and mixing them with cookies for OmniAuth.
I have to work out on a solution for using both depending on the intended use.

How to write spec for concern

I created a concern below. what i am doing is i am mapping the column from a table that has values 0, 1, 2 and returning strings for those in JSON. Can someone guide me how can i write unit test cases for it?
module User
extend ActiveSupport::Concern
included do
def user_mapping(user_in_number)
user_hash = {
'0'=> 'Support',
'1'=> 'Developer',
'2'=> 'Business Analyst'
}.freeze
user_hash[user_in_number]
end
end
end
Thanks
For any module you can do this:
subject = Class.new do
include User
end.new
assert subject.user_mapping('0'), 'Support'
Class.new creates an anonymous class, the block is evaluated in the context of the anonymous class, so acting like the body of a regular class.
Then create an instance of the anonymous class and call the methods as defined by the included module.
By the way the actual method does not need to assign so many vars:
UserMap = {
'0'=> 'Support',
'1'=> 'Developer',
'2'=> 'Business Analyst'
}.freeze
def user_mapping(user_in_number)
UserMap.fetch(user_in_number)
end
fetch will raise if the given key is not found.
I'd might also reconsider the name User for a module, will you never need a User class...

What is a Ruby factory method?

I understand that a factory method is a class method that utilises the self keyword and instantiates an object of it's own class. I don't understand how this is useful or how it can extend the functionality of initialize method.
I'm working on a project creating a command line address book that asks me to use a factory pattern on the Person class so that I can create a Trainee or Instructor (subclasses) with different attributes.
A factory class is a clean way to have a single factory method that produces various kind of objects.
It takes a parameter, a parameter that tells the method which kind of object to create. For example to generate an Employee or a Boss, depending on the symbol that is passed in:
class Person
def initialize(attributes)
end
end
class Boss
def initialize(attributes)
end
end
class Employee
def initialize(attributes)
end
end
class PersonFactory
TYPES = {
employee: Employee,
boss: Boss
}
def self.for(type, attributes)
(TYPES[type] || Person).new(attributes)
end
end
and then:
employee = PersonFactory.for(:employee, name: 'Danny')
boss = PersonFactory.for(:boss, name: 'Danny')
person = PersonFactory.for(:foo, name: 'Danny')
I also wrote a more detailed blog post about that topic: The Factory Pattern
The Factory Method Pattern at least allows you to give an expressive name to what could otherwise be a complicated or opaque constructor. For instance if you have a constructor that takes a bunch of parameters, it may not be clear why to the caller, having a named Factory method or methods could potentially hide the complexity of the object creation and make your code more expressive of what is actually going on.
So in your case a bad design may be:
trainee = Person.new true
or
instructor = Person.new false
Where true or false branches to creating an instructor or trainee.
This could be improved by using a Factory method to clarify what is going on:
trainee = Person.create_trainee
instructor = Person.create_instructor
Why bother with factory methods?
(A) To simplify things:
Creating objects can be complicated, and
you may need to do this multiple times.
It's hard to remember:
# ugh - too much work!
driver = Person.new
engine = Brrrm.new
engine.turbo_charged = true
engine.max_rpm = 100000
car = Porsche.new
car.driver = driver
car.engine = engine
# preference - less to remember
ben = PersonFactory.create("ben")
car = PorscheFactory.create(ben)
# and you get the following for free, without remembering:
car.turbo_charged # => true
car.engine # => brrrm
car.driver # => ben_koshy
car.driver.personality # => :excellent_dude
# you can mix and match default values with options.
# generally speaking you want to inject as much as you can
# i.e. inverting dependencies. I make these illustrates to
# explain a concept, not as an example of great coding.
(B) To allow for overridding / stubbing
If you are writing testable code, you might want to create your own specialised 'crash dummy vehicle' so you can test collisions etc. If you have a factory method / object, then you can do this easily. This is a somewhat adavanced topic - google "creating a seam" or "dependency injection" for more info.

Can an Abstract Factory be responsible for "creating or finding an existing" item?

My Ruby code has a Concrete Factory, which builds some complex objects:
author = Author::Factory.build(email: "john#example.com")
class Author
class Factory < BaseFactory
def self.build(email: nil)
# ... Some data preparation and defaults
Author.new(
email: email
# Map and assign more attributes
)
end
end
end
Now, I've run into a situation where I either need to build a new one,
or assign one from an existing collection. In
database-terms: an UPSERT, or in ActiveRecord: find_or_create_by.
And I am not sure if this:
Is a proper task for an Abstract Factory and
If the proper way to implement this is by passing the collection, or
to make the Factory itself responsible for fetching it.
Passing it in:
author = Author::Factory.build(email: "john#example.com", existing: authors)
class Author
class Factory < BaseFactory
def self.build(email: nil)
author = existing.find {|author| author.email == email }
# If not found, prepare and build a new one, like above.
end
end
end
Letting the Factory find it:
author = Author::Factory.build(email: "john#example.com")
class Author
class Factory < BaseFactory
def self.build(email: nil)
author = Author.find_in_existing_with(email: email)
# If not found, prepare and build a new one, like above.
end
end
end
So: Should a Factory every be responsible for finding-or-building?
And if so, must the Factory be responsible for fetching the items that
it must match against, or should the caller pass them along?
Factory is a creational pattern, so clients will expect fresh new instances out of it.
Sure, what the Factory does internally is of no concern to consuming code. But if Author is a domain entity, I fail to see how an Author-building object could be used by consumers for anything else than the "real world" addition of a new author in the system.
Unless you want be semantically unfair and trick callers by reusing existing authors instead of instantiating new ones. But that doesn't look like something you would typically do in production.

Sharing a class instance between two classes

I have two different classes that both represent objects that need to be persisted to my database and now I want to share the database client object between the two classes. I want to avoid instantiating the client object more than once.
Currently I do this by using a global variable
$client = Mysql2::Client.new(:database => "myDb", :user => "user", :password => "password", :host => "localhost")
class Person
def save
$client.query("INSERT INTO persons")
end
end
class Car
def save
$client.query("INSERT INTO cars")
end
end
This works, but I am wondering if there are more correct ways to do this and why they are more correct?
You can inherit from a parent class. This allows you to share common functionality across objects and follows DRY (do not repeat yourself) programming principles. It will also allow you to protect your DB connection with locks, resuces, queues, pools, and whatever else you may want to do without having to worry about it in your children classes
class Record
#table_name = nil
##client = Mysql2::Client.new(:database => "myDb", :user => "user", :password => "password", :host => "localhost")
def save
##client.query("INSERT INTO #{#table_name}") if #table_name
end
end
class Person < Record
#table_name = "persons"
end
class Car < Record
#table_name = "cars"
end
While we are on the subject, you should look at using ActiveRecord for handling your database models and connections. It already does pretty much anything you'll need and will be more compatible with other gems already out there. It can be used without rails.
As an alternative on using inheritance, why not consider a simple Singleton pattern? This could make your models cleaner, by separating the responsibility outside your classes. And eliminating the need for inheritance.
The example below illustrates this. Only one, single instance of the DataManager class can exist. So, you'll only instantiate it once - but can use it everywhere:
require 'singleton'
class DataManager
include Singleton
attr_accessor :last_run_query
def initialize()
if #client.nil?
p "Initialize the Mysql client here - note that this'll only be called once..."
end
end
def query(args)
# do your magic here
#last_run_query = args
end
end
Next, calling it using the .instance accessor is a breeze - and will always point to one single instance, like so:
# Fetch, or create a new singleton instance
first = DataManager.instance
first.query('drop table mother')
p first.last_run_query
# Again, fetch or create a new instance
# this'll actually just fetch the first instance from above
second = DataManager.instance
p second.last_run_query
# last line prints: "drop table mother"
For the record, the Singleton pattern can have some downsides and using it frequently results in a never-ending debate on whether you should use it or not. But in my opinion it's a decent alternative to your specific question.

Resources