rails-code-generator 0.1.1 → 0.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1787e88d10c2e47cbe9fdafa9ec47b61cb8dde03907191fcc5344f186f69cf9c
4
- data.tar.gz: 8bdfd7f36fd83cffdf6c57d7ecf35f345f7bac31434528a50b4935338b8ae2cb
3
+ metadata.gz: a58785634e9cb4a0e9ea51de9e3feed6953434eda056000455672489d3220b4f
4
+ data.tar.gz: d52276ce775667bb11a6802d5ee12a389587c0a29e2e42020503b276f242f90d
5
5
  SHA512:
6
- metadata.gz: a0181136e1aa0097fe93fdcc0c54931dd0b058f2a8e264184e6b877c9714d2cec6650231756daebfffe2a2a912b62aaeabc508782f550972e57856d906b15c02
7
- data.tar.gz: f1036a08400cfe4b7d522dd9fd4bf600d5f783b120c04306e0b75b7ddeea8482652ee72f49afaaf60033491f508ae5a0e9bb7bb1570fd76bdc0bbdb1b9546afa
6
+ metadata.gz: 97f6426558b8e1ddc60c35af5c931a1e12e628abdb7ca73cbd8641818242958e7179a9e8809e0d65c7d82e90d36e16a1a8654c5379c225cffaddc9f4a3561bfd
7
+ data.tar.gz: 88383886534fdc4954d7bf5b1a7291460324da0d1efec697879df8a7653d9ead08320b538e332471cbe9ac745c8cd294e71a3981459a98e40af4ae8db8b6c262
@@ -35,6 +35,10 @@ module RailsCodeGenerator
35
35
  template "shared/normalizer.rb", File.join("app/normalizers", "#{table_name}_normalizer.rb")
36
36
  end
37
37
 
38
+ def create_factory
39
+ template "shared/factory.rb", File.join("spec/factories", "#{singular_name}.rb")
40
+ end
41
+
38
42
  def create_request_specs
39
43
  template "shared/index_spec.rb", File.join("spec/requests", table_name, "index_spec.rb")
40
44
  template "shared/show_spec.rb", File.join("spec/requests", table_name, "show_spec.rb")
@@ -5,12 +5,13 @@ require "rails_helper"
5
5
  RSpec.describe "[POST] /<%= route_path %>", type: :request do
6
6
  Given(:path) { "/<%= route_path %>" }
7
7
  Given(:user) { create :user }
8
+ <%= request_spec_association_givens %>
8
9
 
9
10
  context "with valid params" do
10
11
  Given(:params) {
11
12
  {
12
13
  <%= table_name %>: {
13
- # TODO: add attributes
14
+ <%= request_params_attributes_list %>
14
15
  },
15
16
  }.to_json
16
17
  }
@@ -6,12 +6,13 @@ RSpec.describe "[PATCH] /<%= route_path %>/:id", type: :request do
6
6
  Given(:path) { "/<%= route_path %>/" + <%= singular_name %>.id.to_s }
7
7
  Given(:<%= singular_name %>) { create :<%= singular_name %> }
8
8
  Given(:user) { create :user }
9
+ <%= request_spec_association_givens %>
9
10
 
10
11
  context "with authenticated user & valid params" do
11
12
  Given(:params) {
12
13
  {
13
14
  <%= table_name %>: {
14
- name: "Changed <%= singular_name %> name",
15
+ <%= request_params_attributes_list %>
15
16
  },
16
17
  }.to_json
17
18
  }
@@ -21,6 +22,5 @@ RSpec.describe "[PATCH] /<%= route_path %>/:id", type: :request do
21
22
  Then { expect(response).to be_successful }
22
23
  And { expect(response_body[:data]).not_to be_empty }
23
24
  And { expect(response_body[:errors]).to be_empty }
24
- And { expect(response_body.dig(:data, :name)).to eq "Changed <%= singular_name %> name" }
25
25
  end
26
26
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :<%= singular_name %> do
5
+ <%= factory_definition_body %>
6
+ end
7
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  class <%= resource_normalizer_class %> < BaseResponseNormalizer
4
4
  attributes(
5
- <%= normalizer_attributes_list %>
5
+ <%= normalizer_attributes_list(include_id: true) %>
6
6
  )
7
7
  end
@@ -5,12 +5,13 @@ require "rails_helper"
5
5
  RSpec.describe "[POST] /<%= route_path %>", type: :request do
6
6
  Given(:path) { "/<%= route_path %>" }
7
7
  Given(:user) { create :user }
8
+ <%= request_spec_association_givens %>
8
9
 
9
10
  context "with valid params" do
10
11
  Given(:params) {
11
12
  {
12
13
  <%= singular_name %>: {
13
- # TODO: add attributes
14
+ <%= request_params_attributes_list %>
14
15
  },
15
16
  }.to_json
16
17
  }
@@ -6,12 +6,13 @@ RSpec.describe "[PATCH] /<%= route_path %>/:id", type: :request do
6
6
  Given(:path) { "/<%= route_path %>/#{<%= singular_name %>.id}" }
7
7
  Given(:<%= singular_name %>) { create :<%= singular_name %> }
8
8
  Given(:user) { create :user }
9
+ <%= request_spec_association_givens %>
9
10
 
10
11
  context "with authenticated user & valid params" do
11
12
  Given(:params) {
12
13
  {
13
14
  <%= singular_name %>: {
14
- name: "Changed <%= singular_name %> name",
15
+ <%= request_params_attributes_list %>
15
16
  },
16
17
  }.to_json
17
18
  }
@@ -21,6 +22,5 @@ RSpec.describe "[PATCH] /<%= route_path %>/:id", type: :request do
21
22
  Then { expect(response).to be_successful }
22
23
  And { expect(response_body[:data]).not_to be_empty }
23
24
  And { expect(response_body[:errors]).to be_empty }
24
- And { expect(response_body.dig(:data, :name)).to eq "Changed <%= singular_name %> name" }
25
25
  end
26
26
  end
@@ -5,6 +5,7 @@ module Rails
5
5
  module Generator
6
6
  module ModelAttributes
7
7
  EXCLUDED_COLUMNS = %w[id created_at updated_at].freeze
8
+ AUTH_USER_ASSOCIATION = "user"
8
9
 
9
10
  DRY_TYPE_MAP = {
10
11
  "string" => :string,
@@ -24,6 +25,24 @@ module Rails
24
25
  "uuid" => :string,
25
26
  }.freeze
26
27
 
28
+ FAKER_EXPRESSION_MAP = {
29
+ "string" => "Faker::Lorem.word",
30
+ "text" => "Faker::Lorem.paragraph",
31
+ "citext" => "Faker::Lorem.word",
32
+ "integer" => "Faker::Number.number(digits: 5)",
33
+ "bigint" => "Faker::Number.number(digits: 5)",
34
+ "float" => "Faker::Number.decimal(l_digits: 2).to_f",
35
+ "decimal" => "Faker::Number.decimal(l_digits: 2)",
36
+ "boolean" => "Faker::Boolean.boolean",
37
+ "date" => "Faker::Date.forward(days: 30)",
38
+ "datetime" => "Faker::Time.forward(days: 30)",
39
+ "timestamp" => "Faker::Time.forward(days: 30)",
40
+ "timestamptz" => "Faker::Time.forward(days: 30)",
41
+ "json" => "{}",
42
+ "jsonb" => "{}",
43
+ "uuid" => "Faker::Internet.uuid",
44
+ }.freeze
45
+
27
46
  def model_attributes
28
47
  @model_attributes ||= fetch_model_attributes
29
48
  end
@@ -34,8 +53,9 @@ module Rails
34
53
  format_symbol_list(names)
35
54
  end
36
55
 
37
- def normalizer_attributes_list
56
+ def normalizer_attributes_list(include_id: false)
38
57
  names = model_attribute_names
58
+ names = [:id, *names] if include_id
39
59
  return " # TODO: add attributes" if names.empty?
40
60
 
41
61
  format_symbol_list(names, indent: 4)
@@ -47,12 +67,60 @@ module Rails
47
67
  model_attributes.map { |attribute| validator_line_for(attribute) }.join("\n")
48
68
  end
49
69
 
70
+ def request_params_attributes_list(indent: 10)
71
+ padding = " " * indent
72
+ return "#{padding}# TODO: add attributes" if model_attributes.empty?
73
+
74
+ model_attributes.map { |attribute| "#{padding}#{attribute[:name]}: #{param_expression_for(attribute)}," }.join("\n")
75
+ end
76
+
77
+ def request_spec_association_givens
78
+ associations_for_givens.map { |association|
79
+ " Given(:#{association[:name]}) { create :#{association[:factory]} }"
80
+ }.join("\n")
81
+ end
82
+
83
+ def factory_definition_body
84
+ lines = factory_association_lines + factory_attribute_lines
85
+ return " # TODO: add attributes" if lines.empty?
86
+
87
+ lines.join("\n")
88
+ end
89
+
50
90
  private
51
91
 
52
92
  def model_attribute_names
53
93
  model_attributes.map { |attribute| attribute[:name].to_sym }
54
94
  end
55
95
 
96
+ def model_associations
97
+ model_attributes.filter_map { |attribute| attribute[:association] }
98
+ .uniq { |association| association[:name] }
99
+ end
100
+
101
+ def associations_for_givens
102
+ model_associations.reject { |association| association[:name] == AUTH_USER_ASSOCIATION }
103
+ end
104
+
105
+ def factory_association_lines
106
+ model_associations.map { |association|
107
+ " association :#{association[:name]}, factory: :#{association[:factory]}"
108
+ }
109
+ end
110
+
111
+ def factory_attribute_lines
112
+ attributes = model_attributes.reject { |attribute| attribute[:association] }
113
+ return [] if attributes.empty?
114
+
115
+ width = attributes.map { |attribute| attribute[:name].length }.max
116
+
117
+ attributes.map { |attribute|
118
+ name = attribute[:name].ljust(width)
119
+ expression = FAKER_EXPRESSION_MAP.fetch(attribute[:type], "Faker::Lorem.word")
120
+ " #{name} { #{expression} }"
121
+ }
122
+ end
123
+
56
124
  def format_symbol_list(names, indent: 6)
57
125
  padding = " " * indent
58
126
  names.map { |name| "#{padding}:#{name}," }.join("\n")
@@ -69,21 +137,81 @@ module Rails
69
137
  end
70
138
  end
71
139
 
140
+ def param_expression_for(attribute)
141
+ association = attribute[:association]
142
+ return "#{association[:name]}.id" if association
143
+
144
+ FAKER_EXPRESSION_MAP.fetch(attribute[:type], "Faker::Lorem.word")
145
+ end
146
+
72
147
  def fetch_model_attributes
73
148
  klass = class_name.constantize
74
149
  return [] unless klass.respond_to?(:columns)
75
150
 
151
+ associations_by_fk, polymorphic_fks = belongs_to_association_maps(klass)
152
+
76
153
  klass.columns.filter_map do |column|
77
154
  next if EXCLUDED_COLUMNS.include?(column.name)
78
155
 
156
+ type = column.type.to_s
157
+
79
158
  {
80
159
  name: column.name,
81
- dry_type: DRY_TYPE_MAP[column.type.to_s],
82
- }
160
+ type: type,
161
+ dry_type: DRY_TYPE_MAP[type],
162
+ association: association_for_column(column.name, associations_by_fk, polymorphic_fks),
163
+ }.compact
83
164
  end
84
165
  rescue StandardError
85
166
  []
86
167
  end
168
+
169
+ def belongs_to_association_maps(klass)
170
+ return [{}, []] unless klass.respond_to?(:reflect_on_all_associations)
171
+
172
+ associations_by_fk = {}
173
+ polymorphic_fks = []
174
+
175
+ klass.reflect_on_all_associations(:belongs_to).each do |reflection|
176
+ foreign_key = reflection.foreign_key.to_s
177
+
178
+ if reflection.options[:polymorphic]
179
+ polymorphic_fks << foreign_key
180
+ next
181
+ end
182
+
183
+ associations_by_fk[foreign_key] = {
184
+ name: reflection.name.to_s,
185
+ factory: factory_name_for(reflection),
186
+ }
187
+ rescue StandardError
188
+ next
189
+ end
190
+
191
+ [associations_by_fk, polymorphic_fks]
192
+ rescue StandardError
193
+ [{}, []]
194
+ end
195
+
196
+ def factory_name_for(reflection)
197
+ reflection.klass.model_name.singular
198
+ rescue StandardError
199
+ reflection.name.to_s
200
+ end
201
+
202
+ def association_for_column(column_name, associations_by_fk, polymorphic_fks)
203
+ return if polymorphic_fks.include?(column_name)
204
+ return associations_by_fk[column_name] if associations_by_fk.key?(column_name)
205
+ return unless column_name.end_with?("_id")
206
+
207
+ association_name = column_name.delete_suffix("_id")
208
+ return if association_name.empty?
209
+
210
+ {
211
+ name: association_name,
212
+ factory: association_name,
213
+ }
214
+ end
87
215
  end
88
216
  end
89
217
  end
@@ -3,7 +3,7 @@
3
3
  module Rails
4
4
  module Code
5
5
  module Generator
6
- VERSION = "0.1.1"
6
+ VERSION = "0.1.3"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-code-generator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Sweat
@@ -48,6 +48,7 @@ files:
48
48
  - lib/generators/rails_code_generator/resources/templates/bulk/update.rb.tt
49
49
  - lib/generators/rails_code_generator/resources/templates/bulk/update_spec.rb.tt
50
50
  - lib/generators/rails_code_generator/resources/templates/bulk/update_validator.rb.tt
51
+ - lib/generators/rails_code_generator/resources/templates/shared/factory.rb.tt
51
52
  - lib/generators/rails_code_generator/resources/templates/shared/index_spec.rb.tt
52
53
  - lib/generators/rails_code_generator/resources/templates/shared/normalizer.rb.tt
53
54
  - lib/generators/rails_code_generator/resources/templates/shared/show_spec.rb.tt