Skip to content

Conversation

@akshayutture-augment
Copy link

@akshayutture-augment akshayutture-augment commented Nov 14, 2025

Test 10

Summary by CodeRabbit

  • New Features

    • Added admin panel for managing embeddable hosts with ability to add, edit, and delete hosts
    • Per-host category assignment for embedded content from different sources
    • Host validation and configuration management through admin interface
  • Refactor

    • Restructured embedding configuration from global settings to individual host-level database records

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/discourse-coderabbit#10

@akshayutture-augment
Copy link
Author

@coderabbitai review

@akshayutture-augment
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

Walkthrough

This pull request introduces a new EmbeddableHost database model to replace SiteSetting-based embeddable host management. It includes new Rails controllers, an admin UI with in-place editing, serializers, a migration, and updates existing code to query the model instead of SiteSetting values.

Changes

Cohort / File(s) Summary
Rails Models & Validation
app/models/embeddable_host.rb, app/models/topic.rb, app/models/topic_embed.rb
New EmbeddableHost model with host validation, category association, and class methods for host lookup. Topic and TopicEmbed updated to use EmbeddableHost queries instead of SiteSetting checks.
Rails Controllers
app/controllers/admin/embedding_controller.rb, app/controllers/admin/embeddable_hosts_controller.rb, app/controllers/embed_controller.rb
New embedding and embeddable_hosts admin controllers with CRUD actions. EmbedController refactored to use EmbeddableHost.host_allowed? instead of SiteSetting checks.
Rails Configuration & Routing
config/routes.rb, config/site_settings.yml
Added embedding and embeddable_hosts routes. Removed embeddable_hosts and embed_category from site settings.
Serializers
app/serializers/embeddable_host_serializer.rb, app/serializers/embedding_serializer.rb
New serializers for EmbeddableHost and Embedding objects with appropriate attributes and associations.
Admin UI Components
app/assets/javascripts/admin/components/embeddable-host.js.es6, app/assets/javascripts/admin/templates/components/embeddable-host.hbs
New Ember component for in-place editing of embeddable host rows with save, delete, and cancel actions.
Admin UI Routes & Templates
app/assets/javascripts/admin/routes/admin-embedding.js.es6, app/assets/javascripts/admin/routes/admin-route-map.js.es6, app/assets/javascripts/admin/controllers/admin-embedding.js.es6, app/assets/javascripts/admin/templates/embedding.hbs, app/assets/javascripts/admin/templates/customize.hbs, app/assets/javascripts/admin/adapters/embedding.js.es6
New admin routes, controller, templates, and adapter for embedding management UI. Adds nav item to customize menu.
JavaScript & Discourse Infrastructure
app/assets/javascripts/discourse/adapters/rest.js.es6, app/assets/javascripts/discourse/models/store.js.es6
Added embeddable-host to ADMIN_MODELS. Extended _hydrateEmbedded to support plural embedded ID fields.
Localization
config/locales/client.en.yml, config/locales/server.en.yml
Added embedding-related translation keys for UI labels. Removed embeddable_hosts and embed_category translations.
Database Migration
db/migrate/20150818190757_create_embeddable_hosts.rb
Migration creating embeddable_hosts table and seeding data from SiteSetting, then removing old settings.
Core Library Updates
lib/topic_retriever.rb
Updated invalid_host? to use EmbeddableHost.host_allowed? instead of SiteSetting.allows_embeddable_host?.
Removed Public API
app/models/site_setting.rb
Removed allows_embeddable_host? class method (functionality moved to EmbeddableHost model).
Controller & Model Specs
spec/controllers/admin/embeddable_hosts_controller_spec.rb, spec/controllers/admin/embedding_controller_spec.rb, spec/models/embeddable_host_spec.rb, spec/models/site_setting_spec.rb
New tests for admin controllers and EmbeddableHost model. Removed tests for removed SiteSetting.allows_embeddable_host? method.
Integration Test Updates
spec/controllers/embed_controller_spec.rb, spec/models/topic_embed_spec.rb, spec/models/topic_spec.rb
Updated tests to use Fabricate(:embeddable_host) instead of setting SiteSetting.embeddable_hosts. Restructured topic expansion tests.
Test Fixtures & Helpers
spec/fabricators/embeddable_host_fabricator.rb, spec/fabricators/category_fabricator.rb, test/javascripts/helpers/create-pretender.js.es6, test/javascripts/models/store-test.js.es6
New embeddable_host fabricators. Category fabricators moved to new file. Test data updated with color relationships and embeddings.

Sequence Diagram

sequenceDiagram
    actor User as Admin User
    participant UI as Admin UI
    participant Controller as EmbeddingController
    participant Component as EmbeddableHost Component
    participant Rails as Rails Models/DB
    
    User->>UI: Open Embedding Settings
    UI->>Controller: GET /admin/customize/embedding
    Controller->>Rails: EmbeddableHost.all.order(:host)
    Rails-->>Controller: embeddable_hosts list
    Controller-->>UI: render embedding + hosts
    
    User->>UI: Click Edit on Host
    UI->>Component: toggle editing=true
    Component->>Component: focus host input
    
    User->>Component: Update host + category
    Component->>Component: track buffered changes
    
    User->>Component: Click Save
    Component->>Controller: PUT with host + category_id
    Controller->>Rails: update(:host, :category_id)
    Rails-->>Controller: updated host
    Controller-->>Component: success
    Component->>Component: exit edit mode, update display
    
    User->>Component: Click Delete
    Component->>Component: confirm dialog
    User->>Component: Confirm
    Component->>Controller: DELETE host
    Controller->>Rails: destroy record
    Rails-->>Controller: success
    Controller->>Component: notifyDeleteHost
    Component->>UI: remove from list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Key areas requiring careful attention:

  • EmbeddableHost model validation and host parsing logic — ensure URI parsing correctly handles various URL schemes and formats
  • Data migration safety — verify the migration correctly maps SiteSetting data to the new table and handles edge cases (missing categories, uncategorized defaults)
  • Component state management — review the editing/saving/canceling flow in embeddable-host component for race conditions and error handling
  • Store hydration changes — ensure plural embedded ID field support doesn't break existing single-ID behavior
  • Integration points — verify that all SiteSetting.allows_embeddable_host? call sites were properly replaced and the new EmbeddableHost queries work correctly across embed_controller, topic_retriever, and topic_embed
  • Test fixture reorganization — confirm category fabricators were correctly split without breaking existing tests

Poem

🐰 A model rose where settings dwelt before,
With hosts in rows now living in the store,
In-place we edit, delete, and we save,
The admin realm now organized and brave,
From strings to records, structured and so neat! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: editing category/host relationships for embedding, which is reflected throughout the changeset with new models, controllers, components, and UI for managing embeddable hosts.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rest-serializer-enhancement-post

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (12)
test/javascripts/models/store-test.js.es6 (1)

109-121: Stronger coverage for embedded relationships; consider aligning ID with fixtures

Using the injected assert plus explicit checks on colors length/IDs and category presence gives good coverage for the embedded relationships on a single fruit.

One minor clarity nit: this test calls store.find('fruit', 2), but in test/javascripts/helpers/create-pretender.js.es6 the fruit that has color_ids: [1, 2] is the one with id: 1, and the Pretender handler for /fruits/:id always returns the first fruit. The test passes, but the coupling is non-obvious. Consider either querying id: 1 here or updating the fixture so the fruit with id: 2 has [1, 2] to better reflect the test name/intent.

For example, either:

-  return store.find('fruit', 2).then(function(f) {
+  return store.find('fruit', 1).then(function(f) {

or:

-const fruits = [{id: 1, name: 'apple', farmer_id: 1, color_ids: [1,2], category_id: 4},
-                {id: 2, name: 'banana', farmer_id: 1, color_ids: [3], category_id: 3},
+const fruits = [{id: 1, name: 'apple', farmer_id: 1, color_ids: [1,2], category_id: 4},
+                {id: 2, name: 'banana', farmer_id: 1, color_ids: [1,2], category_id: 3},
app/assets/javascripts/discourse/adapters/rest.js.es6 (1)

1-24: Admin base path normalization works; consider handling multiple underscores

Adding 'embeddable-host' to ADMIN_MODELS and normalizing type before the lookup correctly routes embeddable-host requests through /admin/. To be slightly more robust for any future multi-underscore types, you could normalize all underscores:

-  basePath(store, type) {
-    if (ADMIN_MODELS.indexOf(type.replace('_', '-')) !== -1) { return "/admin/"; }
+  basePath(store, type) {
+    const normalized = type.replace(/_/g, '-');
+    if (ADMIN_MODELS.indexOf(normalized) !== -1) { return "/admin/"; }
spec/fabricators/embeddable_host_fabricator.rb (1)

1-27: Refactor :private_category fabricator to separate in-memory and database concerns

No duplicate Fabricator(:category) was found elsewhere—this file is the sole definition. However, the callback logic should be refined:

The after_build callback calls cat.update!, which persists to the DB even when tests use Fabricate.build(:private_category, ...), yet category_groups.build only creates an in-memory association and won't persist unless explicitly saved. Splitting into after_build (for in-memory state) and after_create (for DB records) ensures consistent behavior:

Fabricator(:private_category, from: :category) do
  transient :group

  name 'Private Category'
  slug 'private'
  user
-   after_build do |cat, transients|
-     cat.update!(read_restricted: true)
-     cat.category_groups.build(group_id: transients[:group].id, permission_type: CategoryGroup.permission_types[:full])
-   end
+   after_build do |cat, _|
+     cat.read_restricted = true
+   end
+
+   after_create do |cat, transients|
+     CategoryGroup.create!(
+       category: cat,
+       group: transients[:group],
+       permission_type: CategoryGroup.permission_types[:full]
+     )
+   end
end

Also ensure that any call to Fabricate(:private_category) always supplies group: to avoid nil errors, or add validation/documentation to clarify the requirement.

spec/controllers/admin/embeddable_hosts_controller_spec.rb (1)

1-9: Add comprehensive test coverage for controller actions.

This spec only verifies inheritance. Consider adding tests for the controller's CRUD actions (create, update, destroy) to ensure proper authorization, validation, and behavior.

Also, address the RuboCop convention warnings by removing extra blank lines:

 describe Admin::EmbeddableHostsController do
-
   it "is a subclass of AdminController" do
     expect(Admin::EmbeddableHostsController < Admin::AdminController).to eq(true)
   end
-
 end
spec/controllers/admin/embedding_controller_spec.rb (1)

1-9: Add comprehensive test coverage for controller actions.

This spec only verifies inheritance. Consider adding tests for the show and update actions to ensure proper authorization, embedding configuration handling, and response format.

Also, address the RuboCop convention warnings by removing extra blank lines:

 describe Admin::EmbeddingController do
-
   it "is a subclass of AdminController" do
     expect(Admin::EmbeddingController < Admin::AdminController).to eq(true)
   end
-
 end
app/assets/javascripts/admin/templates/embedding.hbs (1)

4-6: Consider using CSS classes instead of inline styles.

The inline style attributes work but are harder to maintain and override. Consider defining CSS classes for table column widths in a stylesheet for better maintainability.

For example:

<th class='embedding-host-column'>{{i18n "admin.embedding.host"}}</th>
<th class='embedding-category-column'>{{i18n "admin.embedding.category"}}</th>
<th class='embedding-actions-column'>&nbsp;</th>

Then in CSS:

.embedding-host-column { width: 50%; }
.embedding-category-column { width: 30%; }
.embedding-actions-column { width: 20%; }
app/serializers/embedding_serializer.rb (1)

2-7: Remove redundant id attribute definition.

The id attribute and method are unnecessary. ActiveModel Serializers (which ApplicationSerializer likely inherits from) automatically serializes the id attribute. Unless there's a specific reason for this custom method, it can be removed.

Apply this diff:

 class EmbeddingSerializer < ApplicationSerializer
-  attributes :id
   has_many :embeddable_hosts, serializer: EmbeddableHostSerializer, embed: :ids
-
-  def id
-    object.id
-  end
 end
spec/models/embeddable_host_spec.rb (2)

23-38: Consider adding edge case tests.

The current tests cover basic scenarios well. Consider adding tests for:

  • Subdomain matching (e.g., should sub.eviltrout.com be allowed if eviltrout.com is in the list?)
  • Port numbers in URLs
  • nil or empty referer values (important for security)

4-5: Fix RuboCop style violations.

Remove extra empty lines flagged by RuboCop.

Apply this diff:

 describe EmbeddableHost do
-
   it "trims http" do
       expect(EmbeddableHost.host_allowed?('http://discourse.org')).to eq(true)
     end
-
   end
-
 end

Also applies to: 37-40

app/serializers/embeddable_host_serializer.rb (1)

4-14: Consider removing redundant accessor methods.

The explicit accessor methods that simply return object.attribute are typically redundant when using the attributes declaration. Unless ApplicationSerializer requires explicit methods or you need to add custom logic later, these can be simplified.

If no custom logic is needed, you can simplify to:

 class EmbeddableHostSerializer < ApplicationSerializer
   attributes :id, :host, :category_id
-
-  def id
-    object.id
-  end
-
-  def host
-    object.host
-  end
-
-  def category_id
-    object.category_id
-  end
 end
app/assets/javascripts/admin/templates/components/embeddable-host.hbs (1)

1-19: Template wiring looks consistent with the component

The editing/view states, bindings (buffered.host, categoryId, cantSave, host.isSaving) and actions all line up with the JS component. This looks good.

If you want a tiny UX tweak, you might also disable the delete button when host.isSaving (mirroring the cancel button), but that’s optional.

spec/models/topic_spec.rb (1)

1399-1427: New expandable_first_post? specs capture the key embedding scenarios

The new expandable_first_post? examples nicely cover:

  • no embeddable hosts → false
  • embeddable_host present + embed_truncate true + has_topic_embed? true → true
  • toggling either embed_truncate or has_topic_embed? → false

That gives good confidence around the new EmbeddableHost‑based behavior. The only nit is the typo in the description string ('emeddable host'); you may want to fix that for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 913c3d6 and d1c6918.

📒 Files selected for processing (36)
  • app/assets/javascripts/admin/adapters/embedding.js.es6 (1 hunks)
  • app/assets/javascripts/admin/components/embeddable-host.js.es6 (1 hunks)
  • app/assets/javascripts/admin/controllers/admin-embedding.js.es6 (1 hunks)
  • app/assets/javascripts/admin/routes/admin-embedding.js.es6 (1 hunks)
  • app/assets/javascripts/admin/routes/admin-route-map.js.es6 (1 hunks)
  • app/assets/javascripts/admin/templates/components/embeddable-host.hbs (1 hunks)
  • app/assets/javascripts/admin/templates/customize.hbs (1 hunks)
  • app/assets/javascripts/admin/templates/embedding.hbs (1 hunks)
  • app/assets/javascripts/discourse/adapters/rest.js.es6 (2 hunks)
  • app/assets/javascripts/discourse/models/store.js.es6 (1 hunks)
  • app/controllers/admin/embeddable_hosts_controller.rb (1 hunks)
  • app/controllers/admin/embedding_controller.rb (1 hunks)
  • app/controllers/embed_controller.rb (1 hunks)
  • app/models/embeddable_host.rb (1 hunks)
  • app/models/site_setting.rb (0 hunks)
  • app/models/topic.rb (1 hunks)
  • app/models/topic_embed.rb (1 hunks)
  • app/serializers/embeddable_host_serializer.rb (1 hunks)
  • app/serializers/embedding_serializer.rb (1 hunks)
  • config/locales/client.en.yml (1 hunks)
  • config/locales/server.en.yml (0 hunks)
  • config/routes.rb (2 hunks)
  • config/site_settings.yml (0 hunks)
  • db/migrate/20150818190757_create_embeddable_hosts.rb (1 hunks)
  • lib/topic_retriever.rb (1 hunks)
  • spec/controllers/admin/embeddable_hosts_controller_spec.rb (1 hunks)
  • spec/controllers/admin/embedding_controller_spec.rb (1 hunks)
  • spec/controllers/embed_controller_spec.rb (3 hunks)
  • spec/fabricators/category_fabricator.rb (1 hunks)
  • spec/fabricators/embeddable_host_fabricator.rb (1 hunks)
  • spec/models/embeddable_host_spec.rb (1 hunks)
  • spec/models/site_setting_spec.rb (0 hunks)
  • spec/models/topic_embed_spec.rb (2 hunks)
  • spec/models/topic_spec.rb (3 hunks)
  • test/javascripts/helpers/create-pretender.js.es6 (2 hunks)
  • test/javascripts/models/store-test.js.es6 (1 hunks)
💤 Files with no reviewable changes (4)
  • app/models/site_setting.rb
  • config/locales/server.en.yml
  • config/site_settings.yml
  • spec/models/site_setting_spec.rb
🧰 Additional context used
🪛 RuboCop (1.81.7)
db/migrate/20150818190757_create_embeddable_hosts.rb

[convention] 2-32: Assignment Branch Condition size for change is too high. [<7, 22, 8> 24.43/23]

(Metrics/AbcSize)

spec/controllers/admin/embeddable_hosts_controller_spec.rb

[convention] 4-5: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 8-9: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)

spec/models/embeddable_host_spec.rb

[convention] 4-5: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 37-38: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)


[convention] 39-40: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)

app/models/embeddable_host.rb

[convention] 11-11: Avoid using rescue in its modifier form.

(Style/RescueModifier)

spec/controllers/admin/embedding_controller_spec.rb

[convention] 4-5: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 8-9: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)

🔇 Additional comments (27)
test/javascripts/helpers/create-pretender.js.es6 (1)

42-44: Colors fixture and fruit payloads match embedded relationship expectations

The new color_ids on fruits, the colors collection, and the inclusion of colors in both /fruits/:id and /fruits responses give the store everything it needs to materialize the colors relationship from side-loaded data. The ID ranges are consistent (1–3) and all referenced color IDs exist, so there are no dangling references.

Also applies to: 49-51, 228-233

test/javascripts/models/store-test.js.es6 (1)

123-135: findAll embedded test correctly validates shared farmer and colors

The updated findAll embedded test now asserts both that the first two fruits share the same farmer instance and that the first fruit’s colors collection has the expected length and IDs, while still verifying the third fruit’s farmer. This lines up cleanly with the Pretender fixtures and gives good coverage of the embedded collection behavior.

app/models/topic.rb (1)

868-870: Simplified expandable_first_post? condition looks correct

Using SiteSetting.embed_truncate? && has_topic_embed? matches the embed truncation behavior and the new TopicEmbed/EmbeddableHost flow; no issues spotted.

config/locales/client.en.yml (1)

2494-2501: Embedding admin translations are well‑scoped

The new admin.embedding.* keys are clearly named, correctly indented, and should integrate cleanly with the embedding UI.

app/assets/javascripts/admin/routes/admin-route-map.js.es6 (1)

17-31: New adminEmbedding route wiring is consistent with existing admin routes

The adminEmbedding resource under /customize cleanly maps to /admin/customize/embedding and matches the existing admin routing style.

app/models/topic_embed.rb (1)

35-45: Host‑driven category selection for embeds is reasonable

Looking up eh = EmbeddableHost.record_for_host(url) and passing category: eh.try(:category_id) cleanly moves embed categorization to the new model; if no host/category is found, the existing Topic callbacks will still assign a default category.

app/assets/javascripts/admin/templates/customize.hbs (1)

8-8: LGTM!

The navigation item follows the established pattern and correctly integrates the new embedding feature into the admin UI.

spec/models/topic_embed_spec.rb (2)

38-38: Good test coverage for category association.

This assertion correctly validates that the embedded topic uses the category from the associated EmbeddableHost. Assuming the fabricator creates a matching host (see previous comment), this test should work as expected.


15-15: The review comment is based on incorrect assumptions.

The embeddable_host fabricator explicitly sets host "eviltrout.com" in spec/fabricators/category_fabricator.rb, which matches the test URL 'http://eviltrout.com/123'. The test expectation at line 38 checks that post.topic.category matches embeddable_host.category, which is already correct since the fabricator also creates a category. No changes are needed.

Likely an incorrect or invalid review comment.

lib/topic_retriever.rb (1)

16-16: LGTM!

The migration from SiteSetting.allows_embeddable_host? to EmbeddableHost.host_allowed? correctly aligns with the new model-based approach for managing embeddable hosts.

config/routes.rb (2)

138-139: LGTM!

The embedding routes are properly configured with admin constraints and follow the established pattern for admin customize routes.


153-153: LGTM!

The embeddable_hosts resource is correctly configured as a RESTful resource with admin constraints, enabling CRUD operations for embeddable host management.

app/assets/javascripts/admin/templates/embedding.hbs (1)

1-15: Template structure looks good.

The template correctly iterates over embeddable hosts and provides a clean interface for managing them with proper i18n support.

app/serializers/embedding_serializer.rb (1)

3-3: LGTM on the has_many association.

The has_many :embeddable_hosts with embed: :ids correctly serializes the relationship by including only the IDs of associated hosts, which is appropriate for this use case.

app/assets/javascripts/admin/routes/admin-embedding.js.es6 (2)

2-4: LGTM!

The model hook correctly fetches the embedding configuration.


6-8: LGTM!

Manual property assignment is appropriate here since the controller references the embedding property specifically rather than the default model property.

app/assets/javascripts/admin/adapters/embedding.js.es6 (1)

4-6: LGTM!

The fixed path is appropriate for a singleton embedding resource.

spec/models/embeddable_host_spec.rb (1)

5-21: LGTM!

The normalization tests appropriately cover HTTP/HTTPS protocol trimming and path removal scenarios.

spec/controllers/embed_controller_spec.rb (3)

33-33: LGTM!

Good use of let! to ensure the embeddable host record exists before the examples run.


68-70: LGTM!

The test setup appropriately creates multiple embeddable hosts with different formats, which will exercise both the host normalization logic and the multi-host allowance checking.


20-23: Fabricator default correctly matches test referer.

Verification confirms the embeddable_host fabricator in spec/fabricators/category_fabricator.rb has host "eviltrout.com" as its default, which matches the test referer domain http://eviltrout.com/some-page. The test setup is correct.

app/assets/javascripts/admin/controllers/admin-embedding.js.es6 (2)

9-12: LGTM!

The addHost action correctly creates a new embeddable-host record and adds it to the collection. Note that this requires saveChanges to be called separately to persist the change.


5-7: Verify embeddable_hosts are included when calling embedding.update({})

Calling embedding.update({}) with an empty attrs object may not include changes to the embedding.embeddable_hosts hasMany — confirm the client-side Embedding model/adapter/serializer actually serializes relationship diffs (or use embedding.save() / send the hosts explicitly) before relying on update({}).
Controller: app/assets/javascripts/admin/controllers/admin-embedding.js.es6 (saveChanges). Check client adapter: app/assets/javascripts/admin/addon/adapters/embedding.js(es6) and server serializer: app/serializers/embedding_serializer.rb.

app/controllers/embed_controller.rb (1)

61-61: The implementation already properly handles nil and blank referer values.

The record_for_host method safely handles nil input by rescuing URI parsing errors, then returns false early if the parsed URI is not present or has no host. This means host_allowed?(nil) returns false, causing the security check on line 61 to correctly raise an exception. No changes needed.

Likely an incorrect or invalid review comment.

app/assets/javascripts/discourse/models/store.js.es6 (1)

196-209: Verify intentional design of asymmetric plural hydration behavior.

The code review correctly identifies an asymmetry in _hydrateEmbedded (app/assets/javascripts/discourse/models/store.js.es6, lines 196-209):

  • Plural case (m[2] true): The original _ids key is always deleted, even if some _lookupSubType calls return undefined, resulting in arrays that may contain undefined values
  • Singular case (m[2] false): The original key is only deleted if hydration succeeds

The implementation is confirmed to work as described. Existing tests for embedded relationships (e.g., "find embedded" and "findAll embedded" in store-test.js.es6) all assume successful hydration with no partial failures.

Verify whether this asymmetry is:

  1. Intentional design (e.g., always preserve array length/structure), or
  2. A potential issue where undefined values in the hydrated array could cause problems in code that iterates or accesses these properties
spec/models/topic_spec.rb (1)

1238-1253: for_digest spec tweaks are fine

Switching to bare Fabricate(:topic, …) calls where the local variable was unused is a no-op in behavior and removes dead code. Nothing to change here.

app/controllers/admin/embedding_controller.rb (1)

1-20: Now I have confirmed that Rails allows using :only to restrict routes. Let me search for the actual routes file to verify the embedding controller's route configuration.Now let me search the codebase for the routes file to verify the embedding controller route configuration:

Admin embedding controller approved—no issues found.

The controller implementation is sound. Both show and update serialize the same embedding container (with ordered embeddable hosts), consistent with the read-only pattern where host management occurs through a separate controller. The auth chain and data structure are appropriate for an admin settings endpoint.

Comment on lines +1 to +63
import { bufferedProperty } from 'discourse/mixins/buffered-content';
import computed from 'ember-addons/ember-computed-decorators';
import { on, observes } from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error';

export default Ember.Component.extend(bufferedProperty('host'), {
editToggled: false,
tagName: 'tr',
categoryId: null,

editing: Ember.computed.or('host.isNew', 'editToggled'),

@on('didInsertElement')
@observes('editing')
_focusOnInput() {
Ember.run.schedule('afterRender', () => { this.$('.host-name').focus(); });
},

@computed('buffered.host', 'host.isSaving')
cantSave(host, isSaving) {
return isSaving || Ember.isEmpty(host);
},

actions: {
edit() {
this.set('categoryId', this.get('host.category.id'));
this.set('editToggled', true);
},

save() {
if (this.get('cantSave')) { return; }

const props = this.get('buffered').getProperties('host');
props.category_id = this.get('categoryId');

const host = this.get('host');
host.save(props).then(() => {
host.set('category', Discourse.Category.findById(this.get('categoryId')));
this.set('editToggled', false);
}).catch(popupAjaxError);
},

delete() {
bootbox.confirm(I18n.t('admin.embedding.confirm_delete'), (result) => {
if (result) {
this.get('host').destroyRecord().then(() => {
this.sendAction('deleteHost', this.get('host'));
});
}
});
},

cancel() {
const host = this.get('host');
if (host.get('isNew')) {
this.sendAction('deleteHost', host);
} else {
this.rollbackBuffer();
this.set('editToggled', false);
}
}
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Component logic looks solid; optionally guard focus on non-editing

The buffered editing, cantSave logic, save/delete/cancel flows, and category handling all look consistent with the template and backend API.

Minor optional tweak: _focusOnInput currently runs on every editing change, including when it becomes false. You can avoid a no‑op focus call by short‑circuiting:

  @on('didInsertElement')
  @observes('editing')
  _focusOnInput() {
    if (!this.get('editing')) { return; }
    Ember.run.schedule('afterRender', () => {
      this.$('.host-name').focus();
    });
  },

🏁 Script executed:

#!/bin/bash
# Optional: look for similar buffered-edit patterns to keep behavior consistent.
rg -n "bufferedProperty\\(" app/assets/javascripts

Length of output: 494



---

🏁 Script executed:

```shell
#!/bin/bash
# Check admin-user-field-item for similar focus/editing patterns
cat app/assets/javascripts/admin/components/admin-user-field-item.js.es6

echo "---"

# Search for other @observes methods to check for guard patterns
rg -n "@observes" app/assets/javascripts/admin/components --type=es6 -A 5

Length of output: 2911



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for @observes methods without type filter
rg -n "@observes" app/assets/javascripts/admin/components -A 3

echo "---"

# Also search for .observes pattern (older Ember syntax)
rg -n "\.observes\(" app/assets/javascripts/admin/components -A 2 | head -50

Length of output: 1877


Align focus guard with established pattern in similar components

The buffered editing, cantSave logic, save/delete/cancel flows, and category handling all look solid. However, the _focusOnInput method should include the editing guard shown in admin-user-field-item.js.es6, which uses the same bufferedProperty pattern.

Currently, _focusOnInput runs on every editing change, including when it becomes false, causing unnecessary DOM operations. The established pattern in similar components is:

_focusOnInput: function() {
  if (this.get('editing')) {
    Ember.run.schedule('afterRender', () => {
      this.$('.host-name').focus();
    });
  }
}.observes('editing').on('didInsertElement'),
🤖 Prompt for AI Agents
app/assets/javascripts/admin/components/embeddable-host.js.es6 lines 1-63:
_focusOnInput runs on every change of editing (including when editing becomes
false) and causes unnecessary DOM operations; update the method to guard on
this.get('editing') before scheduling the focus so it only focuses when editing
is true (keep the didInsertElement and observes('editing') hooks intact and
place the if-check wrapping the Ember.run.schedule call).

Comment on lines +14 to +16
deleteHost(host) {
this.get('embedding.embeddable_hosts').removeObject(host);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify deletion handling for persisted hosts.

The deleteHost action removes the host from the array but doesn't explicitly mark it for deletion or call destroyRecord. Ensure that when saveChanges is called, the backend correctly identifies and deletes removed hosts from the embeddable_hosts collection.


🏁 Script executed:

#!/bin/bash
# Description: Check how the backend handles embeddable_hosts updates

# Find the embedding controller update action
rg -A 30 "def update" app/controllers/admin/embedding_controller.rb

# Check embedding serializer for how it handles has_many
rg -A 10 "embeddable_hosts" app/serializers/embedding_serializer.rb

Length of output: 405


🏁 Script executed:

#!/bin/bash
# Get the full embedding controller to see complete update method and params handling
cat app/controllers/admin/embedding_controller.rb

# Check for strong parameters and param handling
rg -n "params\|permit\|require" app/controllers/admin/embedding_controller.rb

# Look for any Rails concerns or mixins that might handle updates
rg -n "include|extend" app/controllers/admin/embedding_controller.rb

Length of output: 533


🏁 Script executed:

#!/bin/bash
# Check how the EmbeddableHost model handles associations and deletion
cat app/models/embeddable_host.rb

# Look for embedding associations
rg -B5 -A5 "embeddable_host" app/models/embedding.rb

Length of output: 625


🏁 Script executed:

#!/bin/bash
# Check if there's an embeddings resource route that might define update behavior
rg -A2 "embeddable.*host" config/routes.rb

Length of output: 129


🏁 Script executed:

#!/bin/bash
# Find the saveChanges method in the JavaScript file to see what API calls are made
rg -B5 -A10 "saveChanges" app/assets/javascripts/admin/controllers/admin-embedding.js.es6

# Look for any controller action that handles embeddable_hosts updates
cat app/controllers/admin/embeddable_hosts_controller.rb 2>/dev/null || echo "Controller not found"

# Check if there's a separate serializer or method handling host changes
rg -n "embeddable_hosts" app/controllers/admin/*.rb

Length of output: 1306


🏁 Script executed:

#!/bin/bash
# Look for any API structure or endpoint handling in the embedding controller more broadly
rg -B3 -A15 "class Admin::EmbeddingController" app/controllers/admin/embedding_controller.rb

# Check if there's a concern or service handling the deletion logic
fd -e rb app/controllers/concerns/ -o app/services/ | xargs rg -l "embeddable" 2>/dev/null || true

Length of output: 1630


Deletion does not persist to the database. The saveChanges call cannot delete hosts.

The deleteHost action removes the host from the client-side array, but saveChanges calls embedding.update({}) with an empty object. The backend update action ignores parameters and simply re-fetches embeddable_hosts from the database without processing any changes. Deleted hosts remain in the database and will reappear after a page refresh.

To persist deletions, either:

  • Call destroy on each removed host and send DELETE requests to the embeddable_hosts endpoint, or
  • Modify the embedding controller's update action to process the embeddable_hosts IDs from parameters and delete removed records.

Comment on lines +1 to +34
class Admin::EmbeddableHostsController < Admin::AdminController

before_filter :ensure_logged_in, :ensure_staff

def create
save_host(EmbeddableHost.new)
end

def update
host = EmbeddableHost.where(id: params[:id]).first
save_host(host)
end

def destroy
host = EmbeddableHost.where(id: params[:id]).first
host.destroy
render json: success_json
end

protected

def save_host(host)
host.host = params[:embeddable_host][:host]
host.category_id = params[:embeddable_host][:category_id]
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?

if host.save
render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true)
else
render_json_error(host)
end
end

end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Handle missing embeddable hosts in update/destroy

Both update and destroy assume the record exists:

  • EmbeddableHost.where(id: params[:id]).first can return nil.
  • save_host(host) and host.destroy will then raise a NoMethodError.

Safer pattern:

  def update
-    host = EmbeddableHost.where(id: params[:id]).first
-    save_host(host)
+    host = EmbeddableHost.find_by(id: params[:id])
+    return render_not_found unless host
+    save_host(host)
  end

  def destroy
-    host = EmbeddableHost.where(id: params[:id]).first
-    host.destroy
-    render json: success_json
+    host = EmbeddableHost.find_by(id: params[:id])
+    return render_not_found unless host
+    host.destroy
+    render json: success_json
  end

This avoids 500s on bad IDs and returns a clean 404 instead.


🏁 Script executed:

#!/bin/bash
# Check for existing patterns of render_not_found usage in admin controllers.
rg -n "render_not_found" app/controllers/admin

Length of output: 0


I need to verify whether the proposed pattern is available in this codebase. Let me search more comprehensively.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if render_not_found exists anywhere in the codebase
rg -n "render_not_found" --type rb

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Check what error handling patterns ARE used in admin controllers
rg -n -A2 "\.where\(id:" app/controllers/admin --type rb | head -40

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Check the Admin::AdminController parent class for available methods
find app/controllers -name "*admin_controller.rb" | head -5

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Search for render_not_found anywhere in codebase
rg -n "render_not_found"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check what error handling patterns exist in admin controllers
rg -n -A2 "\.where\(id:" app/controllers/admin | head -50

Length of output: 1658


🏁 Script executed:

#!/bin/bash
# Inspect Admin::AdminController for available methods
cat app/controllers/admin/admin_controller.rb

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Check what methods ARE used in codebase to handle missing/not found cases
rg -n "render.*json.*error|render.*error|head" app/controllers/admin | head -20

Length of output: 2295


🏁 Script executed:

#!/bin/bash
# Look for how other update/destroy methods handle nil cases
rg -n -B2 "destroy if.*present" app/controllers/admin

Length of output: 261


Handle missing embeddable hosts in update/destroy

Both update and destroy assume the record exists:

  • EmbeddableHost.where(id: params[:id]).first can return nil.
  • Calling save_host(host) or host.destroy on nil will raise NoMethodError.

The codebase pattern (see user_fields_controller.rb:44) is to check presence before operating:

  def update
    host = EmbeddableHost.where(id: params[:id]).first
+   return render_json_error(I18n.t('not_found')) unless host
    save_host(host)
  end

  def destroy
    host = EmbeddableHost.where(id: params[:id]).first
+   return render_json_error(I18n.t('not_found')) unless host
    host.destroy
    render json: success_json
  end

This prevents 500 errors on missing IDs and returns proper error feedback.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class Admin::EmbeddableHostsController < Admin::AdminController
before_filter :ensure_logged_in, :ensure_staff
def create
save_host(EmbeddableHost.new)
end
def update
host = EmbeddableHost.where(id: params[:id]).first
save_host(host)
end
def destroy
host = EmbeddableHost.where(id: params[:id]).first
host.destroy
render json: success_json
end
protected
def save_host(host)
host.host = params[:embeddable_host][:host]
host.category_id = params[:embeddable_host][:category_id]
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?
if host.save
render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true)
else
render_json_error(host)
end
end
end
class Admin::EmbeddableHostsController < Admin::AdminController
before_filter :ensure_logged_in, :ensure_staff
def create
save_host(EmbeddableHost.new)
end
def update
host = EmbeddableHost.where(id: params[:id]).first
return render_json_error(I18n.t('not_found')) unless host
save_host(host)
end
def destroy
host = EmbeddableHost.where(id: params[:id]).first
return render_json_error(I18n.t('not_found')) unless host
host.destroy
render json: success_json
end
protected
def save_host(host)
host.host = params[:embeddable_host][:host]
host.category_id = params[:embeddable_host][:category_id]
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?
if host.save
render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true)
else
render_json_error(host)
end
end
end
🤖 Prompt for AI Agents
In app/controllers/admin/embeddable_hosts_controller.rb lines 1-34, both update
and destroy call methods on a host that may be nil; change the actions to fetch
the host, return a proper JSON error/404 when not found (follow the pattern in
user_fields_controller.rb:44), and only call save_host(host) or host.destroy
when host is present; ensure the response uses the same render_json_error or
not_found helper used elsewhere to avoid NoMethodError and return a graceful
error to the client.

Comment on lines +1 to +24
class EmbeddableHost < ActiveRecord::Base
validates_format_of :host, :with => /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\Z/i
belongs_to :category

before_validation do
self.host.sub!(/^https?:\/\//, '')
self.host.sub!(/\/.*$/, '')
end

def self.record_for_host(host)
uri = URI(host) rescue nil
return false unless uri.present?

host = uri.host
return false unless host.present?

where("lower(host) = ?", host).first
end

def self.host_allowed?(host)
record_for_host(host).present?
end

end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Guard against nil host and tighten host lookup/normalization

Two correctness issues and one style concern here:

  • before_validation calls self.host.sub! unconditionally; if host is ever nil (e.g., building an empty record in a console or via a malformed request), this will raise before validations run.
  • record_for_host aims for case‑insensitive matching (lower(host) = ?) but doesn’t normalize the parameter or the stored value, so behavior depends on how hosts were originally cased.
  • The URI(host) rescue nil modifier is what RuboCop is flagging.

A small refactor fixes all three:

-  before_validation do
-    self.host.sub!(/^https?:\/\//, '')
-    self.host.sub!(/\/.*$/, '')
-  end
+  before_validation do
+    return if self.host.blank?
+
+    normalized = self.host.sub(/^https?:\/\//, '').sub(/\/.*$/, '')
+    self.host = normalized.downcase
+  end
@@
-  def self.record_for_host(host)
-    uri = URI(host) rescue nil
-    return false unless uri.present?
-
-    host = uri.host
-    return false unless host.present?
-
-    where("lower(host) = ?", host).first
-  end
+  def self.record_for_host(host)
+    uri =
+      begin
+        URI(host)
+      rescue URI::InvalidURIError, ArgumentError
+        nil
+      end
+    return false unless uri && uri.host.present?
+
+    where("lower(host) = ?", uri.host.downcase).first
+  end

This keeps the public behavior (returns a record or false), but avoids callback crashes and makes the “host_allowed?” check reliably case‑insensitive.


🏁 Script executed:

#!/bin/bash
# Quick verification: search for other direct writes to EmbeddableHost#host
rg -n "EmbeddableHost" app spec

Length of output: 2439


🏁 Script executed:

#!/bin/bash
# Inspect the full spec file to understand test expectations and current behavior
cat spec/models/embeddable_host_spec.rb

Length of output: 1162


🏁 Script executed:

#!/bin/bash
# Check the admin controller to see how EmbeddableHost is created
cat app/controllers/admin/embeddable_hosts_controller.rb

Length of output: 819


🏁 Script executed:

#!/bin/bash
# Check if there's a form or API that could pass nil host
rg -n "params\[" app/controllers/admin/embeddable_hosts_controller.rb -A 2 -B 2

Length of output: 490


Guard against nil host and tighten host lookup/normalization

Two correctness issues and one style concern here:

  • before_validation calls self.host.sub! unconditionally; if host is ever nil (e.g., from an API request with missing or empty host parameter), this will raise NoMethodError before validations run.
  • record_for_host aims for case‑insensitive matching (lower(host) = ?) but doesn't normalize the parameter or consistently normalize the stored value, creating brittle behavior.
  • The URI(host) rescue nil modifier is what RuboCop flags.

A small refactor fixes all three:

-  before_validation do
-    self.host.sub!(/^https?:\/\//, '')
-    self.host.sub!(/\/.*$/, '')
-  end
+  before_validation do
+    return if self.host.blank?
+
+    normalized = self.host.sub(/^https?:\/\//, '').sub(/\/.*$/, '')
+    self.host = normalized.downcase
+  end
@@
-  def self.record_for_host(host)
-    uri = URI(host) rescue nil
-    return false unless uri.present?
-
-    host = uri.host
-    return false unless host.present?
-
-    where("lower(host) = ?", host).first
-  end
+  def self.record_for_host(host)
+    uri =
+      begin
+        URI(host)
+      rescue URI::InvalidURIError, ArgumentError
+        nil
+      end
+    return false unless uri && uri.host.present?
+
+    where("lower(host) = ?", uri.host.downcase).first
+  end

This keeps the public behavior (returns a record or false), avoids callback crashes on nil, and makes the "host_allowed?" check reliably case‑insensitive.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class EmbeddableHost < ActiveRecord::Base
validates_format_of :host, :with => /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\Z/i
belongs_to :category
before_validation do
self.host.sub!(/^https?:\/\//, '')
self.host.sub!(/\/.*$/, '')
end
def self.record_for_host(host)
uri = URI(host) rescue nil
return false unless uri.present?
host = uri.host
return false unless host.present?
where("lower(host) = ?", host).first
end
def self.host_allowed?(host)
record_for_host(host).present?
end
end
class EmbeddableHost < ActiveRecord::Base
validates_format_of :host, :with => /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\Z/i
belongs_to :category
before_validation do
return if self.host.blank?
normalized = self.host.sub(/^https?:\/\//, '').sub(/\/.*$/, '')
self.host = normalized.downcase
end
def self.record_for_host(host)
uri =
begin
URI(host)
rescue URI::InvalidURIError, ArgumentError
nil
end
return false unless uri && uri.host.present?
where("lower(host) = ?", uri.host.downcase).first
end
def self.host_allowed?(host)
record_for_host(host).present?
end
end
🧰 Tools
🪛 RuboCop (1.81.7)

[convention] 11-11: Avoid using rescue in its modifier form.

(Style/RescueModifier)

🤖 Prompt for AI Agents
In app/models/embeddable_host.rb around lines 1-24, guard the before_validation
callback against nil by returning early or calling to_s on host before running
sub! (e.g. skip normalization when host.blank?), normalize and persist host to a
canonical lowercase/stripped form (remove scheme and path, downcase) so stored
values are consistent, update record_for_host to safely parse the input without
using the rescue modifier (use begin/URI.parse/rescue) and normalize the
incoming host the same way (strip scheme/path and downcase) before querying with
where("lower(host) = ?", normalized_host), and preserve the public API of
returning a record or false.

Comment on lines +9 to +31
category_id = execute("SELECT c.id FROM categories AS c
INNER JOIN site_settings AS s ON s.value = c.name
WHERE s.name = 'embed_category'")[0]['id'].to_i


if category_id == 0
category_id = execute("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'")[0]['value'].to_i
end

embeddable_hosts = execute("SELECT value FROM site_settings WHERE name = 'embeddable_hosts'")
if embeddable_hosts && embeddable_hosts.cmd_tuples > 0
val = embeddable_hosts[0]['value']
if val.present?
records = val.split("\n")
if records.present?
records.each do |h|
execute "INSERT INTO embeddable_hosts (host, category_id, created_at, updated_at) VALUES ('#{h}', #{category_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
end
end
end
end

execute "DELETE FROM site_settings WHERE name IN ('embeddable_hosts', 'embed_category')"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fix unsafe SQL usage and missing-row handling in migration

Two issues here can break the migration and/or open you up to SQL problems:

  • execute(...)[0]['id'] assumes at least one row; if embed_category is not set, execute will return 0 rows and this will raise before the fallback to uncategorized_category_id runs.
  • The INSERT statement interpolates h directly into SQL. Any host containing ' will cause a syntax error, and in general this is unsafe.

You can make this robust and safe with a small refactor:

-    category_id = execute("SELECT c.id FROM categories AS c
-                        INNER JOIN site_settings AS s ON s.value = c.name
-                        WHERE s.name = 'embed_category'")[0]['id'].to_i
+    category_id = 0
+    result = execute("SELECT c.id FROM categories AS c
+                        INNER JOIN site_settings AS s ON s.value = c.name
+                        WHERE s.name = 'embed_category'")
+    category_id = result[0]['id'].to_i if result && result.respond_to?(:cmd_tuples) && result.cmd_tuples > 0
@@
-        if records.present?
-          records.each do |h|
-            execute "INSERT INTO embeddable_hosts (host, category_id, created_at, updated_at) VALUES ('#{h}', #{category_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
-          end
-        end
+        if records.present?
+          records.each do |h|
+            next if h.blank?
+            quoted_host = ActiveRecord::Base.connection.quote(h)
+            execute "INSERT INTO embeddable_hosts (host, category_id, created_at, updated_at) VALUES (#{quoted_host}, #{category_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
+          end
+        end

(You can also split the data-migration block into a helper method on the migration class if you want to satisfy the ABC-size cop cleanly.)


🏁 Script executed:

#!/bin/bash
# Quick sanity check: ensure the migration still runs on an empty site_settings table.
rg "embed_category" db/migrate -n

Length of output: 276


I'll verify the scope of these issues and confirm the fixes are complete.



🏁 Script executed:

#!/bin/bash
# Check the full migration file to see if there are other unsafe patterns
cat db/migrate/20150818190757_create_embeddable_hosts.rb

Length of output: 1212


🏁 Script executed:

#!/bin/bash
# Search for other similar unsafe execute + array access patterns in migrations
rg "execute\([^)]+\)\[0\]" db/migrate -n

Length of output: 355


🏁 Script executed:

#!/bin/bash
# Search for other direct string interpolation in SQL within migrations
rg 'execute.*\$|execute.*".*#\{' db/migrate -n -C2

Length of output: 8415


🏁 Script executed:

#!/bin/bash
# Verify if ActiveRecord is available in migrations (check Gemfile or Rails version hints)
head -20 Gemfile 2>/dev/null || echo "Gemfile not found at root"

Length of output: 645


🏁 Script executed:

#!/bin/bash
# Check if there are any other embeddable_hosts references that might be affected
rg "embeddable_hosts" db/migrate -n

Length of output: 1011


Fix unsafe SQL usage and missing-row handling in migration—note line 15 also needs the row-check fix

Verification confirms both critical issues:

  1. Missing row handling at lines 11 and 15: Both execute(...)[0] calls assume rows exist without checking. If embed_category or uncategorized_category_id are unset, the migration will raise an IndexError. The fallback at line 16–17 cannot run because the exception at line 11 prevents it.

  2. SQL injection at line 25: Direct string interpolation of the host name will fail for hosts containing single quotes and is a security vulnerability.

The suggested diff fixes line 11 and line 25 correctly, but line 15 has the identical unsafe [0] pattern and also needs the row-check fix:

    if category_id == 0
-     category_id = execute("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'")[0]['value'].to_i
+     result = execute("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'")
+     category_id = result[0]['value'].to_i if result && result.respond_to?(:cmd_tuples) && result.cmd_tuples > 0
    end

Apply the full suggested refactor including the fix for line 15.

Comment on lines +1 to +3
Fabricator(:embeddable_host) do
host "eviltrout.com"
category
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Confirm category fabricators still exist; consider moving this fabricator

This file now only defines Fabricator(:embeddable_host), but many specs (e.g. spec/models/topic_spec.rb) still use Fabricate(:category). Please double‑check that a :category fabricator is still defined elsewhere; if not, specs will fail.

Also, having an :embeddable_host fabricator in category_fabricator.rb is a bit misleading. Consider moving it into an embeddable_host_fabricator.rb file (or similar) for clarity.


🏁 Script executed:

#!/bin/bash
# Verify that a :category fabricator is still defined somewhere.
rg -n "Fabricator\\(:category\\)" spec/fabricators

Length of output: 73


Fabricators are defined in misnamed files; swap or rename them

The verification confirms that :category fabricator still exists, so specs won't fail. However, there's a structural issue: the fabricators are in the wrong files.

  • spec/fabricators/category_fabricator.rb contains Fabricator(:embeddable_host)
  • spec/fabricators/embeddable_host_fabricator.rb contains Fabricator(:category)

Swap the fabricator definitions so each file matches its name, or rename the files to match their contents.

🤖 Prompt for AI Agents
In spec/fabricators/category_fabricator.rb lines 1-3 and the corresponding
spec/fabricators/embeddable_host_fabricator.rb, the fabricator definitions are
placed in the wrong files (category_fabricator.rb defines :embeddable_host and
embeddable_host_fabricator.rb defines :category); fix by swapping the fabricator
definitions so spec/fabricators/category_fabricator.rb contains
Fabricator(:category) and spec/fabricators/embeddable_host_fabricator.rb
contains Fabricator(:embeddable_host), or alternatively rename the two files to
match the fabricator each currently defines, ensuring file names and fabricator
names align.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants