Class: Spree::ApiResourceGenerator
- Inherits:
-
ModelGenerator
- Object
- ActiveRecord::Generators::ModelGenerator
- ModelGenerator
- Spree::ApiResourceGenerator
- Defined in:
- lib/generators/spree/api_resource/api_resource_generator.rb
Overview
spree:api_resource — scaffold a complete v3-conformant API resource on top of ‘spree:model`.
bin/rails g spree:api_resource Brand name:string:uniq active:boolean --writable
Inherits from Spree::ModelGenerator (model + migration with Spree conventions: prefixed IDs, spree_-prefixed tables, null: false, optional acts_as_paranoid + Spree::Metafields). Adds on top:
- Store + Admin controllers (managed — overwrite on re-run)
- Store + Admin serializers (managed — overwrite on re-run)
- Factory (managed — overwrite on re-run)
- Controller specs (managed — overwrite on re-run)
- Routes (idempotent inject between sentinels)
Owned-once contract: if the model file already exists, the generator leaves it (and the migration) alone — domain code is yours after creation. Re-running adds/updates API surfaces only.
TypeScript types and Zod schemas regenerate automatically via the Lefthook pre-commit hook when a serializer file is staged.
See docs/plans/spree-dev-cli-and-generators.md (Track 3) for the owned-once / managed-forever / append-only contract.
Class Method Summary collapse
-
.source_paths ⇒ Object
API-specific templates live alongside this generator.
Instance Method Summary collapse
- #create_admin_controller ⇒ Object
- #create_admin_serializer ⇒ Object
- #create_controller_specs ⇒ Object
- #create_factory ⇒ Object
-
#create_migration_file ⇒ Object
Override parent: skip if the model existed before this run.
-
#create_model_file ⇒ Object
Override parent: skip if the model file already exists.
-
#create_store_controller ⇒ Object
— API surface —.
- #create_store_serializer ⇒ Object
-
#initialize(*args) ⇒ ApiResourceGenerator
constructor
— Owned-once gating —.
- #inject_routes ⇒ Object
- #print_summary ⇒ Object
Methods inherited from ModelGenerator
Constructor Details
#initialize(*args) ⇒ ApiResourceGenerator
— Owned-once gating —
Thor’s parent commands run BEFORE subclass commands (commands merge from superclass first). So we can’t snapshot model existence in a subclass action and have parent’s create_model_file see it. We capture state in initialize, before any action runs.
88 89 90 91 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 88 def initialize(*args) super @model_existed_before_run = File.exist?(File.join(destination_root, model_file_destination)) end |
Class Method Details
.source_paths ⇒ Object
API-specific templates live alongside this generator. Parent’s templates (model.rb.tt, create_table_migration.rb.tt) are inherited via Spree::ModelGenerator.source_paths.
34 35 36 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 34 def self.source_paths [File.('templates', __dir__), *superclass.source_paths] end |
Instance Method Details
#create_admin_controller ⇒ Object
122 123 124 125 126 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 122 def create_admin_controller return unless [:admin] template 'admin_controller.rb.tt', admin_controller_path end |
#create_admin_serializer ⇒ Object
141 142 143 144 145 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 141 def create_admin_serializer return unless [:admin] template 'admin_serializer.rb.tt', admin_serializer_path end |
#create_controller_specs ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 156 def create_controller_specs return if [:skip_specs] if [:store] template 'store_controller_spec.rb.tt', "spec/controllers/spree/api/v3/store/#{plural_name}_controller_spec.rb" end if [:admin] template 'admin_controller_spec.rb.tt', "spec/controllers/spree/api/v3/admin/#{plural_name}_controller_spec.rb" end end |
#create_factory ⇒ Object
147 148 149 150 151 152 153 154 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 147 def create_factory # spec/factories/ is the FactoryBot default scan path that a freshly- # generated `rspec:install` + `factory_bot_rails` setup already picks # up via `FactoryBot.find_definitions`. Spree's own factories live # under lib/spree/testing_support/factories/ because that path is # exported by gems; downstream apps don't have that loader by default. template 'factory.rb.tt', "spec/factories/spree/#{singular_name}_factory.rb" end |
#create_migration_file ⇒ Object
Override parent: skip if the model existed before this run. Migrations are append-only — schema changes get a separate migration:
pnpm exec spree rails g migration AddFooToBar foo:string
106 107 108 109 110 111 112 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 106 def create_migration_file if @model_existed_before_run say_status :skip, 'migration (model already exists; add a new migration for schema changes)', :yellow return end super end |
#create_model_file ⇒ Object
Override parent: skip if the model file already exists. Re-running never overwrites domain code.
95 96 97 98 99 100 101 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 95 def create_model_file if @model_existed_before_run say_status :skip, "model #{model_file_destination} (owned-once; already exists)", :yellow return end super end |
#create_store_controller ⇒ Object
— API surface —
116 117 118 119 120 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 116 def create_store_controller return unless [:store] template 'store_controller.rb.tt', store_controller_path end |
#create_store_serializer ⇒ Object
128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 128 def create_store_serializer return unless [:store] template 'store_serializer.rb.tt', store_serializer_path # --store-name aliases the store-facing class under a different name # (e.g. Brand → Discount) while keeping the model/table internal. if store_external_name != template 'store_aliased_serializer.rb.tt', "app/serializers/spree/api/v3/#{store_external_name.underscore}_serializer.rb" end end |
#inject_routes ⇒ Object
169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 169 def inject_routes return if [:skip_routes] routes_file = api_routes_path unless File.exist?(routes_file) && File.writable?(routes_file) say_status :skip, "routes.rb at #{routes_file} (not writable — only edge installs can modify gem source)", :yellow return end inject_route_for(:store, store_route_line) if [:store] inject_route_for(:admin, admin_route_line) if [:admin] end |
#print_summary ⇒ Object
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/generators/spree/api_resource/api_resource_generator.rb', line 183 def print_summary say '' say "✓ Generated Spree::#{} API resource", :green say '' say " Prefixed ID: #{id_prefix}_xxxxxxxxxx (edit `has_prefix_id` in the model to change)" if store_external_name != say " Store API: /api/v3/store/#{store_external_plural} (aliased from #{})" elsif [:store] say " Store API: /api/v3/store/#{plural_name} (#{writable? ? 'full CRUD' : 'read-only'})" end say " Admin API: /api/v3/admin/#{plural_name} (full CRUD)" if [:admin] say '' say ' Next steps:', :yellow say ' 1. Review the generated model — add validations, scopes, callbacks' say ' 2. Apply the migration: pnpm exec spree migrate' say ' 3. Set up authorization (CanCanCan ability) for the resource' say ' 4. Decide whether this resource is store-scoped (add `has_many` on Store)' if [:store] || [:admin] say ' 5. Run the specs: pnpm exec spree exec bundle exec rspec spec/controllers/spree/api/v3/' end say '' end |