inline_forms_installer 7.11.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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/bin/inline_forms +3 -0
  3. data/inline_forms_installer.gemspec +24 -0
  4. data/lib/inline_forms_installer/app_template.rb +15 -0
  5. data/lib/inline_forms_installer/creator.rb +127 -0
  6. data/lib/inline_forms_installer/installer_core.rb +889 -0
  7. data/lib/inline_forms_installer/version.rb +7 -0
  8. data/lib/inline_forms_installer.rb +34 -0
  9. data/lib/installer_templates/capistrano/Capfile +39 -0
  10. data/lib/installer_templates/capistrano/deploy.rb +59 -0
  11. data/lib/installer_templates/capistrano/production.rb +7 -0
  12. data/lib/installer_templates/dartsass/devise_main.scss +2 -0
  13. data/lib/installer_templates/dartsass/inline_forms_dartsass_builds.rb +14 -0
  14. data/lib/installer_templates/dartsass/inline_forms_main.scss +2 -0
  15. data/lib/installer_templates/example_app_tests/test/example_app/example_integration_test_case.rb +36 -0
  16. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb +73 -0
  17. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb +73 -0
  18. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_required_test.rb +21 -0
  19. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_opening_date_test.rb +49 -0
  20. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_photos_pagination_test.rb +440 -0
  21. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_row_turbo_test.rb +103 -0
  22. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_new_test.rb +70 -0
  23. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_top_level_pagination_test.rb +40 -0
  24. data/lib/installer_templates/example_app_tests/test/integration/example_app_apartment_versions_turbo_test.rb +120 -0
  25. data/lib/installer_templates/example_app_tests/test/integration/example_app_guest_access_test.rb +21 -0
  26. data/lib/installer_templates/example_app_tests/test/integration/example_app_photo_revert_test.rb +94 -0
  27. data/lib/installer_templates/example_app_tests/test/integration/example_app_photos_test.rb +22 -0
  28. data/lib/installer_templates/example_app_tests/test/integration/example_app_routing_test.rb +15 -0
  29. data/lib/installer_templates/example_app_tests/test/integration/example_app_turbo_layout_test.rb +25 -0
  30. data/lib/installer_templates/example_app_tests/test/integration/example_app_validation_hints_test.rb +40 -0
  31. data/lib/installer_templates/example_app_tests/test/models/example_app_apartment_name_validation_test.rb +16 -0
  32. data/lib/installer_templates/example_app_tests/test/models/example_app_apartment_photo_test.rb +26 -0
  33. data/lib/installer_templates/example_app_tests/test/models/example_app_paper_trail_changeset_test.rb +78 -0
  34. data/lib/installer_templates/example_app_tests/test/models/example_app_plain_text_rich_text_edge_cases_test.rb +46 -0
  35. data/lib/installer_templates/example_app_views/apartments/name_list.html.erb +26 -0
  36. data/lib/installer_templates/example_app_views/inline_forms/_header.html.erb +45 -0
  37. data/lib/installer_templates/unicorn/production.rb +39 -0
  38. metadata +120 -0
@@ -0,0 +1,889 @@
1
+ INSTALLER_ROOT = File.expand_path(ENV.fetch("INLINE_FORMS_INSTALLER_ROOT", File.expand_path("..", __dir__)))
2
+ INLINE_FORMS_ROOT = File.expand_path(ENV.fetch("INLINE_FORMS_ROOT", INSTALLER_ROOT))
3
+
4
+ # Rails 7 dropped --skip-gemfile, so `rails new` always writes its own Gemfile.
5
+ # Remove it so our `create_file` below does not prompt for overwrite.
6
+ remove_file 'Gemfile' if File.exist?('Gemfile')
7
+ create_file 'Gemfile', "# created by inline_forms #{ENV['inline_forms_version']} on #{Date.today}\n"
8
+
9
+ # `rails new` is invoked with whatever the system `rails` binary points at
10
+ # (often Rails 8.x once it lands in the global gemset), so the generated
11
+ # `config/application.rb` may carry Rails 7.1+/8.0 idioms (`load_defaults
12
+ # 8.0`, `config.autoload_lib(...)`). The Gemfile we write below pins
13
+ # `rails ~> 7.0.0`, so Rails 7.0 must be able to interpret application.rb;
14
+ # otherwise the first `bundle exec rails …` aborts with `Unknown version
15
+ # "8.0"` or `NoMethodError: undefined method 'autoload_lib'`.
16
+ if File.exist?('config/application.rb')
17
+ gsub_file 'config/application.rb',
18
+ /config\.load_defaults\s+\d+\.\d+/,
19
+ 'config.load_defaults 7.1'
20
+ # Strip Rails 7.1+ `config.autoload_lib(ignore: ...)` (and any surrounding
21
+ # explanatory comment block). Not supported on Rails 7.0.
22
+ gsub_file 'config/application.rb',
23
+ /^\s*#[^\n]*\n(\s*#[^\n]*\n)*\s*config\.autoload_lib\([^)]*\)\s*\n/,
24
+ ""
25
+ gsub_file 'config/application.rb',
26
+ /^\s*config\.autoload_lib\([^)]*\)\s*\n/,
27
+ ""
28
+ end
29
+
30
+ add_source 'https://rubygems.org'
31
+
32
+ gem 'cancancan'
33
+ gem 'carrierwave', '~> 3.1'
34
+ gem 'devise', '~> 5.0'
35
+ gem 'devise-i18n', '~> 1.16'
36
+ gem 'autoprefixer-rails'
37
+ # foundation-rails 6.7+ uses Dart Sass (`sass:math`); sass-rails/sassc removed.
38
+ # Visually tuned against foundation-rails ~> 6.6.2; current pin ~> 6.9 (6.9.0.x).
39
+ gem 'foundation-rails', '~> 6.9'
40
+ gem 'i18n-active_record', :git => 'https://github.com/acesuares/i18n-active_record.git'
41
+ # Pin to the inline_forms version bundled with this installer release.
42
+ # Set INLINE_FORMS_GEMFILE_PATH for maintainer local-path overrides only.
43
+ if ENV["INLINE_FORMS_GEMFILE_PATH"] && File.directory?(ENV["INLINE_FORMS_GEMFILE_PATH"])
44
+ gem "inline_forms", path: ENV["INLINE_FORMS_GEMFILE_PATH"]
45
+ else
46
+ gem "inline_forms", "~> #{ENV['inline_forms_version']}"
47
+ end
48
+ gem 'jquery-rails'
49
+ gem 'jquery-timepicker-rails'
50
+ # jQuery UI JavaScript (`//= require jquery.ui.all` in inline_forms.js). SCSS + PNGs
51
+ # are vendored in the inline_forms engine (Dart Sass cannot evaluate sass-rails
52
+ # `image-path()`). Pin matches former jquery-ui-sass-rails 4.0.3.x stack.
53
+ gem 'jquery-ui-rails', '4.0.3'
54
+ # Foundation Icons SCSS + fonts are vendored in the inline_forms engine (Dart Sass;
55
+ # foundation-icons-sass-rails depended on sass-rails).
56
+ gem 'mini_magick'
57
+ gem 'mysql2'
58
+ gem 'paper_trail', '~> 16.0'
59
+ gem 'rails-i18n', '~> 7.0'
60
+ gem 'rails-jquery-autocomplete'
61
+ gem 'rails', '~> 7.1.5'
62
+ gem 'rake'
63
+ gem 'rvm'
64
+ gem 'dartsass-rails'
65
+ # Rails 7 no longer adds sprockets-rails to the default Gemfile; declare it
66
+ # explicitly because the gem's own assets (foundation, jquery, etc.) live in
67
+ # app/assets and rely on the Sprockets pipeline.
68
+ gem 'sprockets-rails'
69
+ # Rails 7 default JavaScript tooling: importmap-rails replaces Webpacker.
70
+ gem 'importmap-rails'
71
+ # Hotwire/Turbo. Loaded from layouts as `<script type="module">`; inline flows
72
+ # use `<turbo-frame>` + HTML responses (see docs/ujs-to-turbo.md). Registers the
73
+ # `turbo_stream` MIME type for optional stream responses.
74
+ gem 'turbo-rails'
75
+ gem 'tabs_on_rails', :git => 'https://github.com/acesuares/tabs_on_rails.git', :branch => 'update_remote_before_action'
76
+ gem 'unicorn'
77
+ gem 'validation_hints', '~> 6.3'
78
+ gem 'will_paginate' #, git: 'https://github.com/acesuares/will_paginate.git'
79
+
80
+ gem_group :test do
81
+ # Rails 7 still expects Minitest 5; 6.x breaks the railties test runner.
82
+ gem 'minitest', '~> 5.25'
83
+ end
84
+
85
+ gem_group :development do
86
+ gem 'capistrano-bundler', require: false
87
+ gem 'capistrano-rails', require: false
88
+ gem 'capistrano', require: false
89
+ gem 'capistrano3-unicorn'
90
+ gem 'listen'
91
+ gem 'rvm-capistrano', :require => false
92
+ gem 'rvm1-capistrano3', require: false
93
+ gem 'seed_dump', '~> 0.5.3'
94
+ # Rails 6.1 ActiveRecord's sqlite3 adapter requires sqlite3 ~> 1.4; 2.x activates first and breaks.
95
+ gem 'sqlite3', '~> 1.4'
96
+ gem 'thin'
97
+ gem 'yaml_db'
98
+ end
99
+
100
+ gem_group :production do
101
+ gem 'mini_racer'
102
+ gem 'uglifier'
103
+ end
104
+
105
+ say "- Running bundle..."
106
+ run "gem install bundler"
107
+ vh_gem_dirs = [
108
+ ENV["VALIDATION_HINTS_ROOT"],
109
+ File.expand_path("~/validation_hints"),
110
+ File.expand_path("~/code/validation_hints"),
111
+ File.expand_path("../validation_hints", INSTALLER_ROOT)
112
+ ].compact.uniq
113
+ vh_gem = vh_gem_dirs.flat_map { |dir| Dir[File.join(dir, "validation_hints-*.gem")] }.sort.last
114
+ if vh_gem && File.file?(vh_gem)
115
+ say "- Installing #{File.basename(vh_gem)} (local build; not on RubyGems yet)..."
116
+ run "gem install #{vh_gem} --no-document"
117
+ end
118
+ run "bundle install"
119
+
120
+ say "- Dart Sass: inline_forms stylesheet entrypoints + initializer..."
121
+ copy_file File.join(INSTALLER_ROOT, "lib/installer_templates/dartsass/inline_forms_dartsass_builds.rb"),
122
+ "config/initializers/inline_forms_dartsass_builds.rb"
123
+ copy_file File.join(INSTALLER_ROOT, "lib/installer_templates/dartsass/inline_forms_main.scss"),
124
+ "app/assets/stylesheets/inline_forms_install/inline_forms_main.scss"
125
+ copy_file File.join(INSTALLER_ROOT, "lib/installer_templates/dartsass/devise_main.scss"),
126
+ "app/assets/stylesheets/inline_forms_install/devise_main.scss"
127
+
128
+ say "- Dart Sass: rails dartsass:install (builds/, manifest, Procfile.dev)..."
129
+ run "bundle exec rails dartsass:install"
130
+
131
+ say "- Dart Sass: drop default application.css (manifest links builds/*.css only)..."
132
+ remove_file "app/assets/stylesheets/application.css"
133
+
134
+ insert_into_file "test/test_helper.rb", <<~'DARTSASS_TEST', after: %(require "rails/test_help"\n)
135
+
136
+ # Dart Sass writes CSS to app/assets/builds; Sprockets does not compile .scss.
137
+ Rails.application.load_tasks
138
+ Rake::Task["dartsass:build"].invoke
139
+ DARTSASS_TEST
140
+
141
+ say "- Database setup: creating config/database.yml with development database #{ENV['database']}"
142
+ remove_file "config/database.yml" # the one that 'rails new' created
143
+ if ENV['using_sqlite'] == 'true'
144
+ create_file "config/database.yml", <<-END_DATABASEYML.strip_heredoc
145
+ development:
146
+ adapter: sqlite3
147
+ database: db/development.sqlite3
148
+ pool: 5
149
+ timeout: 5000
150
+
151
+ test:
152
+ adapter: sqlite3
153
+ database: db/test.sqlite3
154
+ pool: 5
155
+ timeout: 5000
156
+
157
+ END_DATABASEYML
158
+ else
159
+ create_file "config/database.yml", <<-END_DATABASEYML.strip_heredoc
160
+ development:
161
+ adapter: mysql2
162
+ database: <%= Rails.application.credentials[:db_name] %>
163
+ username: <%= Rails.application.credentials[:db_username] %>
164
+ password: <%= Rails.application.credentials[:db_password] %>
165
+ END_DATABASEYML
166
+
167
+ say "- Setting development database in credentials"
168
+ create_file "temp_development_database_credentials", <<-END_DEV_DB_CRED.strip_heredoc
169
+
170
+ # development database
171
+ db_name: #{app_name.downcase}_dev
172
+ db_username: #{app_name.downcase}_dev_user
173
+ db_password: #{app_name.downcase}_dev_password
174
+
175
+ END_DEV_DB_CRED
176
+
177
+ run "EDITOR='cat temp_development_database_credentials >> ' rails credentials:edit"
178
+
179
+ remove_file 'temp_development_database_credentials'
180
+
181
+ say "\n *** Please make sure to create a mysql development database with the following credentials:
182
+ db_name: #{app_name.downcase}_dev
183
+ db_username: #{app_name.downcase}_dev_user
184
+ db_password: #{app_name.downcase}_dev_password
185
+
186
+ or use 'rails credentials:edit' to change these values.\n\n", :red
187
+
188
+ end
189
+ append_file "config/database.yml", <<-END_DATABASEYML.strip_heredoc
190
+ production:
191
+ adapter: mysql2
192
+ database: <%= Rails.application.credentials[:db_name] %>
193
+ username: <%= Rails.application.credentials[:db_username] %>
194
+ password: <%= Rails.application.credentials[:db_password] %>
195
+ END_DATABASEYML
196
+
197
+ say "Setting production database in credentials"
198
+ create_file "temp_production_database_credentials", <<-END_PROD_DB_CRED.strip_heredoc
199
+
200
+ # production database
201
+ db_name: #{app_name.downcase}_prod
202
+ db_username: #{app_name.downcase}_prod_user
203
+ db_password:
204
+
205
+ END_PROD_DB_CRED
206
+
207
+ run "EDITOR='cat temp_production_database_credentials >> ' rails credentials:edit --environment production"
208
+
209
+ remove_file 'temp_production_database_credentials'
210
+
211
+ say "\n *** Please make sure to create a mysql production database and use 'rails credentials:edit' to set the password.\n\n", :red
212
+
213
+ say "- Devise install..."
214
+ run "bundle exec rails g devise:install"
215
+
216
+ say "- Create Devise route and add path_prefix..."
217
+
218
+ route <<-ROUTE.strip_heredoc
219
+ devise_for :users, :path_prefix => 'auth'
220
+ resources :users do
221
+ post 'revert', :on => :member
222
+ get 'list_versions', :on => :member
223
+ end
224
+ ROUTE
225
+
226
+ say "- Create devise migration file"
227
+
228
+ sleep 1 # to get unique migration number
229
+ create_file "db/migrate/" +
230
+ Time.now.utc.strftime("%Y%m%d%H%M%S") +
231
+ "_" +
232
+ "devise_create_users.rb", <<-DEVISE_MIGRATION.strip_heredoc
233
+ class DeviseCreateUsers < ActiveRecord::Migration[7.1]
234
+
235
+ def change
236
+ create_table(:users) do |t|
237
+ ## Database authenticatable
238
+ t.string :email, null: false, default: ""
239
+ t.string :encrypted_password, null: false, default: ""
240
+
241
+ ## Recoverable
242
+ t.string :reset_password_token
243
+ t.datetime :reset_password_sent_at
244
+
245
+ ## Rememberable
246
+ t.datetime :remember_created_at
247
+
248
+ ## Trackable
249
+ t.integer :sign_in_count, default: 0, null: false
250
+ t.datetime :current_sign_in_at
251
+ t.datetime :last_sign_in_at
252
+ t.string :current_sign_in_ip
253
+ t.string :last_sign_in_ip
254
+
255
+ ## Confirmable
256
+ # t.string :confirmation_token
257
+ # t.datetime :confirmed_at
258
+ # t.datetime :confirmation_sent_at
259
+ # t.string :unconfirmed_email # Only if using reconfirmable
260
+
261
+ ## Lockable
262
+ # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
263
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
264
+ # t.datetime :locked_at
265
+
266
+ t.string :name
267
+ t.integer :locale_id
268
+
269
+ t.timestamps
270
+ end
271
+
272
+ add_index :users, :email, unique: true
273
+ add_index :users, :reset_password_token, unique: true
274
+ # add_index :users, :confirmation_token, unique: true
275
+ # add_index :users, :unlock_token, unique: true
276
+ end
277
+ end
278
+ DEVISE_MIGRATION
279
+
280
+ say "- Create User Controller..."
281
+ create_file "app/controllers/users_controller.rb", <<-USERS_CONTROLLER.strip_heredoc
282
+ class UsersController < InlineFormsController
283
+ set_tab :user
284
+ end
285
+ USERS_CONTROLLER
286
+
287
+ say "- Create User Model..."
288
+ create_file "app/models/user.rb", <<-USER_MODEL.strip_heredoc
289
+ class User < ApplicationRecord
290
+
291
+ # devise options
292
+ devise :database_authenticatable
293
+ # devise :registerable # uncomment this if you want people to be able to register
294
+ devise :recoverable
295
+ devise :rememberable
296
+ devise :trackable
297
+ devise :validatable
298
+ # devise :token_authenticatable
299
+ # devise :confirmable,
300
+ # devise :lockable
301
+ # devise :timeoutable
302
+ # devise :omniauthable
303
+
304
+ # Setup accessible (or protected) attributes for your model
305
+ attr_writer :inline_forms_attribute_list
306
+ #attr_accessible :email, :password, :locale, :remember_me
307
+
308
+ belongs_to :locale
309
+ has_and_belongs_to_many :roles
310
+
311
+ # validations
312
+ validates :name, :presence => true
313
+
314
+ default_scope {order :name}
315
+
316
+ # pagination
317
+ attr_reader :per_page
318
+ @per_page = 7
319
+
320
+ has_paper_trail
321
+
322
+ def _presentation
323
+ "\#{name}"
324
+ end
325
+
326
+ def role?(role)
327
+ return !!self.roles.find_by_name(role)
328
+ end
329
+
330
+ def inline_forms_attribute_list
331
+ @inline_forms_attribute_list ||= [
332
+ [ :header_user_login, '', :header ],
333
+ [ :name, '', :text_field ],
334
+ [ :email, '', :text_field ],
335
+ [ :locale , '', :dropdown ],
336
+ [ :password, '', :devise_password_field ],
337
+ [ :header_user_roles, '', :header ],
338
+ [ :roles, '', :check_list ],
339
+ [ :header_user_other_stuff, '', :header ],
340
+ [ :encrypted_password, '', :info ],
341
+ [ :reset_password_token, '', :info ],
342
+ [ :reset_password_sent_at, '', :info],
343
+ [ :remember_created_at, '', :info ],
344
+ [ :sign_in_count, '', :info ],
345
+ [ :current_sign_in_at, '', :info ],
346
+ [ :last_sign_in_at, '', :info ],
347
+ [ :current_sign_in_ip, '', :info ],
348
+ [ :last_sign_in_ip, '', :info ],
349
+ [ :created_at, '', :info ],
350
+ [ :updated_at, '', :info ],
351
+ ]
352
+ end
353
+
354
+ def self.not_accessible_through_html?
355
+ false
356
+ end
357
+
358
+ def self.order_by_clause
359
+ nil
360
+ end
361
+
362
+ end
363
+ USER_MODEL
364
+
365
+ # Create Locales
366
+ say "- Create locales"
367
+ generate "inline_forms", "Locale name:string title:string users:has_many _enabled:yes _presentation:\#{title}"
368
+ append_to_file "db/seeds.rb", "Locale.create({ id: 1, name: 'en', title: 'English' })\n"
369
+
370
+ # Create Roles
371
+ say "- Create roles"
372
+ generate "inline_forms", "Role name:string description:text users:has_and_belongs_to_many _enabled:yes _presentation:\#{name}"
373
+ append_to_file "db/seeds.rb", "Role.create({ id: 1, name: 'superadmin', description: 'Super Admin can access all.' })\n"
374
+
375
+ # Create Admin User
376
+
377
+ say "- Adding admin user with email: #{ENV['email']}, password: #{ENV['password']} to seeds.rb"
378
+ append_to_file "db/seeds.rb", "User.create({ id: 1, email: '#{ENV['email']}', locale_id: 1, name: 'Admin', password: '#{ENV['password']}', password_confirmation: '#{ENV['password']}' })\n"
379
+
380
+
381
+ sleep 1 # to get unique migration number
382
+ create_file "db/migrate/" +
383
+ Time.now.utc.strftime("%Y%m%d%H%M%S") +
384
+ "_" +
385
+ "inline_forms_create_join_table_user_role.rb", <<-ROLES_MIGRATION.strip_heredoc
386
+ class InlineFormsCreateJoinTableUserRole < ActiveRecord::Migration[7.1]
387
+ def self.up
388
+ create_table :roles_users, :id => false, :force => true do |t|
389
+ t.integer :role_id
390
+ t.integer :user_id
391
+ end
392
+ execute 'INSERT INTO roles_users VALUES (1,1);'
393
+ end
394
+
395
+ def self.down
396
+ drop_table roles_users
397
+ end
398
+ end
399
+ ROLES_MIGRATION
400
+
401
+
402
+ say "- Installaing ZURB Foundation..."
403
+ #generate "foundation:install", "-f"
404
+
405
+ say "- Copy inline_forms_devise file for custom styles..."
406
+ copy_file File.join(INLINE_FORMS_ROOT, 'lib/generators/assets/stylesheets/inline_forms_devise.css'), 'app/assets/stylesheets/inline_forms_devise.css'
407
+
408
+ say "- Sprockets: link inline_forms_devise.css (logical path; dartsass:install drops link_directory ../stylesheets)..."
409
+ append_to_file "app/assets/config/manifest.js", "//= link inline_forms_devise.css\n"
410
+
411
+ say "- Add human_attribute_name in app/models/application_record.rb"
412
+ remove_file 'app/models/application_record.rb' # the one that 'rails new' created
413
+ copy_file File.join(INLINE_FORMS_ROOT, 'lib/generators/templates/application_record.rb'), "app/models/application_record.rb"
414
+
415
+ say "- Install ActionText..."
416
+ run "bundle exec rails active_storage:install"
417
+ run "bundle exec rails action_text:install:migrations"
418
+ run "bundle install"
419
+
420
+ say "- Paper_trail install..."
421
+ # Upstream paper_trail (>= 13) detects MySQL via the live ActiveRecord connection,
422
+ # so the migration's InnoDB options are added only when the dev DB is mysql.
423
+ # For mysql installs the user has been instructed above to create the development
424
+ # database before continuing; for sqlite the file is created on first connection.
425
+ generate "paper_trail:install --with-changes"
426
+ # paper_trail emits two migrations in one second; the next generator would reuse that timestamp.
427
+ sleep 1
428
+
429
+ say "- Track ActionText (rich_text) edits with PaperTrail..."
430
+ # `has_rich_text :foo` stores the body in the separate `action_text_rich_texts`
431
+ # table, not on the parent model, so `has_paper_trail` on the parent never
432
+ # sees rich_text edits. The standard fix (recommended by paper_trail's
433
+ # maintainer in https://stackoverflow.com/q/55544935) is to declare
434
+ # `has_paper_trail` directly on `ActionText::RichText`. We surface those
435
+ # versions in the parent's versions panel from `inline_forms_versions_for`.
436
+ #
437
+ # Note: PaperTrail >= 16 raises if `has_paper_trail` is called twice on the
438
+ # same model, so this initializer must be the only place it's added to
439
+ # `ActionText::RichText` in the generated app.
440
+ create_file 'config/initializers/rich_text_paper_trail.rb', <<-PT_RICH_TEXT.strip_heredoc
441
+ # Generated by inline_forms.
442
+ ActiveSupport.on_load(:action_text_rich_text) do
443
+ has_paper_trail
444
+ end
445
+ PT_RICH_TEXT
446
+
447
+ say "- Configure ActiveRecord YAML permitted classes for PaperTrail changesets..."
448
+ # PaperTrail's YAML serializer (>= 13) uses `YAML.safe_load` and reads its
449
+ # allow-list from `ActiveRecord.yaml_column_permitted_classes`. Rails 7's
450
+ # default is `[Symbol]`, so any update that touches `updated_at`
451
+ # (an `ActiveSupport::TimeWithZone`) raises `Psych::DisallowedClass` inside
452
+ # `version.changeset`; PaperTrail rescues that and returns `{}`, which is why
453
+ # the inline_forms versions list rendered every changeset as `empty`. Permit
454
+ # the classes PT actually emits so `version.changeset` round-trips.
455
+ create_file 'config/initializers/paper_trail_yaml_safe_load.rb', <<-PT_YAML.strip_heredoc
456
+ # Generated by inline_forms.
457
+ # See https://github.com/paper-trail-gem/paper_trail and
458
+ # ActiveRecord::Coders::YAMLColumn safe-loading rules.
459
+ Rails.application.config.active_record.yaml_column_permitted_classes ||= []
460
+ Rails.application.config.active_record.yaml_column_permitted_classes |= [
461
+ Symbol,
462
+ Date,
463
+ Time,
464
+ BigDecimal,
465
+ ActiveSupport::TimeWithZone,
466
+ ActiveSupport::TimeZone,
467
+ ActiveSupport::HashWithIndifferentAccess
468
+ ]
469
+ ActiveRecord.yaml_column_permitted_classes |= Rails.application.config.active_record.yaml_column_permitted_classes
470
+ PT_YAML
471
+
472
+ # Create Translations
473
+ say "- Generate models and tables and views for translations..." # TODO Translations need to be done in inline_forms, and then generate a yml file, perhaps
474
+ generate "inline_forms", "InlineFormsLocale name:string inline_forms_translations:belongs_to _enabled:yes _presentation:\#{name}"
475
+ sleep 1 # unique migration timestamps per generator
476
+ generate "inline_forms", "InlineFormsKey name:string inline_forms_translations:has_many inline_forms_translations:associated _enabled:yes _presentation:\#{name}"
477
+ sleep 1
478
+ generate "inline_forms", "InlineFormsTranslation inline_forms_key:belongs_to inline_forms_locale:dropdown value:text interpolations:text is_proc:boolean _presentation:\#{value}"
479
+ # Plain long text uses :plain_text; ActionText-backed fields use :rich_text.
480
+ sleep 1 # to get unique migration number
481
+ create_file "db/migrate/" +
482
+ Time.now.utc.strftime("%Y%m%d%H%M%S") +
483
+ "_" +
484
+ "inline_forms_create_view_for_translations.rb", <<-VIEW_MIGRATION.strip_heredoc
485
+ class InlineFormsCreateViewForTranslations < ActiveRecord::Migration[7.1]
486
+ def self.up
487
+ execute 'CREATE VIEW translations
488
+ AS
489
+ SELECT L.name AS locale,
490
+ K.name AS thekey,
491
+ T.value AS value,
492
+ T.interpolations AS interpolations,
493
+ T.is_proc AS is_proc
494
+ FROM inline_forms_keys K, inline_forms_locales L, inline_forms_translations T
495
+ WHERE T.inline_forms_key_id = K.id AND T.inline_forms_locale_id = L.id '
496
+ end
497
+ def self.down
498
+ execute 'DROP VIEW translations'
499
+ end
500
+ end
501
+ VIEW_MIGRATION
502
+
503
+ say "- Creating application title via locales..."
504
+ create_file "config/locales/inline_forms_local.en.yml", <<-END_LOCALE.strip_heredoc
505
+ en:
506
+ inline_forms:
507
+ general:
508
+ application_title: #{app_name}
509
+ devise:
510
+ title_for_devise: #{app_name}
511
+ welcome: Welcome to #{app_name}!
512
+ END_LOCALE
513
+
514
+ say "- Migrating Database (only when using sqlite)"
515
+ run "bundle exec rake db:migrate" if ENV['using_sqlite'] == 'true'
516
+
517
+ say "- Seeding the database (only when using sqlite)"
518
+ run "bundle exec rake db:seed" if ENV['using_sqlite'] == 'true'
519
+
520
+ say "- Recreating ApplicationHelper to set application_name and application_title..."
521
+ remove_file "app/helpers/application_helper.rb" # the one that 'rails new' created
522
+ create_file "app/helpers/application_helper.rb", <<-END_APPHELPER.strip_heredoc
523
+ module ApplicationHelper
524
+ def application_name
525
+ '#{app_name}'
526
+ end
527
+ def application_title
528
+ '#{app_name}'
529
+ end
530
+ end
531
+ END_APPHELPER
532
+
533
+ say "- Creating inline_forms initializer"
534
+ create_file "config/initializers/inline_forms.rb", <<-END_INITIALIZER.strip_heredoc
535
+ Rails.application.reloader.to_prepare do
536
+ MODEL_TABS = %w()
537
+ end
538
+ END_INITIALIZER
539
+
540
+ say "- Recreating ApplicationController to add devise, cancan, I18n stuff..."
541
+ remove_file "app/controllers/application_controller.rb" # the one that 'rails new' created
542
+ create_file "app/controllers/application_controller.rb", <<-END_APPCONTROLLER.strip_heredoc
543
+ class ApplicationController < InlineFormsApplicationController
544
+ protect_from_forgery
545
+
546
+ # add whodunnit
547
+ before_action :set_paper_trail_whodunnit
548
+
549
+ # Comment next lines if you don't want Devise authentication
550
+ before_action :authenticate_user!
551
+ check_authorization unless: :devise_controller?
552
+
553
+ rescue_from CanCan::AccessDenied do |exception|
554
+ respond_to do |format|
555
+ format.json { head :forbidden, content_type: 'text/html' }
556
+ format.html { redirect_to main_app.root_url, notice: exception.message }
557
+ format.js { head :forbidden, content_type: 'text/html' }
558
+ end
559
+ end
560
+ # Comment previous lines if you don't want Devise authentication
561
+
562
+ # Uncomment next line if you want I18n (based on subdomain)
563
+ # before_action :set_locale
564
+
565
+ # Uncomment next line and specify default locale
566
+ # I18n.default_locale = :en
567
+
568
+ # Uncomment next line and specify available locales
569
+ # I18n.available_locales = [ :en, :nl, :pp ]
570
+
571
+ # Uncomment next nine line if you want locale based on subdomain, like 'it.example.com, de.example.com'
572
+ # def set_locale
573
+ # I18n.locale = extract_locale_from_subdomain || I18n.default_locale
574
+ # end
575
+ #
576
+ # def extract_locale_from_subdomain
577
+ # locale = request.subdomains.first
578
+ # return nil if locale.nil?
579
+ # I18n.available_locales.include?(locale.to_sym) ? locale.to_s : nil
580
+ # end
581
+ end
582
+ END_APPCONTROLLER
583
+
584
+ say "- Creating Ability model so that the superadmin can access all..."
585
+ create_file "app/models/ability.rb", <<-END_ABILITY.strip_heredoc
586
+ class Ability
587
+ include CanCan::Ability
588
+
589
+ def initialize(user)
590
+ # See the wiki for details: https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
591
+
592
+ user ||= User.new # guest user
593
+
594
+ # use this if you get stuck:
595
+ # if user.id == 1 #quick hack
596
+ # can :manage, :all
597
+ if user.role? :superadmin
598
+ can :manage, :all
599
+ else
600
+ # put restrictions for other users here
601
+ end
602
+ end
603
+ end
604
+ END_ABILITY
605
+
606
+ # devise mailer stuff
607
+ say "- Injecting devise mailer stuff in environments/production.rb..."
608
+ # strip_heredoc_with_indent(2) became strip_heredoc(2), but only in rails 4... :-(
609
+ insert_into_file "config/environments/production.rb", <<-DEVISE_MAILER_PROD_STUFF.strip_heredoc, :before => "end\n"
610
+
611
+ # for devise
612
+ config.action_mailer.default_url_options = { protocol: 'https', host: Rails.application.credentials[:smtp_app_host] }
613
+ config.action_mailer.delivery_method = :smtp
614
+ config.action_mailer.smtp_settings = {
615
+ address: Rails.application.credentials[:smtp_host],
616
+ enable_starttls_auto: true,
617
+ password: Rails.application.credentials[:smtp_password] ,
618
+ user_name: Rails.application.credentials[:smtp_username]
619
+ }
620
+
621
+ DEVISE_MAILER_PROD_STUFF
622
+
623
+ say "Setting production smtp settings in credentials"
624
+ create_file "temp_production_smtp_credentials", <<-END_PROD_SMTP_CRED.strip_heredoc
625
+
626
+ # devise mailer stuff for production:
627
+ smtp_app_host: APP_HOST
628
+ smtp_host: SMTP_HOST
629
+ smtp_username: USERNAME
630
+ smtp_password: PASSWORD
631
+
632
+ END_PROD_SMTP_CRED
633
+
634
+ run "EDITOR='cat temp_production_smtp_credentials >> ' rails credentials:edit --environment production"
635
+
636
+ remove_file 'temp_production_smtp_credentials'
637
+
638
+ say "- Injecting devise mailer stuff in environments/development.rb..."
639
+ # strip_heredoc_with_indent(2) became strip_heredoc(2), but only in rails 4... :-(
640
+ insert_into_file "config/environments/development.rb", <<-DEVISE_MAILER_DEV_STUFF.strip_heredoc, :before => "\nend\n"
641
+ # for devise
642
+ config.action_mailer.default_url_options = { protocol: 'http', host: 'localhost', port: 3000 }
643
+ config.action_mailer.delivery_method = :smtp
644
+ config.action_mailer.smtp_settings = {
645
+ address: Rails.application.credentials[:smtp_host],
646
+ enable_starttls_auto: true,
647
+ password: Rails.application.credentials[:smtp_password] ,
648
+ user_name: Rails.application.credentials[:smtp_username]
649
+ }
650
+
651
+ DEVISE_MAILER_DEV_STUFF
652
+
653
+ say "Setting development smtp settings in credentials"
654
+ create_file "temp_development_smtp_credentials", <<-END_DEV_SMTP_CRED.strip_heredoc
655
+
656
+ # devise mailers stuff for development:
657
+ smtp_app_host: APP_HOST
658
+ smtp_host: SMTP_HOST
659
+ smtp_username: USERNAME
660
+ smtp_password: PASSWORD
661
+
662
+ END_DEV_SMTP_CRED
663
+
664
+ run "EDITOR='cat temp_development_smtp_credentials >> ' rails credentials:edit"
665
+
666
+ remove_file 'temp_development_smtp_credentials'
667
+
668
+
669
+
670
+ # capify
671
+ say "- Capify..."
672
+ run 'bundle exec cap install'
673
+ remove_file "config/deploy.rb" # remove the file capify created!
674
+ copy_file File.join(INSTALLER_ROOT,'lib/installer_templates/capistrano/deploy.rb'), "config/deploy.rb"
675
+ remove_file "config/deploy/production.rb" # remove the production file capify created!
676
+ copy_file File.join(INSTALLER_ROOT,'lib/installer_templates/capistrano/production.rb'), "config/deploy/production.rb"
677
+ remove_file "Capfile" # remove the Capfile file capify created!
678
+ copy_file File.join(INSTALLER_ROOT,'lib/installer_templates/capistrano/Capfile'), "Capfile"
679
+
680
+ # Unicorn
681
+ say "- Unicorn Config..."
682
+ copy_file File.join(INSTALLER_ROOT,'lib/installer_templates/unicorn/production.rb'), "config/unicorn/production.rb"
683
+
684
+ # Git
685
+ say "- adding and committing to git..."
686
+
687
+ git add: "."
688
+ git commit: " -a -m 'Initial Commit'"
689
+
690
+ # example
691
+ if ENV['install_example'] == 'true'
692
+ say "\nInstalling example application..."
693
+ run 'bundle exec rails g inline_forms Photo name:string caption:string image:image_field description:rich_text apartment:belongs_to _presentation:\'#{name}\''
694
+ run 'bundle exec rails generate uploader Image'
695
+ run 'bundle exec rails g inline_forms Apartment name:string title:string opening_date:date description:rich_text photos:has_many photos:associated _enabled:yes _presentation:\'#{name}\''
696
+
697
+ say "- Apartment name is required..."
698
+ inject_into_file "app/models/apartment.rb",
699
+ "\n validates :name, presence: true\n",
700
+ after: " has_paper_trail\n"
701
+
702
+ # CarrierWave + PaperTrail history.
703
+ # PaperTrail snapshots the column scalar (the stored filename) on update,
704
+ # but CarrierWave's default `remove_previously_stored_files_after_update`
705
+ # deletes the old file on disk and re-uses the same filename, so a
706
+ # PaperTrail revert restores a filename whose bytes are gone.
707
+ # We keep every uploaded file on disk and namespace filenames with a
708
+ # per-upload token so successive uploads do not collide. See
709
+ # https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
710
+ # (Answers 2, 4 and 5).
711
+ say "- Configuring CarrierWave to keep previously stored files (PaperTrail history)..."
712
+ create_file "config/initializers/carrierwave.rb", <<-CWINIT.strip_heredoc
713
+ # Keep previously stored files on disk so PaperTrail-driven restore
714
+ # actually returns the previous image bytes. See
715
+ # https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
716
+ # The per-uploader overrides in app/uploaders/image_uploader.rb
717
+ # complement this by giving every upload a unique on-disk filename
718
+ # and by no-op'ing `remove!` so destroyed records keep their files.
719
+ CarrierWave.configure do |config|
720
+ config.remove_previously_stored_files_after_update = false
721
+ end
722
+ CWINIT
723
+
724
+ inject_into_file "app/uploaders/image_uploader.rb",
725
+ after: "class ImageUploader < CarrierWave::Uploader::Base\n" do
726
+ <<-RUBY.strip_heredoc.gsub(/^/, " ")
727
+ # PaperTrail history support. CarrierWave's default behaviour wipes the
728
+ # previous file on update and reuses the same filename; PaperTrail only
729
+ # stores the column scalar, so a plain `version.reify; save!` restores a
730
+ # filename whose bytes are gone. The knobs below preserve every byte:
731
+ #
732
+ # * `remove_previously_stored_files_after_update = false` is set
733
+ # globally in config/initializers/carrierwave.rb (covers
734
+ # `multi_image_field` uploaders too).
735
+ # * `remove!` is a no-op so hard-destroyed records keep their files
736
+ # and revert-after-destroy still finds the bytes on disk.
737
+ # * `filename` is prefixed with a per-upload UUID so successive
738
+ # uploads never collide on disk.
739
+ #
740
+ # Trade-off: files accumulate on disk; sweeping is out of scope.
741
+ # Source: https://stackoverflow.com/questions/9423279/papertrail-and-carrierwave
742
+ def remove!
743
+ # no-op: keep the file so PaperTrail revert can restore it.
744
+ end
745
+
746
+ def filename
747
+ # CarrierWave 3.x calls `filename` again after storing to record the
748
+ # persisted name; at that point `original_filename` may be nil and we
749
+ # must still return the memoized name (see
750
+ # https://github.com/carrierwaveuploader/carrierwave/issues/2708).
751
+ @name ||= "\#{secure_token}-\#{original_filename}" if original_filename
752
+ @name
753
+ end
754
+
755
+ private
756
+
757
+ def secure_token
758
+ var = :"@\#{mounted_as}_secure_token"
759
+ model.instance_variable_get(var) || model.instance_variable_set(var, SecureRandom.uuid)
760
+ end
761
+ RUBY
762
+ end
763
+
764
+ say "- Lower Photo.per_page so the seeded gallery paginates..."
765
+ # The model template (lib/generators/templates/model.erb) emits
766
+ # attr_reader :per_page
767
+ # @per_page = 7
768
+ # which is a long-standing typo: `attr_reader :per_page` defines an
769
+ # *instance* method, then `@per_page = 7` (executed in the class body)
770
+ # actively *clobbers* the class-level per_page that will_paginate
771
+ # exposes via `class_attribute :per_page` (its singleton-ivar storage
772
+ # also lives on `@per_page`). Net effect: nothing reads 7 anywhere,
773
+ # and the class-level per_page silently reverts to will_paginate's
774
+ # 30-default. Replace the pair on Photo with a real `self.per_page = 5`
775
+ # so the seeded gallery (12 photos) actually paginates 5/5/2.
776
+ gsub_file "app/models/photo.rb",
777
+ /^\s*attr_reader\s+:per_page\s*\n\s*@per_page\s*=\s*\d+\s*\n/,
778
+ " self.per_page = 5\n"
779
+
780
+ run 'bundle exec rake db:migrate'
781
+
782
+ # Seed the photos gallery from a local `pics/` folder. The folder is
783
+ # *gitignored* in the gem source (so the built .gem stays small and
784
+ # the gallery images are not committed) which means INSTALLER_ROOT/pics
785
+ # exists only when the installer is run from the source repo, not when
786
+ # it is run from an installed gem on the developer's box. We therefore
787
+ # check, in order:
788
+ # 1. ENV['INLINE_FORMS_SEED_PICS'] -- explicit override path
789
+ # 2. INSTALLER_ROOT/pics -- monorepo / installer checkout
790
+ # 3. /home/code/inline_forms/pics -- local dev convention
791
+ # and copy whichever is found into the generated app's db/seed_images/.
792
+ # The migration generated below is what reads from db/seed_images at
793
+ # `db:migrate` / `db:test:prepare` time, so this copy is only ever a
794
+ # one-shot at app generation.
795
+ pics_candidates = [
796
+ ENV["INLINE_FORMS_SEED_PICS"],
797
+ File.join(INSTALLER_ROOT, "pics"),
798
+ "/home/code/inline_forms/pics",
799
+ ].compact
800
+ pics_src = pics_candidates.find { |p| Dir.exist?(p) }
801
+ if pics_src
802
+ seed_pics = Dir.glob(File.join(pics_src, "*.{jpg,jpeg,JPG,JPEG,png,PNG,gif,GIF}")).sort
803
+ if seed_pics.any?
804
+ say "- Copying #{seed_pics.size} sample photo(s) into db/seed_images/..."
805
+ empty_directory "db/seed_images"
806
+ seed_pics.each do |abs|
807
+ copy_file abs, File.join("db/seed_images", File.basename(abs))
808
+ end
809
+
810
+ say "- Generating Konferensha apartment + photos seed migration..."
811
+ sleep 1 # unique migration timestamp
812
+ seed_ts = Time.now.utc.strftime("%Y%m%d%H%M%S")
813
+ create_file "db/migrate/#{seed_ts}_seed_konferensha_photos.rb", <<-SEED_MIGRATION.strip_heredoc
814
+ class SeedKonferenshaPhotos < ActiveRecord::Migration[7.1]
815
+ # Seed an Apartment with a gallery of photos so the nested
816
+ # has_many list (apartments -> photos) has enough rows to
817
+ # trigger pagination. Driven by db/seed_images/, which the
818
+ # inline_forms installer copies from the gem's pics/ dir.
819
+ # Runs in development (via db:migrate) and against the test
820
+ # DB (via db:test:prepare), so integration tests can assert
821
+ # the paginated <turbo-frame> renders without seeding manually.
822
+ def up
823
+ apartment = Apartment.find_or_create_by!(name: "Konferensha") do |a|
824
+ a.title = "Konferensha sobre Papiamentu"
825
+ a.opening_date = Date.new(2020, 5, 18)
826
+ end
827
+
828
+ seed_dir = Rails.root.join("db", "seed_images")
829
+ return unless seed_dir.directory?
830
+
831
+ Dir.glob(seed_dir.join("*.{jpg,jpeg,png,gif}"), File::FNM_CASEFOLD).sort.each do |abs|
832
+ base = File.basename(abs)
833
+ next if Photo.exists?(name: base, apartment_id: apartment.id)
834
+ File.open(abs, "rb") do |io|
835
+ Photo.create!(
836
+ name: base,
837
+ caption: "Konferensha foto \#{base}",
838
+ apartment: apartment,
839
+ image: io
840
+ )
841
+ end
842
+ end
843
+ end
844
+
845
+ def down
846
+ apartment = Apartment.find_by(name: "Konferensha")
847
+ return unless apartment
848
+ apartment.photos.destroy_all
849
+ apartment.destroy
850
+ end
851
+ end
852
+ SEED_MIGRATION
853
+
854
+ run "bundle exec rake db:migrate"
855
+ end
856
+ end
857
+
858
+ remove_file 'public/index.html'
859
+
860
+ say "- Apartment name list demo (field-level inline edit without _show)..."
861
+ inject_into_file "app/controllers/apartments_controller.rb",
862
+ "\n skip_load_and_authorize_resource only: :name_list\n\n def name_list\n authorize! :read, Apartment\n @apartments = Apartment.accessible_by(current_ability).order(:id).limit(10)\n end\n",
863
+ after: "set_tab :apartment\n"
864
+
865
+ example_views_root = File.join(INSTALLER_ROOT, "lib/installer_templates/example_app_views")
866
+ Dir.glob(File.join(example_views_root, "**", "*")).sort.each do |abs|
867
+ next unless File.file?(abs)
868
+ rel = abs.delete_prefix(example_views_root + File::SEPARATOR).tr("\\", "/")
869
+ create_file File.join("app/views", rel), File.read(abs)
870
+ end
871
+
872
+ route 'get "apartments/name_list", to: "apartments#name_list", as: :apartment_name_list'
873
+ route "root :to => 'apartments#index'"
874
+
875
+ say "- Adding example app regression tests (bundle exec rails test)..."
876
+ example_tests_root = File.join(INSTALLER_ROOT, "lib/installer_templates/example_app_tests")
877
+ Dir.glob(File.join(example_tests_root, "**", "*.rb")).sort.each do |abs|
878
+ rel = abs.delete_prefix(example_tests_root + File::SEPARATOR).tr("\\", "/")
879
+ create_file rel, File.read(abs)
880
+ end
881
+
882
+ say "\nDone! Example app (Photo + Apartment) is ready.", :yellow
883
+ say " bundle exec rails test # example regression tests", :yellow
884
+ say " bundle exec rails s # then http://localhost:3000/apartments", :yellow
885
+ say " More menu → Apartment names (first 10) # /apartments/name_list", :yellow
886
+ say " Log in: #{ENV["email"]} / #{ENV["password"]}", :yellow
887
+ end
888
+ # done!
889
+ say "\nDone! Now make your tables with 'bundle exec rails g inline_forms ...", :yellow