-
Notifications
You must be signed in to change notification settings - Fork 0
FEATURE: Can edit category/host relationships for embedding #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: rest-serializer-enhancement-pre
Are you sure you want to change the base?
FEATURE: Can edit category/host relationships for embedding #1
Conversation
|
@coderabbitai review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Key areas requiring careful attention:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this 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 fixturesUsing the injected
assertplus explicit checks oncolorslength/IDs andcategorypresence gives good coverage for the embedded relationships on a single fruit.One minor clarity nit: this test calls
store.find('fruit', 2), but intest/javascripts/helpers/create-pretender.js.es6the fruit that hascolor_ids: [1, 2]is the one withid: 1, and the Pretender handler for/fruits/:idalways returns the first fruit. The test passes, but the coupling is non-obvious. Consider either queryingid: 1here or updating the fixture so the fruit withid: 2has[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 underscoresAdding
'embeddable-host'toADMIN_MODELSand normalizingtypebefore 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_categoryfabricator to separate in-memory and database concernsNo duplicate
Fabricator(:category)was found elsewhere—this file is the sole definition. However, the callback logic should be refined:The
after_buildcallback callscat.update!, which persists to the DB even when tests useFabricate.build(:private_category, ...), yetcategory_groups.buildonly creates an in-memory association and won't persist unless explicitly saved. Splitting intoafter_build(for in-memory state) andafter_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 endAlso ensure that any call to
Fabricate(:private_category)always suppliesgroup: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 - endspec/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 - endapp/assets/javascripts/admin/templates/embedding.hbs (1)
4-6: Consider using CSS classes instead of inline styles.The inline
styleattributes 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'> </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
idattribute and method are unnecessary. ActiveModel Serializers (whichApplicationSerializerlikely inherits from) automatically serializes theidattribute. 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 endspec/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.combe allowed ifeviltrout.comis 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" doexpect(EmbeddableHost.host_allowed?('http://discourse.org')).to eq(true) end - end - endAlso 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.attributeare typically redundant when using theattributesdeclaration. UnlessApplicationSerializerrequires 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 endapp/assets/javascripts/admin/templates/components/embeddable-host.hbs (1)
1-19: Template wiring looks consistent with the componentThe 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 scenariosThe new
expandable_first_post?examples nicely cover:
- no embeddable hosts → false
- embeddable_host present +
embed_truncatetrue +has_topic_embed?true → true- toggling either
embed_truncateorhas_topic_embed?→ falseThat 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
📒 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 expectationsThe new
color_idsonfruits, thecolorscollection, and the inclusion ofcolorsin both/fruits/:idand/fruitsresponses give the store everything it needs to materialize thecolorsrelationship 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 colorsThe updated
findAll embeddedtest now asserts both that the first two fruits share the same farmer instance and that the first fruit’scolorscollection 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: Simplifiedexpandable_first_post?condition looks correctUsing
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‑scopedThe 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: NewadminEmbeddingroute wiring is consistent with existing admin routesThe
adminEmbeddingresource under/customizecleanly maps to/admin/customize/embeddingand matches the existing admin routing style.app/models/topic_embed.rb (1)
35-45: Host‑driven category selection for embeds is reasonableLooking up
eh = EmbeddableHost.record_for_host(url)and passingcategory: 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_hostfabricator explicitly setshost "eviltrout.com"inspec/fabricators/category_fabricator.rb, which matches the test URL'http://eviltrout.com/123'. The test expectation at line 38 checks thatpost.topic.categorymatchesembeddable_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?toEmbeddableHost.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_hostsresource 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_hostswithembed: :idscorrectly 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
embeddingproperty specifically rather than the defaultmodelproperty.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_hostfabricator inspec/fabricators/category_fabricator.rbhashost "eviltrout.com"as its default, which matches the test referer domainhttp://eviltrout.com/some-page. The test setup is correct.app/assets/javascripts/admin/controllers/admin-embedding.js.es6 (2)
9-12: LGTM!The
addHostaction correctly creates a new embeddable-host record and adds it to the collection. Note that this requiressaveChangesto 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_hostmethod safely handles nil input by rescuing URI parsing errors, then returnsfalseearly if the parsed URI is not present or has no host. This meanshost_allowed?(nil)returnsfalse, 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
_idskey is always deleted, even if some_lookupSubTypecalls 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:
- Intentional design (e.g., always preserve array length/structure), or
- 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 fineSwitching 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:onlyto 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
showandupdateserialize 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.
| 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); | ||
| } | ||
| } | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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/javascriptsLength 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).
| deleteHost(host) { | ||
| this.get('embedding.embeddable_hosts').removeObject(host); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.rbLength 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.rbLength 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.rbLength 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.rbLength 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/*.rbLength 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 || trueLength 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
updateaction to process the embeddable_hosts IDs from parameters and delete removed records.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Handle missing embeddable hosts in update/destroy
Both update and destroy assume the record exists:
EmbeddableHost.where(id: params[:id]).firstcan returnnil.save_host(host)andhost.destroywill then raise aNoMethodError.
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
endThis 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/adminLength 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 -40Length of output: 26
🏁 Script executed:
#!/bin/bash
# Check the Admin::AdminController parent class for available methods
find app/controllers -name "*admin_controller.rb" | head -5Length 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 -50Length of output: 1658
🏁 Script executed:
#!/bin/bash
# Inspect Admin::AdminController for available methods
cat app/controllers/admin/admin_controller.rbLength 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 -20Length 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/adminLength of output: 261
Handle missing embeddable hosts in update/destroy
Both update and destroy assume the record exists:
EmbeddableHost.where(id: params[:id]).firstcan returnnil.- Calling
save_host(host)orhost.destroyonnilwill raiseNoMethodError.
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
endThis 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.
| 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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Guard against nil host and tighten host lookup/normalization
Two correctness issues and one style concern here:
before_validationcallsself.host.sub!unconditionally; ifhostis evernil(e.g., building an empty record in a console or via a malformed request), this will raise before validations run.record_for_hostaims 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 nilmodifier 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
+ endThis 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 specLength 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.rbLength of output: 1162
🏁 Script executed:
#!/bin/bash
# Check the admin controller to see how EmbeddableHost is created
cat app/controllers/admin/embeddable_hosts_controller.rbLength 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 2Length of output: 490
Guard against nil host and tighten host lookup/normalization
Two correctness issues and one style concern here:
before_validationcallsself.host.sub!unconditionally; ifhostis evernil(e.g., from an API request with missing or empty host parameter), this will raiseNoMethodErrorbefore validations run.record_for_hostaims 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 nilmodifier 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
+ endThis 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.
| 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.
| 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')" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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; ifembed_categoryis not set,executewill return 0 rows and this will raise before the fallback touncategorized_category_idruns.- The INSERT statement interpolates
hdirectly 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 -nLength 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.rbLength of output: 1212
🏁 Script executed:
#!/bin/bash
# Search for other similar unsafe execute + array access patterns in migrations
rg "execute\([^)]+\)\[0\]" db/migrate -nLength of output: 355
🏁 Script executed:
#!/bin/bash
# Search for other direct string interpolation in SQL within migrations
rg 'execute.*\$|execute.*".*#\{' db/migrate -n -C2Length 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 -nLength 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:
-
Missing row handling at lines 11 and 15: Both
execute(...)[0]calls assume rows exist without checking. Ifembed_categoryoruncategorized_category_idare unset, the migration will raise an IndexError. The fallback at line 16–17 cannot run because the exception at line 11 prevents it. -
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
endApply the full suggested refactor including the fix for line 15.
| Fabricator(:embeddable_host) do | ||
| host "eviltrout.com" | ||
| category |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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/fabricatorsLength 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.rbcontainsFabricator(:embeddable_host)spec/fabricators/embeddable_host_fabricator.rbcontainsFabricator(: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.
Test 10
Summary by CodeRabbit
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.
Replicated from ai-code-review-evaluation/discourse-coderabbit#10