kanji-web 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/Rakefile +1 -0
  4. data/bin/kanji +6 -0
  5. data/lib/kanji.rb +5 -0
  6. data/lib/kanji/application.rb +37 -0
  7. data/lib/kanji/cli.rb +36 -0
  8. data/lib/kanji/cli/generate.rb +13 -0
  9. data/lib/kanji/container.rb +12 -0
  10. data/lib/kanji/errors.rb +10 -0
  11. data/lib/kanji/generate.rb +48 -0
  12. data/lib/kanji/generators/abstract_generator.rb +50 -0
  13. data/lib/kanji/generators/project.rb +155 -0
  14. data/lib/kanji/generators/type.rb +110 -0
  15. data/lib/kanji/graph.rb +1 -0
  16. data/lib/kanji/graph/coerce_type.rb +66 -0
  17. data/lib/kanji/graph/container.rb +18 -0
  18. data/lib/kanji/graph/helpers.rb +27 -0
  19. data/lib/kanji/graph/import.rb +8 -0
  20. data/lib/kanji/graph/query.rb +16 -0
  21. data/lib/kanji/graph/register_mutation.rb +43 -0
  22. data/lib/kanji/graph/register_object.rb +40 -0
  23. data/lib/kanji/graph/schema.rb +17 -0
  24. data/lib/kanji/import.rb +5 -0
  25. data/lib/kanji/instance_define.rb +11 -0
  26. data/lib/kanji/logger.rb +10 -0
  27. data/lib/kanji/repository.rb +53 -0
  28. data/lib/kanji/templates/.gitignore +11 -0
  29. data/lib/kanji/templates/.keep +0 -0
  30. data/lib/kanji/templates/.rspec +2 -0
  31. data/lib/kanji/templates/Gemfile +14 -0
  32. data/lib/kanji/templates/README.md.tt +12 -0
  33. data/lib/kanji/templates/Rakefile.tt +113 -0
  34. data/lib/kanji/templates/app/mutation_type.rb.tt +14 -0
  35. data/lib/kanji/templates/app/query_type.rb.tt +13 -0
  36. data/lib/kanji/templates/app/repositories/repo.rb.tt +7 -0
  37. data/lib/kanji/templates/app/schema.rb.tt +21 -0
  38. data/lib/kanji/templates/app/types.rb.tt +5 -0
  39. data/lib/kanji/templates/app/types/type.rb.tt +26 -0
  40. data/lib/kanji/templates/app/views/graphiql.html.erb +24 -0
  41. data/lib/kanji/templates/bin/console.tt +7 -0
  42. data/lib/kanji/templates/bin/setup +7 -0
  43. data/lib/kanji/templates/config.ru.tt +2 -0
  44. data/lib/kanji/templates/db/sample_data.rb +1 -0
  45. data/lib/kanji/templates/db/seed.rb +1 -0
  46. data/lib/kanji/templates/migration.rb.tt +10 -0
  47. data/lib/kanji/templates/spec/db_spec_helper.rb.tt +23 -0
  48. data/lib/kanji/templates/spec/factories/example.rb +9 -0
  49. data/lib/kanji/templates/spec/spec_helper.rb +61 -0
  50. data/lib/kanji/templates/spec/support/db/factory.rb +8 -0
  51. data/lib/kanji/templates/spec/support/db/helpers.rb.tt +13 -0
  52. data/lib/kanji/templates/system/boot.rb.tt +13 -0
  53. data/lib/kanji/templates/system/boot/graph.rb.tt +11 -0
  54. data/lib/kanji/templates/system/boot/monitor.rb.tt +9 -0
  55. data/lib/kanji/templates/system/boot/repos.rb.tt +23 -0
  56. data/lib/kanji/templates/system/boot/rom.rb.tt +37 -0
  57. data/lib/kanji/templates/system/boot/settings.rb.tt +10 -0
  58. data/lib/kanji/templates/system/project/application.rb.tt +35 -0
  59. data/lib/kanji/templates/system/project/container.rb.tt +13 -0
  60. data/lib/kanji/templates/system/project/import.rb.tt +5 -0
  61. data/lib/kanji/templates/system/project/settings.rb.tt +8 -0
  62. data/lib/kanji/type.rb +62 -0
  63. data/lib/kanji/type/argument.rb +12 -0
  64. data/lib/kanji/type/attribute.rb +14 -0
  65. data/lib/kanji/type/attribute_definer.rb +33 -0
  66. data/lib/kanji/type/class_interface.rb +156 -0
  67. data/lib/kanji/type/mutation.rb +13 -0
  68. data/lib/kanji/type/mutation_definer.rb +43 -0
  69. data/lib/kanji/types.rb +11 -0
  70. data/lib/kanji/types/callable.rb +30 -0
  71. data/lib/kanji/types/type_interface.rb +43 -0
  72. data/lib/kanji/version.rb +3 -0
  73. metadata +538 -0
@@ -0,0 +1 @@
1
+ require "kanji/graph/container"
@@ -0,0 +1,66 @@
1
+ require "graphql"
2
+
3
+ module Kanji
4
+ class Graph
5
+ class CoerceType
6
+ def self.call(type)
7
+ if array_type?(type)
8
+ member_type = get_member_type(type)
9
+ graphql_type = get_graphql_type(member_type)
10
+ graphql_type.to_list_type
11
+ else
12
+ get_graphql_type(type)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ TYPE_MAP = {
19
+ "String" => "String",
20
+ "Integer" => "Int",
21
+ "Float" => "Float",
22
+ "FalseClass" => "Boolean",
23
+ "TrueClass | FalseClass" => "Boolean",
24
+ "FalseClass | TrueClass" => "Boolean"
25
+ }
26
+
27
+ def self.get_graphql_type(type)
28
+ return type if type.is_a?(GraphQL::ObjectType)
29
+
30
+ type_string = TYPE_MAP[get_primitive_type(type)]
31
+
32
+ if type.optional?
33
+ GraphQL::Define::TypeDefiner.instance.send(type_string)
34
+ else
35
+ !GraphQL::Define::TypeDefiner.instance.send(type_string)
36
+ end
37
+ end
38
+
39
+ def self.get_primitive_type(type)
40
+ type.optional? ?
41
+ type.right.primitive.to_s :
42
+ type.primitive.to_s
43
+ end
44
+
45
+ def self.get_member_type(type)
46
+ member_type = type.options[:member] || type.type.options[:member]
47
+
48
+ if has_ancestor?(member_type, Kanji::Type)
49
+ member_type[:graphql_type]
50
+ else
51
+ member_type
52
+ end
53
+ end
54
+
55
+ def self.array_type?(type)
56
+ return type.primitive == Array if defined?(type.primitive)
57
+ defined?(type.type) && type.type.primitive == Array
58
+ end
59
+
60
+ def self.has_ancestor?(type, ancestor)
61
+ return false unless type.respond_to?(:ancestors)
62
+ type.ancestors.include?(ancestor)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ require "dry-container"
2
+ require "kanji/graph/register_object"
3
+ require "kanji/graph/register_mutation"
4
+ require "kanji/graph/query"
5
+ require "kanji/graph/schema"
6
+
7
+ module Kanji
8
+ class Graph
9
+ class Container
10
+ extend Dry::Container::Mixin
11
+
12
+ register :register_object, -> (params) { RegisterObject.new(params) }
13
+ register :register_mutation, -> (params) { RegisterMutation.new(params) }
14
+ register :query, Kanji::Graph::Query
15
+ register :schema, Kanji::Graph::Schema
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ require "dry-container"
2
+ require "dry-auto_inject"
3
+ require "kanji/graph/coerce_type"
4
+ require "graphql"
5
+
6
+ module Graph
7
+ class Helpers
8
+ extend Dry::Container::Mixin
9
+
10
+ Import = Dry::AutoInject(Graph::Helpers)
11
+
12
+ register :coerce_type, -> { Graph::CoerceType.new }
13
+
14
+ register :explain do
15
+ ->(schema) { schema.execute GraphQL::Introspection::INTROSPECTION_QUERY }
16
+ end
17
+
18
+ register :generate do
19
+ lambda do |schema, destination|
20
+ result = JSON.pretty_generate(explain(schema))
21
+ unless File.exist?(destination) && File.read(destination) == result
22
+ File.write(destination, result)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ require "dry-auto_inject"
2
+ require "kanji/graph/container"
3
+
4
+ module Kanji
5
+ class Graph
6
+ Import = Dry::AutoInject(Graph::Container)
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ require "graphql"
2
+
3
+ module Kanji
4
+ class Graph
5
+ class Query
6
+ def self.call(schema:, query:, variables: {}, context: {})
7
+ GraphQL::Query.new(
8
+ schema.call,
9
+ query,
10
+ variables: variables,
11
+ context: context
12
+ ).result
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ require "graphql"
2
+ require "dry-initializer"
3
+ require "kanji/types"
4
+ require "kanji/graph/coerce_type"
5
+
6
+ module Kanji
7
+ class Graph
8
+ class RegisterMutation
9
+ extend Dry::Initializer
10
+
11
+ option :return_type, Types::Class
12
+ option :attributes, Types::Strict::Array.member(Type::Attribute)
13
+ option :name, Types::Strict::String
14
+ option :description, Types::Strict::String, optional: true
15
+ option :resolve, Types::Callable
16
+
17
+ def call
18
+ return_type = self.return_type
19
+ attributes = self.attributes
20
+ name = self.name
21
+ description = self.description
22
+ resolve_proc = self.resolve
23
+ coercer = Graph::CoerceType
24
+
25
+ GraphQL::Field.define do
26
+ type -> { return_type }
27
+ name name
28
+ description description
29
+
30
+ attributes.each do |attribute|
31
+ argument(
32
+ attribute.name,
33
+ coercer.(attribute.type),
34
+ attribute.description
35
+ )
36
+ end
37
+
38
+ resolve resolve_proc
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ require "graphql"
2
+ require "dry-initializer"
3
+ require "kanji/types"
4
+ require "kanji/type/attribute"
5
+ require "kanji/graph/coerce_type"
6
+
7
+ module Kanji
8
+ class Graph
9
+ class RegisterObject
10
+ extend Dry::Initializer
11
+
12
+ option :attributes, Types::Strict::Array.member(Type::Attribute)
13
+ option :name, Types::Strict::String
14
+ option :description, Types::Strict::String, optional: true
15
+
16
+ def call
17
+ name = "#{self.name}Type"
18
+ attributes = self.attributes
19
+ description = self.description
20
+ coercer = Graph::CoerceType
21
+
22
+ GraphQL::ObjectType.define do
23
+ name name
24
+ description description
25
+
26
+ attributes.each do |attribute|
27
+ field attribute.name do
28
+ type -> { coercer.(attribute.type) }
29
+ description attribute.description
30
+
31
+ if attribute.resolve
32
+ resolve attribute.resolve
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ require "graphql"
2
+
3
+ module Kanji
4
+ class Graph
5
+ class Schema
6
+ def call(query_type, mutation_type)
7
+ GraphQL::Schema.define do
8
+ query -> { query_type }
9
+ mutation -> { mutation_type }
10
+
11
+ max_depth 10
12
+ max_complexity 200
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ require "kanji/container"
2
+
3
+ module Kanji
4
+ Import = Kanji::Container.injector
5
+ end
@@ -0,0 +1,11 @@
1
+ module Kanji
2
+ module InstanceDefine
3
+ def instance_define(*args)
4
+ args.each do |arg|
5
+ define_method "#{arg}" do |value|
6
+ instance_variable_set("@_#{arg}", value)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require "kanji/container"
2
+
3
+ Kanji::Container.finalize :logger do |container|
4
+ require "logger"
5
+
6
+ container.register(
7
+ :logger,
8
+ Logger.new(container.root.join("log/#{container.config.env}.log"))
9
+ )
10
+ end
@@ -0,0 +1,53 @@
1
+ require "rom-repository"
2
+ require "dry/core/inflector"
3
+
4
+ module Kanji
5
+ class Repository < ROM::Repository::Root
6
+ def klass
7
+ @_klass ||= self.class
8
+ end
9
+
10
+ def relation_name
11
+ @_relation_name ||= begin
12
+ name = Dry::Core::Inflector.demodulize(klass.name)
13
+ Dry::Core::Inflector.underscore(name)
14
+ end
15
+ end
16
+
17
+ def type
18
+ @_type ||= begin
19
+ name = Dry::Core::Inflector.demodulize(klass.name)
20
+ singular_name = Dry::Core::Inflector.singularize(name)
21
+ Dry::Core::Inflector.constantize("Types::#{singular_name}")
22
+ end
23
+ end
24
+
25
+ def relation
26
+ @_relation ||= relations[relation_name].map_to(type)
27
+ end
28
+
29
+ def value_object
30
+ @_value_object ||= type.resolve(:value_object)
31
+ end
32
+
33
+ def all
34
+ relation.to_a
35
+ end
36
+
37
+ def create(params)
38
+ pk = relation.insert(params)
39
+ relation.where(id: pk).one
40
+ end
41
+
42
+ def update(params)
43
+ relation.where(id: params["id"]).update(params)
44
+ relation.where(id: params["id"]).one
45
+ end
46
+
47
+ def destroy(id)
48
+ item = relation.where(id: id).one
49
+ relation.where(id: id).delete
50
+ item
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ # Ignore app settings (copy to .example.env.* to check in example files)
2
+ /.env*
3
+
4
+ # RSpec
5
+ /spec/examples.txt
6
+
7
+ # Logs
8
+ /log/*.log
9
+
10
+ # Temp files
11
+ tmp/*
File without changes
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "kanji", path: "~/code/kanji"
4
+ gem "pg"
5
+
6
+ group :development, :test do
7
+ gem "pry-byebug", platform: :mri
8
+ end
9
+
10
+ group :test do
11
+ gem "database_cleaner"
12
+ gem "rspec"
13
+ gem "rom-factory"
14
+ end
@@ -0,0 +1,12 @@
1
+ # <%= config[:camel_cased_app_name] %>
2
+
3
+ Welcome! You’ve generated an app using Kanji.
4
+
5
+ ## First steps
6
+
7
+ 1. Run `bundle`
8
+ 1. Review `.env` & `.env.test` (and make a copy to e.g. `.example.env` if you want example settings checked in)
9
+ 1. Create a `blog_development` database
10
+ 1. Add your own steps to `bin/setup`
11
+ 1. Run the app with `bundle exec shotgun -p 3000 -o 0.0.0.0 config.ru`
12
+ 1. Initialize git with `git init` and make your initial commit
@@ -0,0 +1,113 @@
1
+ require "bundler/setup"
2
+ require "pry-byebug" unless ENV["RACK_ENV"] == "production"
3
+ require "rom/sql/rake_task"
4
+ require "shellwords"
5
+ require_relative "system/<%= config[:underscored_project_name] %>/container"
6
+
7
+ begin
8
+ require "rspec/core/rake_task"
9
+ RSpec::Core::RakeTask.new :spec
10
+ task default: [:spec]
11
+ rescue LoadError
12
+ end
13
+
14
+ def db
15
+ <%= config[:camel_cased_app_name] %>::Container["persistence.db"]
16
+ end
17
+
18
+ def settings
19
+ <%= config[:camel_cased_app_name] %>::Container["settings"]
20
+ end
21
+
22
+ def database_uri
23
+ require "uri"
24
+ URI.parse(settings.database_url)
25
+ end
26
+
27
+ def postgres_env_vars(uri)
28
+ {}.tap do |vars|
29
+ vars["PGHOST"] = uri.host
30
+ vars["PGPORT"] = uri.port if uri.port
31
+ vars["PGUSER"] = uri.user if uri.user
32
+ vars["PGPASSWORD"] = uri.password if uri.password
33
+ end
34
+ end
35
+
36
+ namespace :db do
37
+ task :setup do
38
+ <%= config[:camel_cased_app_name] %>::Container.start :rom
39
+ end
40
+
41
+ task :settings do
42
+ <%= config[:camel_cased_app_name] %>::Container.start :settings
43
+ end
44
+
45
+ desc "Print current database schema version"
46
+ task version: :setup do
47
+ version =
48
+ if db.tables.include?(:schema_migrations)
49
+ db[:schema_migrations].order(:filename).last[:filename]
50
+ else
51
+ "not available"
52
+ end
53
+
54
+ puts "Current schema version: #{version}"
55
+ end
56
+
57
+ desc "Create database"
58
+ task create: :settings do
59
+ if system("which createdb", out: File::NULL)
60
+ uri = database_uri
61
+ system(postgres_env_vars(uri), "createdb #{Shellwords.escape(uri.path[1..-1])}")
62
+ else
63
+ puts "You must have Postgres installed to create a database"
64
+ exit 1
65
+ end
66
+ end
67
+
68
+ desc "Drop database"
69
+ task drop: :settings do
70
+ if system("which dropdb", out: File::NULL)
71
+ uri = database_uri
72
+ system(postgres_env_vars(uri), "dropdb #{Shellwords.escape(uri.path[1..-1])}")
73
+ else
74
+ puts "You must have Postgres installed to drop a database"
75
+ exit 1
76
+ end
77
+ end
78
+
79
+ desc "Migrate database up to latest migration available"
80
+ task :migrate do
81
+ # Enhance the migration task provided by ROM
82
+
83
+ # Once it finishes, dump the db structure
84
+ Rake::Task["db:structure:dump"].execute
85
+
86
+ # And print the current migration version
87
+ Rake::Task["db:version"].execute
88
+ end
89
+
90
+ namespace :structure do
91
+ desc "Dump database structure to db/structure.sql"
92
+ task :dump do
93
+ if system("which pg_dump", out: File::NULL)
94
+ uri = database_uri
95
+ system(postgres_env_vars(uri), "pg_dump -s -x -O #{Shellwords.escape(uri.path[1..-1])}", out: "db/structure.sql")
96
+ else
97
+ puts "You must have pg_dump installed to dump the database structure"
98
+ end
99
+ end
100
+ end
101
+
102
+ desc "Load seed data into the database"
103
+ task :seed do
104
+ seed_data = File.join("db", "seed.rb")
105
+ load(seed_data) if File.exist?(seed_data)
106
+ end
107
+
108
+ desc "Load a small, representative set of data so that the application can start in a useful state (for development)."
109
+ task :sample_data do
110
+ sample_data = File.join("db", "sample_data.rb")
111
+ load(sample_data) if File.exist?(sample_data)
112
+ end
113
+ end