Class: Seams::Generators::TeamsGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Includes:
EjectAware, HostInjector
Defined in:
lib/generators/seams/teams/teams_generator.rb

Overview

Generates a canonical Teams engine on top of the generic engine scaffold. Models cover Team, Membership, Invitation; controllers cover team CRUD + membership management + invitation send/accept.

Wave 9 model: Teams is a peer to Accounts (not nested). A Teams::Membership joins Auth::Identity directly to a Teams::Team. The host-User Teamable concern is gone — Wave 9 dropped the canonical demo’s host User, so there’s nowhere to mix it into.

Run with: bin/rails generate seams:teams

Constant Summary collapse

ENGINE_NAME =
"teams"
DEFAULT_FEATURES =
%w[invitations roles].freeze

Constants included from EjectAware

EjectAware::EJECT_HEADER_PREFIX

Instance Method Summary collapse

Methods included from EjectAware

#ejected?, #template_unless_ejected

Methods included from HostInjector

#host_inject_gem, #host_inject_include_in_application_controller, #host_inject_include_in_user, #host_inject_mount, #host_uninject_gem, #host_uninject_include, #host_uninject_mount, #routes_draw_anchor

Instance Method Details

#create_base_engineObject



35
36
37
# File 'lib/generators/seams/teams/teams_generator.rb', line 35

def create_base_engine
  EngineGenerator.start([ENGINE_NAME], destination_root: destination_root)
end

#create_concernsObject



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/generators/seams/teams/teams_generator.rb', line 93

def create_concerns
  # Phase 4A — account scoping helper that pairs with Core's
  # TenantScoped. Mix into models that belong to a single team.
  template_unless_ejected "lib/concerns/account_scoped.rb.tt",
                          engine_path("lib/teams/concerns/account_scoped.rb")
  # `--with=roles` ships role-based controller filters.
  return unless features.include?("roles")

  template_unless_ejected "lib/concerns/authorization.rb.tt",
                          engine_path("lib/teams/concerns/authorization.rb")
end

#create_controllersObject



66
67
68
69
70
71
72
73
74
75
# File 'lib/generators/seams/teams/teams_generator.rb', line 66

def create_controllers
  template_unless_ejected "app/controllers/teams_controller.rb.tt",
                          engine_path("app/controllers/teams/teams_controller.rb")
  template_unless_ejected "app/controllers/memberships_controller.rb.tt",
                          engine_path("app/controllers/teams/memberships_controller.rb")
  return unless features.include?("invitations")

  template_unless_ejected "app/controllers/invitations_controller.rb.tt",
                          engine_path("app/controllers/teams/invitations_controller.rb")
end

#create_dummy_appObject



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/generators/seams/teams/teams_generator.rb', line 163

def create_dummy_app
  # Wave 9: no host User model in the dummy. The dummy ships a
  # slim Auth::Identity stub at app/models/auth/identity.rb so
  # the teams engine's boot-time dependency assertion (defined?
  # ::Auth::Identity) passes without pulling in the full auth
  # engine. The same stub also lets `create(:auth_identity)`
  # build real AR rows against the auth_identities table baked
  # into dummy_schema.
  Seams::Generators::DummyAppWriter.write!(
    engine_path: File.join(destination_root, "engines", ENGINE_NAME),
    engine_module: "Teams",
    mount_at: "/teams",
    schema: dummy_schema,
    host_user: dummy_host_identity,
    host_user_path: "app/models/auth/identity.rb"
  )
  template "spec/runtime/boot_spec.rb.tt",
           engine_path("spec/runtime/teams_boot_spec.rb")
end

#create_jobsObject



105
106
107
108
# File 'lib/generators/seams/teams/teams_generator.rb', line 105

def create_jobs
  template_unless_ejected "app/jobs/application_job.rb.tt",
                          engine_path("app/jobs/teams/application_job.rb")
end

#create_mailer_and_subscriberObject



110
111
112
113
114
115
116
117
118
119
# File 'lib/generators/seams/teams/teams_generator.rb', line 110

def create_mailer_and_subscriber
  return unless features.include?("invitations")

  template_unless_ejected "app/mailers/invitation_mailer.rb.tt",
                          engine_path("app/mailers/teams/invitation_mailer.rb")
  template_unless_ejected "app/views/invitation_mailer/invite.text.erb.tt",
                          engine_path("app/views/teams/invitation_mailer/invite.text.erb")
  template_unless_ejected "app/subscribers/invitation_subscriber.rb.tt",
                          engine_path("app/subscribers/teams/invitation_subscriber.rb")
end

#create_migrationsObject



121
122
123
124
125
126
127
128
129
130
# File 'lib/generators/seams/teams/teams_generator.rb', line 121

def create_migrations
  template "db/migrate/create_teams.rb.tt",
           engine_path("db/migrate/#{timestamp(0)}_create_teams.rb")
  template "db/migrate/create_team_memberships.rb.tt",
           engine_path("db/migrate/#{timestamp(1)}_create_team_memberships.rb")
  return unless features.include?("invitations")

  template "db/migrate/create_team_invitations.rb.tt",
           engine_path("db/migrate/#{timestamp(2)}_create_team_invitations.rb")
end

#create_modelsObject



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/generators/seams/teams/teams_generator.rb', line 51

def create_models
  template_unless_ejected "app/models/application_record.rb.tt",
                          engine_path("app/models/teams/application_record.rb")
  template_unless_ejected "app/models/team.rb.tt",
                          engine_path("app/models/teams/team.rb")
  template_unless_ejected "app/models/membership.rb.tt",
                          engine_path("app/models/teams/membership.rb")
  template_unless_ejected "app/models/current.rb.tt",
                          engine_path("app/models/teams/current.rb")
  return unless features.include?("invitations")

  template_unless_ejected "app/models/invitation.rb.tt",
                          engine_path("app/models/teams/invitation.rb")
end

#create_specsObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/generators/seams/teams/teams_generator.rb', line 132

def create_specs
  template_unless_ejected "spec/models/team_spec.rb.tt",
                          engine_path("spec/models/teams/team_spec.rb")
  template_unless_ejected "spec/models/membership_spec.rb.tt",
                          engine_path("spec/models/teams/membership_spec.rb")
  # Phase 4A — factories live alongside the model specs so any
  # spec can `create(:team)` without rolling its own fixture.
  template_unless_ejected "spec/factories/teams.rb.tt",
                          engine_path("spec/factories/teams.rb")
  return unless features.include?("invitations")

  template_unless_ejected "spec/models/invitation_spec.rb.tt",
                          engine_path("spec/models/teams/invitation_spec.rb")
end

#create_viewsObject

Phase 4A (2/2) — bare-bones views so the engine renders out of the box. Hosts override by dropping files at app/views/teams/teams/* in their own tree.



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/generators/seams/teams/teams_generator.rb', line 80

def create_views
  %w[index show new edit].each do |action|
    template_unless_ejected "app/views/teams/#{action}.html.erb.tt",
                            engine_path("app/views/teams/teams/#{action}.html.erb")
  end
  template_unless_ejected "app/views/memberships/index.html.erb.tt",
                          engine_path("app/views/teams/memberships/index.html.erb")
  return unless features.include?("invitations")

  template_unless_ejected "app/views/invitations/index.html.erb.tt",
                          engine_path("app/views/teams/invitations/index.html.erb")
end

#overwrite_engine_entry_pointObject



39
40
41
42
43
44
45
# File 'lib/generators/seams/teams/teams_generator.rb', line 39

def overwrite_engine_entry_point
  # engine.rb / lib/teams.rb stay framework-managed.
  template "lib/engine.rb.tt",                 engine_path("lib/teams/engine.rb"),        force: true
  template "lib/teams.rb.tt",                  engine_path("lib/teams.rb"),               force: true
  template_unless_ejected "lib/configuration.rb.tt",
                          engine_path("lib/teams/configuration.rb")
end

#overwrite_readmeObject



147
148
149
# File 'lib/generators/seams/teams/teams_generator.rb', line 147

def overwrite_readme
  template "README.md.tt", engine_path("README.md"), force: true
end

#overwrite_routesObject



47
48
49
# File 'lib/generators/seams/teams/teams_generator.rb', line 47

def overwrite_routes
  template_unless_ejected "config/routes.rb.tt", engine_path("config/routes.rb"), force: true
end

#report_summaryObject



193
194
195
196
197
198
199
200
201
# File 'lib/generators/seams/teams/teams_generator.rb', line 193

def report_summary
  say ""
  say "  Teams engine generated at engines/teams/", :green
  say ""
  say "  Next steps:", :yellow
  say "    1. bin/rails db:migrate"
  say "    2. Run the engine specs: bin/rails seams:test[teams]"
  say ""
end

#update_exposed_concernsObject



151
152
153
154
155
156
157
158
159
160
161
# File 'lib/generators/seams/teams/teams_generator.rb', line 151

def update_exposed_concerns
  rubocop_path = engine_path(".rubocop.yml")
  return unless File.exist?(rubocop_path)

  contents       = File.read(rubocop_path)
  exposed_lines  = ["    - Teams::AccountScoped"]
  exposed_lines << "    - Teams::Authorization" if features.include?("roles")
  replacement    = "  ExposedConcerns:\n#{exposed_lines.join("\n")}"
  contents.sub!(/^  ExposedConcerns: \[\]$/, replacement)
  File.write(rubocop_path, contents)
end

#wire_into_hostObject



183
184
185
186
187
188
189
190
191
# File 'lib/generators/seams/teams/teams_generator.rb', line 183

def wire_into_host
  # factory_bot_rails powers spec/factories/teams.rb. Lives in
  # the host's test group only.
  host_inject_gem("factory_bot_rails", "~> 6.4", group: :test)
  host_inject_mount(engine_class: "Teams::Engine", at: "/teams")
  # NB: no host_inject_include_in_user — the host User is gone
  # post-Wave-9. Hosts that DO keep a User model and want
  # team-membership query helpers wire those up themselves.
end