Skip to content

Instantly share code, notes, and snippets.

@RichardOrnelas
Created February 22, 2023 20:26
Show Gist options
  • Save RichardOrnelas/7de2915e51872d373e49bad68591830e to your computer and use it in GitHub Desktop.
Save RichardOrnelas/7de2915e51872d373e49bad68591830e to your computer and use it in GitHub Desktop.
Generators for Rails models and GQL types
# frozen_string_literal: true
namespace :generate do
desc "create blueprint for each loan entity"
task graphql_attributes: :environment do
# include the GeneratorHelpers
include GeneratorHelpers
# Types to be generated after looping through the models
new_union_types = {
# "user_type": [ CustomerType, CatType ]
}
# go through each model and build their associated GQL Type
model_names_and_filepaths.filter do |(model_name, _fp)|
model_name != "application_record"
end.each do |(model_name, _model_file_path)|
lines = [
"module Types::Mutation\n",
"\tclass #{model_name.camelize}Attributes < Types::BaseInputObject\n"
]
model = model_name.camelize.constantize
# 1 - get all the column names
lines << %(\t\t# Database fields\n)
model.columns.each do |c|
gql_type = ""
gql_type = case c.type.to_s.camelize
when "Date", "Datetime"
"GraphQL::Types::ISO8601DateTime"
when "Integer"
# check for enum
enum_names = model.defined_enums.keys
gql_type = enum_names.include?(c.name) ? "String" : "Integer"
when "Decimal", "Float"
"Float"
when "Uuid", "Inet", "Text"
"String"
else
c.type.to_s.camelize
end
# @todo how to controll for password_digest and ssn_ciphertext, ssn_bidx situations?
lines << %(\t\targument :#{c.name}, #{gql_type}, required: false\n)
end
lines << %(\n)
lines << %(\t\t# Relationships\n)
# 2 - Put all of the relationships into the file body
model.reflections.keys.filter do |key|
["attachments_attachments", "attachments_blobs", "versions"].exclude? key
end.each do |association_key|
reflection = model.reflections[association_key]
# i - Insert a new union type if this relationship has a "type" attribute (i.e. polymorphic)
if model.reflections[association_key].type
r_file_name = reflection.type.gsub("type", "attributes")
new_union_types[r_file_name] = [] if new_union_types[r_file_name].nil?
new_union_types[r_file_name] << %(#{model_name.camelize}Attributes)
end
lines << %(\t\targument :#{association_key}, [Types::Mutation::#{reflection.class_name}Attributes], required: false\n)
end
lines << %(\n)
# Finish the boilerplate
lines << %(\tend\n)
lines << %(end)
# 4 - write it to a new type file
write_file_by_lines("app/graphql/types/mutation/#{model_name}_attributes.rb", lines)
end
# Build all the union types that were identified while going through each of the models
new_union_types.each do |key, arr_of_types|
# build some boilerplate for the new union GQL Type
union_file_lines = [
"module Types::Mutation\n",
"\tclass #{key.camelize} < Types::BaseInputObject\n",
# "\t\tpossible_types #{arr_of_types.join(', ')}\n",
"\n",
"\t\tdef self.resolve_type(obj, ctx)\n",
"\t\t\t\"\#{obj.class}Attributes\".constantize\n",
"\t\tend\n",
"\tend\n",
"end\n"
]
write_file_by_lines("app/graphql/types/mutation/#{key.underscore}.rb", union_file_lines)
end
end
end
# frozen_string_literal: true
namespace :generate do
desc "create blueprint for each loan entity"
task graphql_types: :environment do
# include the GeneratorHelpers
include GeneratorHelpers
# Types to be generated after looping through the models
new_union_types = {
# "user_type": [ CustomerType, CatType ]
}
# go through each model and build their associated GQL Type
model_names_and_filepaths.filter do |(model_name, _fp)|
model_name != "application_record"
end.each do |(model_name, _model_file_path)|
lines = [
"module Types::Query\n",
"\tclass #{model_name.camelize}Type < Types::BaseObject\n"
]
model = model_name.camelize.constantize
# 1 - get all the column names
lines << %(\t\t# Database fields\n)
model.columns.each do |c|
gql_type = ""
gql_type = case c.type.to_s.camelize
when "Date", "Datetime"
"GraphQL::Types::ISO8601DateTime"
when "Integer"
# check for enum
enum_names = model.defined_enums.keys
gql_type = enum_names.include?(c.name) ? "String" : "Integer"
when "Decimal", "Float"
"Float"
when "Uuid", "Inet", "Text"
"String"
else
c.type.to_s.camelize
end
lines << %(\t\tfield :#{c.name}, #{gql_type}, null: #{c.null}\n)
end
lines << %(\n)
lines << %(\t\t# Relationships\n)
# 2 - Put all of the relationships into the file body
model.reflections.keys.filter do |key|
["attachments_attachments", "attachments_blobs", "versions"].exclude? key
end.each do |association_key|
reflection = model.reflections[association_key]
# i - Insert a new union type if this relationship has a "type" attribute (i.e. polymorphic)
if model.reflections[association_key].type
new_union_types[reflection.type] = [] if new_union_types[reflection.type].nil?
new_union_types[reflection.type] << %(#{model_name.camelize}Type)
end
lines << %(\t\tfield :#{association_key}, [Types::Query::#{reflection.class_name}Type], null: true\n)
end
lines << %(\n)
# 2 - get the derived methods
lines << %(\t\t# Derived fields\n)
get_model_derived_methods(model_name).each do |method_name|
lines << %(\t\tdef #{method_name}\n)
lines << %(\t\t\tobject.#{method_name}\n)
lines << %(\t\tend\n\n)
end
# Finish the boilerplate
lines << %(\tend\n)
lines << %(end)
# 4 - write it to a new type file
write_file_by_lines("app/graphql/types/query/#{model_name}_type.rb", lines)
end
# Build all the union types that were identified while going through each of the models
new_union_types.each do |key, arr_of_types|
# build some boilerplate for the new union GQL Type
union_file_lines = [
"module Types::Query\n",
"\tclass #{key.camelize} < Types::BaseUnion\n",
"\t\tpossible_types #{arr_of_types.uniq.join(', ')}\n",
"\n",
"\t\tdef self.resolve_type(obj, ctx)\n",
"\t\t\t\"\#{obj.class}Type\".constantize\n",
"\t\tend\n",
"\tend\n",
"end\n"
]
write_file_by_lines("app/graphql/types/query/#{key.underscore}.rb", union_file_lines)
end
end
end
# frozen_string_literal: true
namespace :generate do
desc "create custom models"
task models: :environment do
# Include helper data
require_relative "../../generation_data/gov_1003_mapping"
require_relative "./generator_helpers"
include GeneratorHelpers
include Gov1003Mapping
def gather_all_fields
all_fields = []
gov_mapping_hash.each do |_section, section_content|
fields = section_content[:fields]
fields.each do |field|
all_fields << field unless all_fields.include?(field)
next if field[:dependent_fields].nil?
field[:dependent_fields].each do |_dfield|
all_fields << field unless all_fields.include?(field)
end
end
end
return all_fields
end
# pull derived fields from gov 1003 mapping for custom methods
def add_derived_methods(fields, entity)
output = []
derived_fields = []
fields.each do |field|
next unless field[:derived] && field[:entity] == entity && !derived_fields.include?(field[:db_field_name])
derived_fields << field[:db_field_name]
output << "\tdef #{field[:db_field_name].downcase.gsub(/[^\w]/, '_')}\n"
output << "\t\t# @note #{field[:notes]}\n"
output << "\t\treturn nil\n"
output << "\tend\n\n"
end
return output
end
# pull derived fields from gov 1003 mapping for custom methods
def define_enums(fields, entity)
output = []
defined_enums_fields = []
fields.each do |field|
unless field[:entity] == entity && field[:enum].present? && !defined_enums_fields.include?(field[:db_field_name])
next
end
defined_enums_fields << field[:db_field_name]
output << "\tenum #{field[:db_field_name]}: [\n"
field[:enum].each { |e| output << "\t\t :#{e.downcase.gsub(/[^\w]/, '_')},\n" }
output << "\t]\n"
end
return output
end
models_to_ignore = ["application_record", "event"]
model_names_and_filepaths.filter do |(model, _file_path)|
models_to_ignore.exclude? model
end.each do |(model, model_file_path)|
puts "#{model}, path: #{model_file_path}"
lines = []
lines << "class #{model.capitalize} < ApplicationRecord\n"
lines << "\t# Relationships #\n"
# use relationship hash to insert relatiionships
lines << "\thas_paper_trail\n\n"
# binding.pry if model == 'document'
if entity_data_for_models[model.to_sym]
entity_data_for_models[model.to_sym][:relationships]&.each do |r|
lines << "\t#{r}\n"
end
end
# Universal validations
lines << "\n\n\t# Validations #\n"
lines << "\t# @todo add validations\n"
fields = gather_all_fields
# Define Enums
lines << "\n\n\t# Enum Definitions #\n"
lines.concat(define_enums(fields, model))
# Derived Methods
lines << "\n\n\t# Custom Derived Methods #\n\n"
lines.concat(add_derived_methods(fields, model))
lines << "end"
write_file_by_lines(model_file_path, lines)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment