factory_burgers 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/lib/assets/asset-manifest.json +23 -0
  3. data/lib/assets/bootstrap.4.0.0.min.css +6 -0
  4. data/lib/assets/favicon.ico +0 -0
  5. data/lib/assets/fonts/SpecialElite-Regular.ttf +0 -0
  6. data/lib/assets/index.html +1 -0
  7. data/lib/assets/layout.css +46 -0
  8. data/lib/assets/logo192.png +0 -0
  9. data/lib/assets/logo512.png +0 -0
  10. data/lib/assets/manifest.json +25 -0
  11. data/lib/assets/robots.txt +3 -0
  12. data/lib/assets/static/css/main.510d0fb6.chunk.css +2 -0
  13. data/lib/assets/static/css/main.510d0fb6.chunk.css.map +1 -0
  14. data/lib/assets/static/css/main.8c8b27cf.chunk.css +2 -0
  15. data/lib/assets/static/css/main.8c8b27cf.chunk.css.map +1 -0
  16. data/lib/assets/static/css/main.9d5b29c0.chunk.css +2 -0
  17. data/lib/assets/static/css/main.9d5b29c0.chunk.css.map +1 -0
  18. data/lib/assets/static/css/main.ff283a2d.chunk.css +2 -0
  19. data/lib/assets/static/css/main.ff283a2d.chunk.css.map +1 -0
  20. data/lib/assets/static/js/2.05989fca.chunk.js +3 -0
  21. data/lib/assets/static/js/2.05989fca.chunk.js.LICENSE.txt +47 -0
  22. data/lib/assets/static/js/2.05989fca.chunk.js.map +1 -0
  23. data/lib/assets/static/js/2.3cffa5b0.chunk.js +3 -0
  24. data/lib/assets/static/js/2.3cffa5b0.chunk.js.LICENSE.txt +41 -0
  25. data/lib/assets/static/js/2.3cffa5b0.chunk.js.map +1 -0
  26. data/lib/assets/static/js/2.3ff1dc5f.chunk.js +3 -0
  27. data/lib/assets/static/js/2.3ff1dc5f.chunk.js.LICENSE.txt +47 -0
  28. data/lib/assets/static/js/2.3ff1dc5f.chunk.js.map +1 -0
  29. data/lib/assets/static/js/2.42c06104.chunk.js +3 -0
  30. data/lib/assets/static/js/2.42c06104.chunk.js.LICENSE.txt +41 -0
  31. data/lib/assets/static/js/2.42c06104.chunk.js.map +1 -0
  32. data/lib/assets/static/js/2.72048743.chunk.js +3 -0
  33. data/lib/assets/static/js/2.72048743.chunk.js.LICENSE.txt +41 -0
  34. data/lib/assets/static/js/2.72048743.chunk.js.map +1 -0
  35. data/lib/assets/static/js/2.99b8c933.chunk.js +3 -0
  36. data/lib/assets/static/js/2.99b8c933.chunk.js.LICENSE.txt +47 -0
  37. data/lib/assets/static/js/2.99b8c933.chunk.js.map +1 -0
  38. data/lib/assets/static/js/2.bbbcdbd6.chunk.js +3 -0
  39. data/lib/assets/static/js/2.bbbcdbd6.chunk.js.LICENSE.txt +41 -0
  40. data/lib/assets/static/js/2.bbbcdbd6.chunk.js.map +1 -0
  41. data/lib/assets/static/js/2.d996d4ae.chunk.js +3 -0
  42. data/lib/assets/static/js/2.d996d4ae.chunk.js.LICENSE.txt +41 -0
  43. data/lib/assets/static/js/2.d996d4ae.chunk.js.map +1 -0
  44. data/lib/assets/static/js/3.27d693f5.chunk.js +2 -0
  45. data/lib/assets/static/js/3.27d693f5.chunk.js.map +1 -0
  46. data/lib/assets/static/js/3.29ce1833.chunk.js +2 -0
  47. data/lib/assets/static/js/3.29ce1833.chunk.js.map +1 -0
  48. data/lib/assets/static/js/3.566901ad.chunk.js +2 -0
  49. data/lib/assets/static/js/3.566901ad.chunk.js.map +1 -0
  50. data/lib/assets/static/js/3.996d9368.chunk.js +2 -0
  51. data/lib/assets/static/js/3.996d9368.chunk.js.map +1 -0
  52. data/lib/assets/static/js/3.ee90b0ac.chunk.js +2 -0
  53. data/lib/assets/static/js/3.ee90b0ac.chunk.js.map +1 -0
  54. data/lib/assets/static/js/3.f6dcabfc.chunk.js +2 -0
  55. data/lib/assets/static/js/3.f6dcabfc.chunk.js.map +1 -0
  56. data/lib/assets/static/js/main.06049a6c.chunk.js +2 -0
  57. data/lib/assets/static/js/main.06049a6c.chunk.js.map +1 -0
  58. data/lib/assets/static/js/main.0d902f02.chunk.js +2 -0
  59. data/lib/assets/static/js/main.0d902f02.chunk.js.map +1 -0
  60. data/lib/assets/static/js/main.1aaab429.chunk.js +2 -0
  61. data/lib/assets/static/js/main.1aaab429.chunk.js.map +1 -0
  62. data/lib/assets/static/js/main.2d0b71bd.chunk.js +2 -0
  63. data/lib/assets/static/js/main.2d0b71bd.chunk.js.map +1 -0
  64. data/lib/assets/static/js/main.5b37e0b8.chunk.js +2 -0
  65. data/lib/assets/static/js/main.5b37e0b8.chunk.js.map +1 -0
  66. data/lib/assets/static/js/main.6d8f3ae8.chunk.js +2 -0
  67. data/lib/assets/static/js/main.6d8f3ae8.chunk.js.map +1 -0
  68. data/lib/assets/static/js/main.7725e8ca.chunk.js +2 -0
  69. data/lib/assets/static/js/main.7725e8ca.chunk.js.map +1 -0
  70. data/lib/assets/static/js/main.7c6faf79.chunk.js +2 -0
  71. data/lib/assets/static/js/main.7c6faf79.chunk.js.map +1 -0
  72. data/lib/assets/static/js/main.7d2ac052.chunk.js +2 -0
  73. data/lib/assets/static/js/main.7d2ac052.chunk.js.map +1 -0
  74. data/lib/assets/static/js/main.7d7d3506.chunk.js +2 -0
  75. data/lib/assets/static/js/main.7d7d3506.chunk.js.map +1 -0
  76. data/lib/assets/static/js/main.93ba19ed.chunk.js +2 -0
  77. data/lib/assets/static/js/main.93ba19ed.chunk.js.map +1 -0
  78. data/lib/assets/static/js/main.9d0e4106.chunk.js +2 -0
  79. data/lib/assets/static/js/main.9d0e4106.chunk.js.map +1 -0
  80. data/lib/assets/static/js/main.9df3330b.chunk.js +2 -0
  81. data/lib/assets/static/js/main.9df3330b.chunk.js.map +1 -0
  82. data/lib/assets/static/js/main.b7479be0.chunk.js +2 -0
  83. data/lib/assets/static/js/main.b7479be0.chunk.js.map +1 -0
  84. data/lib/assets/static/js/main.c9276c68.chunk.js +2 -0
  85. data/lib/assets/static/js/main.c9276c68.chunk.js.map +1 -0
  86. data/lib/assets/static/js/main.d85be3fe.chunk.js +2 -0
  87. data/lib/assets/static/js/main.d85be3fe.chunk.js.map +1 -0
  88. data/lib/assets/static/js/main.d9e659d7.chunk.js +2 -0
  89. data/lib/assets/static/js/main.d9e659d7.chunk.js.map +1 -0
  90. data/lib/assets/static/js/main.dace488a.chunk.js +2 -0
  91. data/lib/assets/static/js/main.dace488a.chunk.js.map +1 -0
  92. data/lib/assets/static/js/main.f3fd366c.chunk.js +2 -0
  93. data/lib/assets/static/js/main.f3fd366c.chunk.js.map +1 -0
  94. data/lib/assets/static/js/main.f686dbca.chunk.js +2 -0
  95. data/lib/assets/static/js/main.f686dbca.chunk.js.map +1 -0
  96. data/lib/assets/static/js/runtime-main.38f920d2.js +2 -0
  97. data/lib/assets/static/js/runtime-main.38f920d2.js.map +1 -0
  98. data/lib/assets/static/js/runtime-main.5aecb345.js +2 -0
  99. data/lib/assets/static/js/runtime-main.5aecb345.js.map +1 -0
  100. data/lib/assets/static/js/runtime-main.63c7a71d.js +2 -0
  101. data/lib/assets/static/js/runtime-main.63c7a71d.js.map +1 -0
  102. data/lib/assets/static/js/runtime-main.d938a5a3.js +2 -0
  103. data/lib/assets/static/js/runtime-main.d938a5a3.js.map +1 -0
  104. data/lib/assets/static/js/runtime-main.e9b43cb3.js +2 -0
  105. data/lib/assets/static/js/runtime-main.e9b43cb3.js.map +1 -0
  106. data/lib/assets/static/js/runtime-main.fa834e43.js +2 -0
  107. data/lib/assets/static/js/runtime-main.fa834e43.js.map +1 -0
  108. data/lib/assets/static/media/SpecialElite-Regular.9dc7a59a.ttf +0 -0
  109. data/lib/assets/static/media/logo.6ce24c58.svg +1 -0
  110. data/lib/factory_burgers.rb +13 -0
  111. data/lib/factory_burgers/app.rb +17 -0
  112. data/lib/factory_burgers/builder.rb +32 -0
  113. data/lib/factory_burgers/cheating.rb +39 -0
  114. data/lib/factory_burgers/factory_bot_adapter.rb +44 -0
  115. data/lib/factory_burgers/init.rb +9 -0
  116. data/lib/factory_burgers/introspection.rb +31 -0
  117. data/lib/factory_burgers/middleware/build.rb +62 -0
  118. data/lib/factory_burgers/middleware/data.rb +17 -0
  119. data/lib/factory_burgers/middleware/static.rb +35 -0
  120. data/lib/factory_burgers/models/factory.rb +98 -0
  121. data/lib/factory_burgers/models/factory_output.rb +38 -0
  122. data/lib/factory_burgers/presenter_builder.rb +41 -0
  123. data/lib/factory_burgers/presenters.rb +46 -0
  124. data/lib/factory_burgers/presenters/base.rb +29 -0
  125. data/lib/factory_burgers/sequence_cheater.rb +28 -0
  126. data/lib/factory_burgers/sequence_injector.rb +25 -0
  127. metadata +251 -0
@@ -0,0 +1,44 @@
1
+ module FactoryBurgers
2
+ module FactoryBotAdapter
3
+ module_function
4
+
5
+ def factory_bot
6
+ FactoryBot
7
+ end
8
+
9
+ def version
10
+ factory_boy::VERSION
11
+ end
12
+
13
+ def numeric_version
14
+ verion.split(".").map(&:to_i)
15
+ end
16
+
17
+ def major_version
18
+ numeric_versionnn.first
19
+ end
20
+
21
+ # TODO: support non-v6 versions
22
+ def load_factories
23
+ FactoryBot::V6.load_factories
24
+ end
25
+
26
+ def factories
27
+ FactoryBot::Internal.factories
28
+ end
29
+
30
+ def sequences
31
+ FactoryBot::Internal.sequences
32
+ end
33
+ end
34
+
35
+ module FactoryBotAdapter
36
+ module FactoryBot::V6
37
+ module_function
38
+
39
+ def load_factories
40
+ FactoryBurgers::FactoryBotAdapter.factory_bot.reload
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ require Pathname(__dir__).join("factory_bot_adapter.rb")
2
+
3
+ begin
4
+ require "factory_bot"
5
+ rescue LoadError
6
+ raise LoadError, "Could not load factory_bot. Please make sure it is installed."
7
+ end
8
+
9
+ FactoryBurgers::FactoryBotAdapter.load_factories
@@ -0,0 +1,31 @@
1
+ module FactoryBurgers
2
+ module Introspection
3
+ module_function
4
+
5
+ def factories
6
+ FactoryBot::Internal.factories.sort_by(&:name)
7
+ end
8
+
9
+ # Return a list of factories for a model instance's associations
10
+ def association_factories(klass)
11
+ buildable_associations(klass).flat_map do |assoc|
12
+ factories_for_class(assoc.klass).map do |factory|
13
+ {
14
+ association: assoc,
15
+ factory: factory,
16
+ }
17
+ end
18
+ end
19
+ end
20
+
21
+ def factories_for_class(klass)
22
+ factories.select do |factory|
23
+ factory.build_class.ancestors.include?(klass)
24
+ end
25
+ end
26
+
27
+ def buildable_associations(klass)
28
+ klass.reflect_on_all_associations.reject { |assoc| assoc.is_a?(ActiveRecord::Reflection::ThroughReflection) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ require "logger"
2
+ require "json"
3
+
4
+ module FactoryBurgers
5
+ module Middleware
6
+ # TODO: extract controller methods into controller-like classes
7
+ class Build
8
+ def call(env)
9
+ params = paramters(env)
10
+ factory = params.fetch("factory")
11
+ traits = params["traits"]&.keys
12
+ attributes = attribute_overrides(params)
13
+ owner = get_resource_owner(params)
14
+ resource = FactoryBurgers::Builder.new.build(factory, traits, attributes, owner)
15
+ object_data = FactoryBurgers::Models::FactoryOutput.new(resource).data
16
+ response_data = {ok: true, data: object_data}
17
+ return [200, {"Content-Type" => "application/json"}, [JSON.dump(response_data)]]
18
+ rescue ActiveRecord::RecordInvalid => err
19
+ log_error(err)
20
+ return [422, {"Content-Type" => "application/json"}, [JSON.dump({ok: false, error: err.message})]]
21
+ rescue StandardError => err
22
+ log_error(err)
23
+ return [500, {"Content-Type" => "application/json"}, [JSON.dump({ok: false, error: err.message})]]
24
+ end
25
+
26
+ def request(env)
27
+ Rack::Request.new(env)
28
+ end
29
+
30
+ def paramters(env)
31
+ request(env).params
32
+ end
33
+
34
+ def attribute_overrides(params)
35
+ attribute_items = params["attributes"] || []
36
+ attribute_items = attribute_items.reject{ |attr| !attr["name"].present? }
37
+ return attribute_items.map { |attr| [attr["name"], attr["value"]] }.to_h
38
+ end
39
+
40
+ # TODO: make params explicit
41
+ def get_resource_owner(params)
42
+ return nil if params[:owner_type].blank? || params[:owner_id].blank? || params[:owner_association].blank?
43
+
44
+ valid_owner_types = ActiveRecord::Base.descendants.map(&:name)
45
+ raise "Danger, will Robinson! #{params[:owner_type]} is an impostor!" if !valid_owner_types.include?(params[:owner_type])
46
+
47
+ klass = params[:owner_type].constantize
48
+ raise "Danger, will Robinson! #{params[:owner_association]} is an impostor!" if !klass.reflections.include?(params[:owner_association])
49
+
50
+ return params[:owner_type].constantize.find(params[:owner_id])
51
+ end
52
+
53
+ def log_error(error)
54
+ logger.error("#{error.class}: #{error.message}\n" + error.backtrace&.join("\n").to_s)
55
+ end
56
+
57
+ def logger
58
+ @logger ||= Logger.new($stdout)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ require "json"
2
+
3
+ module FactoryBurgers
4
+ module Middleware
5
+ class Data
6
+ def call(env)
7
+ factories = FactoryBot.factories.sort_by(&:name)
8
+ factory_data = factories.map { |factory| factory_data(factory) }
9
+ return [200, {"Content-Type" => "application/json"}, [JSON.dump(factory_data)]]
10
+ end
11
+
12
+ def factory_data(factory)
13
+ FactoryBurgers::Models::Factory.new(factory).to_h
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ module FactoryBurgers
2
+ module Middleware
3
+ class Static
4
+ def call(env)
5
+ return slashpath_redirect(env["REQUEST_PATH"]) if slashpath_redirect?(env)
6
+
7
+ rack_static.call(env)
8
+ end
9
+
10
+ def slashpath_redirect?(env)
11
+ env["REQUEST_PATH"] == env["SCRIPT_NAME"] && env["REQUEST_PATH"][-1] != "/"
12
+ end
13
+
14
+ def slashpath_redirect(path)
15
+ return [
16
+ 302,
17
+ {'Location' => path + "/", 'Content-Type' => 'text/html', 'Content-Length' => '0'},
18
+ [],
19
+ ]
20
+ end
21
+
22
+ def rack_static
23
+ Rack::Static.new(nil, static_options)
24
+ end
25
+
26
+ def static_options
27
+ {urls: [""], root: asset_path, index: 'index.html'}
28
+ end
29
+
30
+ def asset_path
31
+ @asset_path ||= FactoryBurgers.root.join("assets/")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,98 @@
1
+ require "json"
2
+
3
+ module FactoryBurgers
4
+ module Models
5
+ # TODO: support transient attributes (f.definition.attributes ...?)
6
+ # TODO: support associations
7
+ class Factory
8
+ attr_reader :factory
9
+
10
+ def initialize(factory)
11
+ @factory = factory
12
+ end
13
+
14
+ def to_h
15
+ {
16
+ name: name,
17
+ class_name: class_name,
18
+ traits: traits.map(&:to_h),
19
+ attributes: attributes.map(&:to_h),
20
+ }
21
+ end
22
+
23
+ def to_json(*opts, &blk)
24
+ to_h.to_json(*opts, &blk)
25
+ end
26
+
27
+ def name
28
+ factory.name.to_s
29
+ end
30
+
31
+ def class_name
32
+ build_class.base_class.name
33
+ end
34
+
35
+ def traits
36
+ defined_traits.map { |trait| Trait.new(trait) }
37
+ end
38
+
39
+ def attributes
40
+ settable_columns.map { |col| Attribute.new(col) }
41
+ end
42
+
43
+ private
44
+
45
+ def build_class
46
+ factory.build_class
47
+ end
48
+
49
+ def settable_columns
50
+ factory.build_class.columns.reject { |col| col.name == build_class.primary_key }
51
+ end
52
+
53
+ def defined_traits
54
+ factory.definition.defined_traits
55
+ end
56
+ end
57
+
58
+ class Attribute
59
+ attr_reader :column
60
+
61
+ def initialize(column)
62
+ @column = column
63
+ end
64
+
65
+ def to_h
66
+ {name: name}
67
+ end
68
+
69
+ def to_json(*opts, &blk)
70
+ to_h.to_json(*opts, &blk)
71
+ end
72
+
73
+ def name
74
+ column.name
75
+ end
76
+ end
77
+
78
+ class Trait
79
+ attr_reader :trait
80
+
81
+ def initialize(trait)
82
+ @trait = trait
83
+ end
84
+
85
+ def to_h
86
+ {name: name}
87
+ end
88
+
89
+ def to_json(*opts, &blk)
90
+ to_h.to_json(*opts, &blk)
91
+ end
92
+
93
+ def name
94
+ trait.name
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,38 @@
1
+ module FactoryBurgers
2
+ module Models
3
+ class FactoryOutput
4
+ attr_reader :object, :presenter
5
+
6
+ # `object` is whatever the output of `Factory#create` was
7
+ def initialize(object)
8
+ @object = object
9
+ @presenter = FactoryBurgers::Presenters.presenter_for(object)
10
+ end
11
+
12
+ def data
13
+ # TODO: Sorting by name is a UI concern; move it there.
14
+ sorted_assocs = association_factories.sort_by { |item| item[:association].name }
15
+ # TODO: group type, attributes, and link into `object`, move keys into presenter
16
+ return {
17
+ type: presenter.type,
18
+ attributes: presenter.attributes,
19
+ link: presenter.link_path,
20
+ association_factories: sorted_assocs.map { |item| association_factory_data(item) },
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ def association_factories
27
+ FactoryBurgers::Introspection.association_factories(object.class)
28
+ end
29
+
30
+ def association_factory_data(assoc_factory)
31
+ {
32
+ association_name: assoc_factory[:association].name.to_s,
33
+ factory_name: assoc_factory[:factory].name.to_s,
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ module FactoryBurgers
2
+ class PresenterBuilder < BasicObject
3
+ def initialize(klass)
4
+ @presenter = ::Class.new(::FactoryBurgers::Presenters::Base)
5
+ @klass = klass
6
+ end
7
+
8
+ def build(&blk)
9
+ instance_eval(&blk)
10
+ return @presenter
11
+ end
12
+
13
+ def presents(name)
14
+ @presetner.presents(name)
15
+ end
16
+
17
+ def type(&blk)
18
+ @presenter.define_method(:type) do
19
+ blk.call(object)
20
+ end
21
+ end
22
+
23
+ def type(&blk)
24
+ @presenter.define_method(:type) do
25
+ blk.call(object)
26
+ end
27
+ end
28
+
29
+ def attributes(&blk)
30
+ @presenter.define_method(:attributes) do
31
+ blk.call(object)
32
+ end
33
+ end
34
+
35
+ def link_path(&blk)
36
+ @presenter.define_method(:link_path) do
37
+ blk.call(object)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ module FactoryBurgers
2
+ module Presenters
3
+ @presenters = {}
4
+
5
+ module_function
6
+
7
+ def present(klass, with: nil, &blk)
8
+ raise ArgumentError, "Provide `with` or block, but not both" if with && blk
9
+
10
+ presenter = with || build_presenter(klass, &blk)
11
+ @presenters[klass.to_s] = presenter
12
+ end
13
+
14
+ def purge!
15
+ @links = {}
16
+ @presenters = {}
17
+ end
18
+
19
+ def presenter_for(object)
20
+ presenter_class_for(object).new(object)
21
+ end
22
+
23
+ def presenter_class_for(object)
24
+ matching_class = object.class.ancestors.map(&:name).find do |class_name|
25
+ @presenters.key?(class_name)
26
+ end
27
+
28
+ return matching_class ? @presenters[matching_class] : FactoryBurgers::Presenters::Base
29
+ end
30
+
31
+ # TODO use this from FactoryOutput
32
+ def data_for(object)
33
+ presenter = presenter_for(object) or return nil
34
+ {
35
+ type: presenter.type,
36
+ attribuets: presenter.attributes,
37
+ link: presenter.link_path,
38
+ }
39
+ end
40
+
41
+ # block provided to `present`; build a presenter class
42
+ def build_presenter(klass, &blk)
43
+ PresenterBuilder.new(klass).build(&blk)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ module FactoryBurgers
2
+ module Presenters
3
+ class Base
4
+ class << self
5
+ def presents(name)
6
+ define_method(name) { object }
7
+ end
8
+ end
9
+
10
+ attr_reader :object
11
+
12
+ def initialize(object)
13
+ @object = object
14
+ end
15
+
16
+ def type
17
+ object.class.name
18
+ end
19
+
20
+ def attributes
21
+ object.attributes.slice("id", "name")
22
+ end
23
+
24
+ def link_path
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end