Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Post History

#2: Post edited by user avatar trichoplax‭ · 2024-07-05T16:20:23Z (5 months ago)
Explicitly remove syntax highlighting from pseudocode and bash output for clarity
Please teach me to fish: how do I navigate the edit/suggested-edit code to apply the same error check to both?
  • I was trying to fix what I thought was an [easy bug](https://github.com/codidact/qpixel/issues/1299): suggested edits can add restricted tags (the ones only moderators can apply), which creates a suggested edit that most reviewers can't approve. We already prevent you from adding those tags when you directly edit a post, so I figured -- how hard could it be to apply that same logic to *suggested* edits?
  • Harder than I thought, apparently. I don't know if my mental block is due to insufficient Ruby/Rails knowledge, insufficient MVC clues, or something about the QPixel code in particular.
  • Editing through either path leads to the same form, in `app/views/posts/edit.html.erb`. For direct saves, there's this check:
  • ```ruby
  • <% if @post.errors.any? %>
  • <div class="notice is-danger is-filled">
  • <p>The following errors prevented your post from being saved:</p>
  • <ul>
  • <% @post.errors.full_messages.each do |msg| %>
  • <li><%= msg %></li>
  • <% end %>
  • </ul>
  • </div>
  • <% end %>
  • ```
  • Ok, I said -- how do the errors get there? This question led me to `app/controllers/posts_controller.rb` and specifically the `update` function. I'm having a little trouble following the logic, but the outline seems to be (in pseudocode):
  • ```
  • if current user can edit
  • if current user can push to network
  • (lots of stuff we don't care about here)
  • else (do an edit in a transaction)
  • else // current user cannot edit
  • create suggested edit and send notification to owner
  • ```
  • That last part, in a little more detail:
  • ```ruby
  • data = {
  • post: @post,
  • user: current_user,
  • body: body_rendered == @post.body ? nil : body_rendered,
  • title: params[:post][:title] == @post.title ? nil : params[:post][:title],
  • tags_cache: new_tags_cache == @post.tags_cache ? @post.tags_cache : new_tags_cache,
  • body_markdown: params[:post][:body_markdown] == @post.body_markdown ? nil : params[:post][:body_markdown],
  • comment: params[:edit_comment],
  • active: true, accepted: false
  • }
  • // NOTE the absence of any error checks here
  • edit = SuggestedEdit.new(data)
  • ```
  • This seems to be the place where I should check for errors, same as we do for direct edits. The first block of code I quoted in this question is how we *report* errors to the user, but where do they get *checked* and *set*?
  • I grepped for the text of the error message, which led me to `app/models/post.rb`, in a function called `moderator_tags`. A comment in the source code says "intended to be used for validation" -- sounds like just what I need! So then I grepped for that function name, looking for where it's called.
  • That's where I got stuck:
  • ```bash
  • $ grep -r moderator_tags app/*
  • app/controllers/tags_controller.rb: exec_sql([sql.gsub('$TABLENAME', 'categories_moderator_tags'), @primary.id, @subordinate.id])
  • app/controllers/tags_controller.rb: tables = ['posts_tags', 'categories_moderator_tags', 'categories_required_tags', 'categories_topic_tags',
  • app/controllers/tags_controller.rb~: exec_sql([sql.gsub('$TABLENAME', 'categories_moderator_tags'), @primary.id, @subordinate.id])
  • app/controllers/tags_controller.rb~: tables = ['posts_tags', 'categories_moderator_tags', 'categories_required_tags', 'categories_topic_tags',
  • app/controllers/admin_controller.rb: meta_category.moderator_tags << Tag.unscoped.where(community: @community, name: status_tags)
  • app/models/category.rb: has_and_belongs_to_many :moderator_tags, class_name: 'Tag', join_table: 'categories_moderator_tags'
  • app/models/post.rb: validate :moderator_tags, if: -> { post_type.has_tags && post_type.has_category && tags_cache_changed? }
  • app/models/post.rb: def moderator_tags
  • app/models/post.rb: mod_tags = category&.moderator_tags&.map(&:name)
  • app/views/categories/_form.html.erb: <%= f.select :moderator_tag_ids, options_for_select(@category.moderator_tags.map { |t| [t.name, t.id] },
  • ```
  • What should I do next? How do I find how that validation is done for *regular* edits, so I can (I hope) apply it to the suggested-edit case and prevent an unapprovable suggestion from getting created?
  • I was trying to fix what I thought was an [easy bug](https://github.com/codidact/qpixel/issues/1299): suggested edits can add restricted tags (the ones only moderators can apply), which creates a suggested edit that most reviewers can't approve. We already prevent you from adding those tags when you directly edit a post, so I figured -- how hard could it be to apply that same logic to *suggested* edits?
  • Harder than I thought, apparently. I don't know if my mental block is due to insufficient Ruby/Rails knowledge, insufficient MVC clues, or something about the QPixel code in particular.
  • Editing through either path leads to the same form, in `app/views/posts/edit.html.erb`. For direct saves, there's this check:
  • ```ruby
  • <% if @post.errors.any? %>
  • <div class="notice is-danger is-filled">
  • <p>The following errors prevented your post from being saved:</p>
  • <ul>
  • <% @post.errors.full_messages.each do |msg| %>
  • <li><%= msg %></li>
  • <% end %>
  • </ul>
  • </div>
  • <% end %>
  • ```
  • Ok, I said -- how do the errors get there? This question led me to `app/controllers/posts_controller.rb` and specifically the `update` function. I'm having a little trouble following the logic, but the outline seems to be (in pseudocode):
  • ```text
  • if current user can edit
  • if current user can push to network
  • (lots of stuff we don't care about here)
  • else (do an edit in a transaction)
  • else // current user cannot edit
  • create suggested edit and send notification to owner
  • ```
  • That last part, in a little more detail:
  • ```ruby
  • data = {
  • post: @post,
  • user: current_user,
  • body: body_rendered == @post.body ? nil : body_rendered,
  • title: params[:post][:title] == @post.title ? nil : params[:post][:title],
  • tags_cache: new_tags_cache == @post.tags_cache ? @post.tags_cache : new_tags_cache,
  • body_markdown: params[:post][:body_markdown] == @post.body_markdown ? nil : params[:post][:body_markdown],
  • comment: params[:edit_comment],
  • active: true, accepted: false
  • }
  • // NOTE the absence of any error checks here
  • edit = SuggestedEdit.new(data)
  • ```
  • This seems to be the place where I should check for errors, same as we do for direct edits. The first block of code I quoted in this question is how we *report* errors to the user, but where do they get *checked* and *set*?
  • I grepped for the text of the error message, which led me to `app/models/post.rb`, in a function called `moderator_tags`. A comment in the source code says "intended to be used for validation" -- sounds like just what I need! So then I grepped for that function name, looking for where it's called.
  • That's where I got stuck:
  • ```bash
  • $ grep -r moderator_tags app/*
  • ```
  • ```text
  • app/controllers/tags_controller.rb: exec_sql([sql.gsub('$TABLENAME', 'categories_moderator_tags'), @primary.id, @subordinate.id])
  • app/controllers/tags_controller.rb: tables = ['posts_tags', 'categories_moderator_tags', 'categories_required_tags', 'categories_topic_tags',
  • app/controllers/tags_controller.rb~: exec_sql([sql.gsub('$TABLENAME', 'categories_moderator_tags'), @primary.id, @subordinate.id])
  • app/controllers/tags_controller.rb~: tables = ['posts_tags', 'categories_moderator_tags', 'categories_required_tags', 'categories_topic_tags',
  • app/controllers/admin_controller.rb: meta_category.moderator_tags << Tag.unscoped.where(community: @community, name: status_tags)
  • app/models/category.rb: has_and_belongs_to_many :moderator_tags, class_name: 'Tag', join_table: 'categories_moderator_tags'
  • app/models/post.rb: validate :moderator_tags, if: -> { post_type.has_tags && post_type.has_category && tags_cache_changed? }
  • app/models/post.rb: def moderator_tags
  • app/models/post.rb: mod_tags = category&.moderator_tags&.map(&:name)
  • app/views/categories/_form.html.erb: <%= f.select :moderator_tag_ids, options_for_select(@category.moderator_tags.map { |t| [t.name, t.id] },
  • ```
  • What should I do next? How do I find how that validation is done for *regular* edits, so I can (I hope) apply it to the suggested-edit case and prevent an unapprovable suggestion from getting created?
#1: Initial revision by user avatar Monica Cellio‭ · 2024-06-05T02:12:32Z (6 months ago)
Please teach me to fish: how do I navigate the edit/suggested-edit code to apply the same error check to both?
I was trying to fix what I thought was an [easy bug](https://github.com/codidact/qpixel/issues/1299): suggested edits can add restricted tags (the ones only moderators can apply), which creates a suggested edit that most reviewers can't approve.  We already prevent you from adding those tags when you directly edit a post, so I figured -- how hard could it be to apply that same logic to *suggested* edits?

Harder than I thought, apparently.  I don't know if my mental block is due to insufficient Ruby/Rails knowledge, insufficient MVC clues, or something about the QPixel code in particular.

Editing through either path leads to the same form, in `app/views/posts/edit.html.erb`.  For direct saves, there's this check:

```ruby
<% if @post.errors.any? %>
  <div class="notice is-danger is-filled">
    <p>The following errors prevented your post from being saved:</p>
    <ul>
      <% @post.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>
```

Ok, I said -- how do the errors get there?  This question led me to `app/controllers/posts_controller.rb` and specifically the `update` function.  I'm having a little trouble following the logic, but the outline seems to be (in pseudocode):

```
if current user can edit
  if current user can push to network
    (lots of stuff we don't care about here)
  else (do an edit in a transaction)
else // current user cannot edit
  create suggested edit and send notification to owner
```

That last part, in a little more detail:

```ruby
        data = {
          post: @post,
          user: current_user,
          body: body_rendered == @post.body ? nil : body_rendered,
          title: params[:post][:title] == @post.title ? nil : params[:post][:title],
          tags_cache: new_tags_cache == @post.tags_cache ? @post.tags_cache : new_tags_cache,
          body_markdown: params[:post][:body_markdown] == @post.body_markdown ? nil : params[:post][:body_markdown],
          comment: params[:edit_comment],
          active: true, accepted: false
        }
// NOTE the absence of any error checks here
        edit = SuggestedEdit.new(data)
```

This seems to be the place where I should check for errors, same as we do for direct edits.  The first block of code I quoted in this question is how we *report* errors to the user, but where do they get *checked* and *set*?

I grepped for the text of the error message, which led me to `app/models/post.rb`, in a function called `moderator_tags`.  A comment in the source code says "intended to be used for validation" -- sounds like just what I need!  So then I grepped for that function name, looking for where it's called.

That's where I got stuck:
```bash
$ grep -r moderator_tags app/*
app/controllers/tags_controller.rb:      exec_sql([sql.gsub('$TABLENAME', 'categories_moderator_tags'), @primary.id, @subordinate.id])
app/controllers/tags_controller.rb:      tables = ['posts_tags', 'categories_moderator_tags', 'categories_required_tags', 'categories_topic_tags',
app/controllers/tags_controller.rb~:      exec_sql([sql.gsub('$TABLENAME', 'categories_moderator_tags'), @primary.id, @subordinate.id])
app/controllers/tags_controller.rb~:      tables = ['posts_tags', 'categories_moderator_tags', 'categories_required_tags', 'categories_topic_tags',
app/controllers/admin_controller.rb:    meta_category.moderator_tags << Tag.unscoped.where(community: @community, name: status_tags)
app/models/category.rb:  has_and_belongs_to_many :moderator_tags, class_name: 'Tag', join_table: 'categories_moderator_tags'
app/models/post.rb:  validate :moderator_tags, if: -> { post_type.has_tags && post_type.has_category && tags_cache_changed? }
app/models/post.rb:  def moderator_tags
app/models/post.rb:    mod_tags = category&.moderator_tags&.map(&:name)
app/views/categories/_form.html.erb:      <%= f.select :moderator_tag_ids, options_for_select(@category.moderator_tags.map { |t| [t.name, t.id] },
```

What should I do next?  How do I find how that validation is done for *regular* edits, so I can (I hope) apply it to the suggested-edit case and prevent an unapprovable suggestion from getting created?