inline_forms 8.0.4 → 8.1.1
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 +4 -4
- data/CHANGELOG.md +48 -0
- data/app/controllers/inline_forms_controller.rb +23 -20
- data/app/views/inline_forms/_list.html.erb +2 -1
- data/inline_forms.gemspec +1 -1
- data/lib/generators/inline_forms_addto_generator.rb +291 -0
- data/lib/generators/inline_forms_attribute_overrides.rb +85 -0
- data/lib/generators/inline_forms_generator.rb +18 -75
- data/lib/generators/templates/add_columns_migration.erb +7 -0
- data/lib/generators/templates/application_record.rb +32 -0
- data/lib/generators/templates/model.erb +7 -14
- data/lib/inline_forms/form_elements/check_list_helper.rb +3 -5
- data/lib/inline_forms/version.rb +1 -1
- data/test/inline_forms_addto_generator_test.rb +251 -0
- data/test/inline_forms_generator_test.rb +61 -0
- metadata +8 -13
- data/docs/git-deps-assessment.md +0 -93
- data/docs/jquery-widgets.md +0 -25
- data/docs/prompt/.gitignore +0 -5
- data/docs/prompt/test-the-example-app.md +0 -32
- data/docs/rails-8-phase4-audit.md +0 -39
- data/docs/rails-8-release.md +0 -56
- data/docs/turbo-stream-audit.md +0 -16
- data/docs/ujs-to-turbo.md +0 -207
- data/docs/zeitwerk-and-load-paths.md +0 -49
- data/lib/generators/assets/stylesheets/inline_forms.scss +0 -491
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
|
2
2
|
require "inline_forms"
|
|
3
|
+
require_relative "inline_forms_attribute_overrides"
|
|
3
4
|
module InlineForms
|
|
4
5
|
# == Usage
|
|
5
6
|
# This generator generates a migration, a model and a controller.
|
|
@@ -13,75 +14,12 @@ module InlineForms
|
|
|
13
14
|
# rails g example_generator Modelname attribute:type attribute:type ...
|
|
14
15
|
# an array with attributes and types is created for use in the generator.
|
|
15
16
|
#
|
|
16
|
-
# Rails::Generators::GeneratedAttribute creates, among others,
|
|
17
|
+
# Rails::Generators::GeneratedAttribute creates, among others, an attribute_type.
|
|
17
18
|
# This attribute_type maps column types to form attribute helpers like text_field.
|
|
18
|
-
# We override it
|
|
19
|
+
# We override it in `lib/generators/inline_forms_attribute_overrides.rb`
|
|
20
|
+
# (shared with `InlineFormsAddtoGenerator`).
|
|
19
21
|
#
|
|
20
22
|
class InlineFormsGenerator < Rails::Generators::NamedBase
|
|
21
|
-
Rails::Generators::GeneratedAttribute.class_eval do #:doc:
|
|
22
|
-
# Override Rails::Generators::GeneratedAttribute.valid_type? so that our
|
|
23
|
-
# custom field types (dropdown, check_list, image_field, rich_text, ...)
|
|
24
|
-
# pass through parsing. We do our own unknown-type detection later (with
|
|
25
|
-
# Thor::Error + --allow-unknown), so it is safe to accept everything here.
|
|
26
|
-
#
|
|
27
|
-
# Rails 6.1 used to rescue ActiveRecord::Base.connection failures, which
|
|
28
|
-
# masked the issue; Rails 7+ raises NameError when ActiveRecord is not
|
|
29
|
-
# loaded yet, breaking generator unit tests.
|
|
30
|
-
def self.valid_type?(_type)
|
|
31
|
-
true
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Deducts the column_type for migrations from the type.
|
|
35
|
-
#
|
|
36
|
-
# We first merge the Special Column Types with the Default Column Types,
|
|
37
|
-
# which has the effect that the Default Column Types with the same key override
|
|
38
|
-
# the Special Column Types.
|
|
39
|
-
#
|
|
40
|
-
# If the type is not in the merged hash, then column_type defaults to :unknown
|
|
41
|
-
#
|
|
42
|
-
# You are advised to check you migrations for the :unknown, because either you made a
|
|
43
|
-
# typo in the generator command line or you need to add a Form Element!
|
|
44
|
-
#
|
|
45
|
-
def column_type
|
|
46
|
-
SPECIAL_COLUMN_TYPES.merge(DEFAULT_COLUMN_TYPES).merge(RELATIONS).merge(SPECIAL_RELATIONS)[type] || :unknown
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Override the attribute_type to include our special column types.
|
|
50
|
-
#
|
|
51
|
-
# If a type is not in the Special Column Type hash, then the default
|
|
52
|
-
# column type hash is used, and if that fails, the attribute_type
|
|
53
|
-
# will be :unknown. Make sure to check your models for the :unknown.
|
|
54
|
-
#
|
|
55
|
-
def attribute_type
|
|
56
|
-
SPECIAL_COLUMN_TYPES.merge(RELATIONS).has_key?(type) ? type : DEFAULT_FORM_ELEMENTS[type] || :unknown
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def special_relation?
|
|
60
|
-
SPECIAL_RELATIONS.has_key?(type)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def relation?
|
|
64
|
-
RELATIONS.has_key?(type) || special_relation?
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def migration?
|
|
68
|
-
not ( column_type == :no_migration ||
|
|
69
|
-
name == "_presentation" ||
|
|
70
|
-
name == "_order" ||
|
|
71
|
-
name == "_enabled" ||
|
|
72
|
-
name == "_id" )
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def attribute?
|
|
76
|
-
not ( name == '_presentation' ||
|
|
77
|
-
name == '_order' ||
|
|
78
|
-
name == '_enabled' ||
|
|
79
|
-
name == "_id" ||
|
|
80
|
-
relation? )
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
end
|
|
85
23
|
argument :attributes, :type => :array, :banner => "[name:form_element]..."
|
|
86
24
|
class_option :allow_unknown, :type => :boolean, :default => false, :desc => "Allow unknown field types (legacy behavior: comment generated lines instead of failing)."
|
|
87
25
|
|
|
@@ -126,10 +64,7 @@ module InlineForms
|
|
|
126
64
|
@has_attached_files = "\n"
|
|
127
65
|
@presentation = "\n"
|
|
128
66
|
@order = "\n"
|
|
129
|
-
@
|
|
130
|
-
" \"name\"\n" +
|
|
131
|
-
" end\n" +
|
|
132
|
-
"\n"
|
|
67
|
+
@list_scopes = ""
|
|
133
68
|
@carrierwave_mounters = "\n"
|
|
134
69
|
@inline_forms_attribute_list = String.new
|
|
135
70
|
|
|
@@ -165,15 +100,23 @@ module InlineForms
|
|
|
165
100
|
" end\n" +
|
|
166
101
|
"\n"
|
|
167
102
|
end
|
|
168
|
-
|
|
103
|
+
# `_list_order:col` (preferred) and the legacy alias `_order:col`
|
|
104
|
+
# both emit the inline_forms_list scope plus a Ruby `<=>` for
|
|
105
|
+
# in-memory sort (used by check_list show on the association).
|
|
106
|
+
if attribute.name == '_order' || attribute.name == '_list_order'
|
|
107
|
+
if attribute.name == '_order'
|
|
108
|
+
say_status :deprecated,
|
|
109
|
+
"_order:#{attribute.type} is deprecated; use _list_order:#{attribute.type}",
|
|
110
|
+
:yellow
|
|
111
|
+
end
|
|
169
112
|
@order << " def <=>(other)\n" +
|
|
170
113
|
" self.#{attribute.type} <=> other.#{attribute.type}\n" +
|
|
171
114
|
" end\n" +
|
|
172
115
|
"\n"
|
|
173
|
-
@
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
116
|
+
@list_scopes << " scope :inline_forms_list, -> { order(:#{attribute.type}, :id) }\n"
|
|
117
|
+
end
|
|
118
|
+
if attribute.name == '_list_search'
|
|
119
|
+
@list_scopes << " scope :inline_forms_search, ->(q) { where(\"#{attribute.type} LIKE ?\", \"%\#{q}%\") }\n"
|
|
177
120
|
end
|
|
178
121
|
if attribute.attribute?
|
|
179
122
|
attribute.attribute_type == :unknown ? commenter = '#' : commenter = ' '
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
class ApplicationRecord < ActiveRecord::Base
|
|
2
2
|
self.abstract_class = true
|
|
3
3
|
|
|
4
|
+
# PaperTrail 16 defaults to `on: [:create, :update, :destroy, :touch]`.
|
|
5
|
+
# ActionText's `belongs_to :record, polymorphic: true, touch: true` (set by
|
|
6
|
+
# `has_rich_text`) calls `parent.touch` on every rich-text save, which
|
|
7
|
+
# produced a parent-side `update` version with an empty changeset — visible
|
|
8
|
+
# in the inline_forms versions panel as a meaningless "empty" row whose
|
|
9
|
+
# Restore link reifies the same state (no-op). Excluding `:touch` here
|
|
10
|
+
# suppresses that noise without affecting real attribute updates.
|
|
11
|
+
has_paper_trail on: [:create, :update, :destroy]
|
|
12
|
+
|
|
13
|
+
attr_writer :inline_forms_attribute_list
|
|
14
|
+
|
|
15
|
+
# will_paginate reads Klass.per_page (class level), not instance ivars.
|
|
16
|
+
self.per_page = 7
|
|
17
|
+
|
|
18
|
+
# Default list ordering for inline_forms #index/#create and nested
|
|
19
|
+
# associated lists. Subclasses override via
|
|
20
|
+
# scope :inline_forms_list, -> { order(:col, :id) }
|
|
21
|
+
# (typically emitted by `rails g inline_forms <Model> _list_order:<col>`).
|
|
22
|
+
# `all` is a no-op so the gem adds no ORDER BY by default; pair with
|
|
23
|
+
# explicit named scopes rather than `default_scope` so callers can
|
|
24
|
+
# `unscope`/`reorder` cleanly.
|
|
25
|
+
scope :inline_forms_list, -> { all }
|
|
26
|
+
|
|
27
|
+
# Default list search; no-op so the gem's search box is inert until a model
|
|
28
|
+
# opts in via
|
|
29
|
+
# scope :inline_forms_search, ->(q) { where("col LIKE ?", "%#{q}%") }
|
|
30
|
+
# (emitted by `_list_search:<col>` in the generator).
|
|
31
|
+
scope :inline_forms_search, ->(_q) { all }
|
|
32
|
+
|
|
4
33
|
# Wrapper for @model.human_attribute_name -> Model.human_attribute_name
|
|
5
34
|
def human_attribute_name(*args)
|
|
6
35
|
self.class.human_attribute_name(*args)
|
|
7
36
|
end
|
|
8
37
|
|
|
38
|
+
def self.not_accessible_through_html?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
9
41
|
end
|
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
class <%= name %> < ApplicationRecord
|
|
2
|
-
attr_reader :per_page
|
|
3
|
-
@per_page = 7
|
|
4
|
-
attr_writer :inline_forms_attribute_list
|
|
5
|
-
# PaperTrail 16 defaults to `on: [:create, :update, :destroy, :touch]`.
|
|
6
|
-
# ActionText's `belongs_to :record, polymorphic: true, touch: true` (set by
|
|
7
|
-
# `has_rich_text`) calls `parent.touch` on every rich-text save, which
|
|
8
|
-
# produced a parent-side `update` version with an empty changeset — visible
|
|
9
|
-
# in the inline_forms versions panel as a meaningless "empty" row whose
|
|
10
|
-
# Restore link reifies the same state (no-op). Excluding `:touch` here
|
|
11
|
-
# suppresses that noise without affecting real attribute updates.
|
|
12
|
-
has_paper_trail on: [:create, :update, :destroy]
|
|
13
2
|
<%= @carrierwave_mounters if @carrierwave_mounters.length > 1 -%>
|
|
14
3
|
<%= @belongs_to if @belongs_to.length > 1 -%>
|
|
15
4
|
<%= @has_many if @has_many.length > 1 -%>
|
|
@@ -17,14 +6,18 @@ class <%= name %> < ApplicationRecord
|
|
|
17
6
|
<%= @habtm if @habtm.length > 1 -%>
|
|
18
7
|
<%= @has_rich_text if @has_rich_text.length > 1 -%>
|
|
19
8
|
<%= @has_attached_files if @has_attached_files.length > 1 -%>
|
|
9
|
+
<% if @list_scopes && !@list_scopes.empty? -%>
|
|
10
|
+
|
|
11
|
+
<%= @list_scopes -%>
|
|
12
|
+
<% end -%>
|
|
20
13
|
<%= @presentation if @presentation.length > 1 -%>
|
|
21
14
|
<%= @inline_forms_attribute_list -%>
|
|
22
15
|
<%= @order if @order.length > 1 -%>
|
|
16
|
+
<% if @flag_not_accessible_through_html -%>
|
|
23
17
|
|
|
24
18
|
def self.not_accessible_through_html?
|
|
25
|
-
|
|
19
|
+
true
|
|
26
20
|
end
|
|
27
|
-
|
|
28
|
-
<%= @order_by_clause -%>
|
|
21
|
+
<% end -%>
|
|
29
22
|
|
|
30
23
|
end
|
|
@@ -19,11 +19,9 @@ module InlineForms
|
|
|
19
19
|
|
|
20
20
|
def check_list_edit(object, attribute)
|
|
21
21
|
object.send(attribute).build if object.send(attribute).empty?
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
values = object.send(attribute).first.class.name.constantize.order(attribute.to_s.singularize.camelcase.constantize.order_by_clause)
|
|
26
|
-
end
|
|
22
|
+
klass = object.send(attribute).first.class
|
|
23
|
+
values = cancan_enabled? ? klass.accessible_by(current_ability) : klass.all
|
|
24
|
+
values = values.merge(klass.inline_forms_list) if klass.respond_to?(:inline_forms_list)
|
|
27
25
|
out = ''
|
|
28
26
|
values.each do | item |
|
|
29
27
|
out << "<div class='row #{cycle('odd', 'even')}'>"
|
data/lib/inline_forms/version.rb
CHANGED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "logger"
|
|
7
|
+
require "rails"
|
|
8
|
+
require "rails/generators"
|
|
9
|
+
require "inline_forms"
|
|
10
|
+
require_relative "../lib/generators/inline_forms_addto_generator"
|
|
11
|
+
|
|
12
|
+
class InlineFormsAddtoGeneratorTest < Minitest::Test
|
|
13
|
+
GENERATOR_SHAPED_MODEL = <<~RUBY
|
|
14
|
+
class Widget < ApplicationRecord
|
|
15
|
+
|
|
16
|
+
belongs_to :category
|
|
17
|
+
|
|
18
|
+
def inline_forms_attribute_list
|
|
19
|
+
@inline_forms_attribute_list ||= [
|
|
20
|
+
[ :name , "name", :text_field ],
|
|
21
|
+
[ :category , "category", :belongs_to ],
|
|
22
|
+
]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
RUBY
|
|
27
|
+
|
|
28
|
+
INSTALLER_SHAPED_USER = <<~RUBY
|
|
29
|
+
class User < ApplicationRecord
|
|
30
|
+
|
|
31
|
+
devise :database_authenticatable
|
|
32
|
+
belongs_to :locale
|
|
33
|
+
has_and_belongs_to_many :roles
|
|
34
|
+
|
|
35
|
+
def _presentation
|
|
36
|
+
"\#{name}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def inline_forms_attribute_list
|
|
40
|
+
@inline_forms_attribute_list ||= [
|
|
41
|
+
[ :header_user_login, '', :header ],
|
|
42
|
+
[ :name, '', :text_field ],
|
|
43
|
+
[ :email, '', :text_field ],
|
|
44
|
+
[ :locale , '', :dropdown ],
|
|
45
|
+
[ :password, '', :devise_password_field ],
|
|
46
|
+
[ :header_user_roles, '', :header ],
|
|
47
|
+
[ :roles, '', :check_list ],
|
|
48
|
+
]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
RUBY
|
|
53
|
+
|
|
54
|
+
BARE_MODEL = <<~RUBY
|
|
55
|
+
class Bare < ApplicationRecord
|
|
56
|
+
end
|
|
57
|
+
RUBY
|
|
58
|
+
|
|
59
|
+
def setup
|
|
60
|
+
@destination_root = Dir.mktmpdir("inline_forms_addto_generator_test")
|
|
61
|
+
mkdir_p("app/models")
|
|
62
|
+
mkdir_p("db/migrate")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def teardown
|
|
66
|
+
FileUtils.remove_entry(@destination_root) if @destination_root && Dir.exist?(@destination_root)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_raises_when_model_file_missing
|
|
70
|
+
stdout, stderr = capture_io do
|
|
71
|
+
run_generator("DoesNotExist", "occupation:string")
|
|
72
|
+
end
|
|
73
|
+
output = "#{stdout}#{stderr}"
|
|
74
|
+
assert_includes(output, "rails g inline_forms DoesNotExist")
|
|
75
|
+
refute(File.exist?(File.join(@destination_root, "app/models/does_not_exist.rb")))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_adds_scalar_column_belongs_to_rich_text_and_image_field_to_existing_model
|
|
79
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
80
|
+
|
|
81
|
+
run_generator(
|
|
82
|
+
"Widget",
|
|
83
|
+
"occupation:string",
|
|
84
|
+
"organization:belongs_to",
|
|
85
|
+
"supplier:dropdown",
|
|
86
|
+
"bio:rich_text",
|
|
87
|
+
"avatar:image_field"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
model = read("app/models/widget.rb")
|
|
91
|
+
migration = read_single_addto_migration_for("widgets")
|
|
92
|
+
|
|
93
|
+
assert_includes(model, "belongs_to :organization")
|
|
94
|
+
assert_includes(model, "belongs_to :supplier")
|
|
95
|
+
assert_includes(model, "has_rich_text :bio")
|
|
96
|
+
assert_includes(model, "mount_uploader :avatar, AvatarUploader")
|
|
97
|
+
assert_includes(model, '[ :occupation , "occupation", :text_field ]')
|
|
98
|
+
# :belongs_to is a relation -> no row in inline_forms_attribute_list
|
|
99
|
+
# (matches InlineFormsGenerator semantics). :dropdown is not a relation
|
|
100
|
+
# at lookup time, so it does get a row.
|
|
101
|
+
refute_includes(model, '[ :organization , "organization", :belongs_to ]')
|
|
102
|
+
assert_includes(model, '[ :supplier , "supplier", :dropdown ]')
|
|
103
|
+
assert_includes(model, '[ :avatar , "avatar", :image_field ]')
|
|
104
|
+
|
|
105
|
+
assert_includes(migration, "add_column :widgets, :occupation, :string")
|
|
106
|
+
assert_includes(migration, "add_reference :widgets, :organization, foreign_key: true")
|
|
107
|
+
assert_includes(migration, "add_reference :widgets, :supplier, foreign_key: true")
|
|
108
|
+
assert_includes(migration, "add_column :widgets, :avatar, :string")
|
|
109
|
+
refute_includes(migration, ":bio")
|
|
110
|
+
refute_includes(migration, "create_table")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_idempotent_rerun_does_not_duplicate_lines
|
|
114
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
115
|
+
|
|
116
|
+
run_generator("Widget", "occupation:string", "organization:belongs_to")
|
|
117
|
+
sleep 1 # unique migration timestamp on second run
|
|
118
|
+
run_generator("Widget", "occupation:string", "organization:belongs_to")
|
|
119
|
+
|
|
120
|
+
model = read("app/models/widget.rb")
|
|
121
|
+
|
|
122
|
+
assert_equal(1, model.scan("belongs_to :organization").size)
|
|
123
|
+
assert_equal(1, model.scan('[ :occupation , "occupation", :text_field ]').size)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_appends_row_to_installer_shaped_user_attribute_list
|
|
127
|
+
write_model("user.rb", INSTALLER_SHAPED_USER)
|
|
128
|
+
|
|
129
|
+
run_generator("User", "occupation:string", "birthdate:date")
|
|
130
|
+
|
|
131
|
+
model = read("app/models/user.rb")
|
|
132
|
+
|
|
133
|
+
assert_includes(model, '[ :occupation , "occupation", :text_field ]')
|
|
134
|
+
assert_includes(model, '[ :birthdate , "birthdate", :date_select ]')
|
|
135
|
+
refute_match(/\]\s*\n\s*\[\s*:occupation/, model)
|
|
136
|
+
|
|
137
|
+
user_array_section = model[/@inline_forms_attribute_list \|\|=\s*\[(.|\n)*?\n\s*\]/]
|
|
138
|
+
assert(user_array_section, "could not locate @inline_forms_attribute_list array")
|
|
139
|
+
assert_match(/\[ :roles,.*\].*?\[ :occupation\b/m, user_array_section)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_bare_model_gets_fresh_attribute_list_method
|
|
143
|
+
write_model("bare.rb", BARE_MODEL)
|
|
144
|
+
|
|
145
|
+
out, _err = capture_io do
|
|
146
|
+
run_generator("Bare", "occupation:string")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
model = read("app/models/bare.rb")
|
|
150
|
+
|
|
151
|
+
assert_includes(out, "no inline_forms_attribute_list found")
|
|
152
|
+
assert_includes(model, "def inline_forms_attribute_list")
|
|
153
|
+
assert_includes(model, '[ :occupation , "occupation", :text_field ]')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_unknown_type_raises_thor_error_by_default
|
|
157
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
158
|
+
|
|
159
|
+
stdout, stderr = capture_io do
|
|
160
|
+
run_generator("Widget", "payload:not_a_real_type")
|
|
161
|
+
end
|
|
162
|
+
output = "#{stdout}#{stderr}"
|
|
163
|
+
assert_includes(output, "Unknown field type(s): payload:not_a_real_type")
|
|
164
|
+
assert_includes(output, "--allow-unknown")
|
|
165
|
+
|
|
166
|
+
refute_addto_migration_for("widgets")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_allow_unknown_keeps_legacy_commented_output
|
|
170
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
171
|
+
|
|
172
|
+
run_generator("Widget", "payload:not_a_real_type", "--allow-unknown")
|
|
173
|
+
|
|
174
|
+
model = read("app/models/widget.rb")
|
|
175
|
+
migration = read_single_addto_migration_for("widgets")
|
|
176
|
+
|
|
177
|
+
assert_includes(model, '[ :payload , "payload", :unknown ]')
|
|
178
|
+
assert_includes(migration, "# add_column :widgets, :payload, :unknown")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def test_install_time_only_names_are_rejected
|
|
182
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
183
|
+
|
|
184
|
+
%w[_no_model _no_migration _id _enabled].each do |forbidden|
|
|
185
|
+
stdout, stderr = capture_io do
|
|
186
|
+
run_generator("Widget", "#{forbidden}:yes")
|
|
187
|
+
end
|
|
188
|
+
output = "#{stdout}#{stderr}"
|
|
189
|
+
assert_includes(output, forbidden, "expected error to mention #{forbidden}")
|
|
190
|
+
assert_includes(output, "install-time only")
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_skips_replace_only_names_without_replace_flag
|
|
195
|
+
write_model("widget.rb", GENERATOR_SHAPED_MODEL)
|
|
196
|
+
|
|
197
|
+
out, _err = capture_io do
|
|
198
|
+
run_generator("Widget", "_list_order:title")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
assert_includes(out, "_list_order:title")
|
|
202
|
+
assert_includes(out, "skipped")
|
|
203
|
+
model = read("app/models/widget.rb")
|
|
204
|
+
refute_includes(model, "scope :inline_forms_list, -> { order(:title, :id) }")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_replace_flag_rewrites_list_order_scope
|
|
208
|
+
initial = GENERATOR_SHAPED_MODEL.sub(
|
|
209
|
+
/class Widget < ApplicationRecord\n/,
|
|
210
|
+
"class Widget < ApplicationRecord\n\n scope :inline_forms_list, -> { order(:name, :id) }\n def <=>(other)\n self.name <=> other.name\n end\n"
|
|
211
|
+
)
|
|
212
|
+
write_model("widget.rb", initial)
|
|
213
|
+
|
|
214
|
+
run_generator("Widget", "_list_order:title", "--replace")
|
|
215
|
+
|
|
216
|
+
model = read("app/models/widget.rb")
|
|
217
|
+
assert_includes(model, "scope :inline_forms_list, -> { order(:title, :id) }")
|
|
218
|
+
refute_includes(model, "order(:name, :id)")
|
|
219
|
+
assert_includes(model, "self.title <=> other.title")
|
|
220
|
+
refute_includes(model, "self.name <=> other.name")
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def run_generator(*args)
|
|
226
|
+
InlineForms::InlineFormsAddtoGenerator.start(args, destination_root: @destination_root)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def write_model(file, content)
|
|
230
|
+
File.write(File.join(@destination_root, "app/models", file), content)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def read(relative_path)
|
|
234
|
+
File.read(File.join(@destination_root, relative_path))
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def mkdir_p(relative_path)
|
|
238
|
+
FileUtils.mkdir_p(File.join(@destination_root, relative_path))
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def read_single_addto_migration_for(table_name)
|
|
242
|
+
files = Dir.glob(File.join(@destination_root, "db/migrate/*_inline_forms_add_to_#{table_name}_*.rb"))
|
|
243
|
+
assert_equal(1, files.size, "Expected one addto migration for #{table_name}, got #{files.inspect}")
|
|
244
|
+
File.read(files.first)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def refute_addto_migration_for(table_name)
|
|
248
|
+
files = Dir.glob(File.join(@destination_root, "db/migrate/*_inline_forms_add_to_#{table_name}_*.rb"))
|
|
249
|
+
assert_equal([], files, "Expected no addto migration for #{table_name}")
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -36,6 +36,12 @@ class InlineFormsGeneratorTest < Minitest::Test
|
|
|
36
36
|
migration = read_single_migration_for("things")
|
|
37
37
|
|
|
38
38
|
assert_includes(model, "class Thing < ApplicationRecord")
|
|
39
|
+
refute_includes(model, "has_paper_trail")
|
|
40
|
+
refute_includes(model, "attr_reader :per_page")
|
|
41
|
+
refute_includes(model, 'def self.not_accessible_through_html?')
|
|
42
|
+
refute_includes(model, "def self.order_by_clause")
|
|
43
|
+
refute_includes(model, "scope :inline_forms_list")
|
|
44
|
+
refute_includes(model, "scope :inline_forms_search")
|
|
39
45
|
assert_includes(model, "belongs_to :category")
|
|
40
46
|
assert_includes(model, "has_many :photos")
|
|
41
47
|
assert_includes(model, "[ :name , \"name\", :text_field ]")
|
|
@@ -100,6 +106,61 @@ class InlineFormsGeneratorTest < Minitest::Test
|
|
|
100
106
|
refute_includes(migration, "t.text :content")
|
|
101
107
|
end
|
|
102
108
|
|
|
109
|
+
def test_not_accessible_and_list_order_emits_scope_and_spaceship
|
|
110
|
+
run_generator(
|
|
111
|
+
"Photo",
|
|
112
|
+
"name:string",
|
|
113
|
+
"album:belongs_to",
|
|
114
|
+
"_presentation:\\#{name}",
|
|
115
|
+
"_list_order:caption"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
model = read("app/models/photo.rb")
|
|
119
|
+
assert_includes(model, "def self.not_accessible_through_html?\n true")
|
|
120
|
+
assert_includes(model, "scope :inline_forms_list, -> { order(:caption, :id) }")
|
|
121
|
+
assert_includes(model, "def <=>(other)")
|
|
122
|
+
assert_includes(model, "self.caption <=> other.caption")
|
|
123
|
+
refute_includes(model, "def self.order_by_clause")
|
|
124
|
+
refute_includes(model, "has_paper_trail")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_legacy_order_alias_still_emits_scope_with_deprecation_warning
|
|
128
|
+
stdout, stderr = capture_io do
|
|
129
|
+
run_generator(
|
|
130
|
+
"Photo",
|
|
131
|
+
"name:string",
|
|
132
|
+
"_presentation:\\#{name}",
|
|
133
|
+
"_order:caption"
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
output = "#{stdout}#{stderr}"
|
|
138
|
+
assert_includes(output, "_order:caption is deprecated")
|
|
139
|
+
assert_includes(output, "_list_order:caption")
|
|
140
|
+
|
|
141
|
+
model = read("app/models/photo.rb")
|
|
142
|
+
assert_includes(model, "scope :inline_forms_list, -> { order(:caption, :id) }")
|
|
143
|
+
refute_includes(model, "def self.order_by_clause")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_list_search_emits_search_scope
|
|
147
|
+
run_generator(
|
|
148
|
+
"Apartment",
|
|
149
|
+
"name:string",
|
|
150
|
+
"_enabled:yes",
|
|
151
|
+
"_list_order:name",
|
|
152
|
+
"_list_search:name",
|
|
153
|
+
"_presentation:\\#{name}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
model = read("app/models/apartment.rb")
|
|
157
|
+
assert_includes(model, "scope :inline_forms_list, -> { order(:name, :id) }")
|
|
158
|
+
assert_includes(model, "scope :inline_forms_search, ->(q) { where(\"name LIKE ?\", \"%\#{q}%\") }")
|
|
159
|
+
migration = read_single_migration_for("apartments")
|
|
160
|
+
refute_includes(migration, "_list_order")
|
|
161
|
+
refute_includes(migration, "_list_search")
|
|
162
|
+
end
|
|
163
|
+
|
|
103
164
|
def test_plain_text_generates_text_column_and_plain_text_form_element
|
|
104
165
|
run_generator("Note", "title:string", "description:plain_text")
|
|
105
166
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inline_forms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.
|
|
4
|
+
version: 8.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ace Suares
|
|
@@ -17,7 +17,7 @@ dependencies:
|
|
|
17
17
|
requirements:
|
|
18
18
|
- - ">="
|
|
19
19
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: 8.0
|
|
20
|
+
version: 8.1.0
|
|
21
21
|
- - "<"
|
|
22
22
|
- !ruby/object:Gem::Version
|
|
23
23
|
version: '9.0'
|
|
@@ -27,7 +27,7 @@ dependencies:
|
|
|
27
27
|
requirements:
|
|
28
28
|
- - ">="
|
|
29
29
|
- !ruby/object:Gem::Version
|
|
30
|
-
version: 8.0
|
|
30
|
+
version: 8.1.0
|
|
31
31
|
- - "<"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '9.0'
|
|
@@ -492,21 +492,14 @@ files:
|
|
|
492
492
|
- archived/form_elements/tree/app/helpers/form_elements/move.rb
|
|
493
493
|
- archived/form_elements/tree/app/views/inline_forms/_show_tree.html.erb
|
|
494
494
|
- archived/form_elements/tree/app/views/inline_forms/_tree.html.erb
|
|
495
|
-
- docs/git-deps-assessment.md
|
|
496
|
-
- docs/jquery-widgets.md
|
|
497
|
-
- docs/prompt/.gitignore
|
|
498
|
-
- docs/prompt/test-the-example-app.md
|
|
499
|
-
- docs/rails-8-phase4-audit.md
|
|
500
|
-
- docs/rails-8-release.md
|
|
501
|
-
- docs/turbo-stream-audit.md
|
|
502
|
-
- docs/ujs-to-turbo.md
|
|
503
|
-
- docs/zeitwerk-and-load-paths.md
|
|
504
495
|
- inline_forms.gemspec
|
|
505
496
|
- lib/generators/USAGE
|
|
506
|
-
- lib/generators/assets/stylesheets/inline_forms.scss
|
|
507
497
|
- lib/generators/assets/stylesheets/inline_forms_devise.css
|
|
498
|
+
- lib/generators/inline_forms_addto_generator.rb
|
|
499
|
+
- lib/generators/inline_forms_attribute_overrides.rb
|
|
508
500
|
- lib/generators/inline_forms_generator.rb
|
|
509
501
|
- lib/generators/templates/_inline_forms_tabs.html.erb
|
|
502
|
+
- lib/generators/templates/add_columns_migration.erb
|
|
510
503
|
- lib/generators/templates/application_record.rb
|
|
511
504
|
- lib/generators/templates/controller.erb
|
|
512
505
|
- lib/generators/templates/migration.erb
|
|
@@ -568,6 +561,7 @@ files:
|
|
|
568
561
|
- test/archived_form_elements_test.rb
|
|
569
562
|
- test/form_element_from_callee_test.rb
|
|
570
563
|
- test/inline_edit_polymorphic_path_test.rb
|
|
564
|
+
- test/inline_forms_addto_generator_test.rb
|
|
571
565
|
- test/inline_forms_generator_test.rb
|
|
572
566
|
- test/plain_text_configuration_test.rb
|
|
573
567
|
- test/test_helper.rb
|
|
@@ -600,6 +594,7 @@ test_files:
|
|
|
600
594
|
- test/archived_form_elements_test.rb
|
|
601
595
|
- test/form_element_from_callee_test.rb
|
|
602
596
|
- test/inline_edit_polymorphic_path_test.rb
|
|
597
|
+
- test/inline_forms_addto_generator_test.rb
|
|
603
598
|
- test/inline_forms_generator_test.rb
|
|
604
599
|
- test/plain_text_configuration_test.rb
|
|
605
600
|
- test/test_helper.rb
|