Stimulus Discourse

How to remotely update model

I tried to create a post upvoting downvoting controller as in the screen below.

How can I submit the changes remotely to the model?

For example:

Every time when upvote is clicked, it should update only vote_counter in the model

I have a simple model

rails g scaffold post titel vote_counter:integer

app/views/posts/index.html.erb

  <tbody>
    <% @posts.each do |post| %>
      <tr data-controller="post" data-post-count="<%= post.vote_counter %>" >
        <td><%= post.titel %></td>

        <td>
          <button data-action="post#upvote" class="button success">
            Upvote
          </button>
        </td>

        <td>
          <button data-action="post#downvote" class="button alert">
            Downvote
          </button>
        </td>

        <td>
          <h1 data-target="post.output"><%= post.vote_counter %></h1>
        </td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>

app/javascript/controllers/post_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "output" ]

  initialize() {
    this.outputTarget.textContent = this.count
  }

  upvote() {
    console.log('upvoting')

    this.count++
  }

  downvote() {
    this.count--
  }

  get count() {
    return this.data.get("count")
  }

  set count(value) {
    this.data.set("count", value)
    this.outputTarget.textContent = this.count
  }
}

I have a blog post that describes one way to remotely update a model:

In a nutshell, the fetch() api is going to help you, and you’ll need to create a form data object that will post to your Rails controller action.

Hi John,

I could successfully update db using your example with a checkbox. But I have no clue how to do it without a checkbox. :slight_smile:

The action triggered by the checkbox would become the action triggered by the buttons instead. You can calculate the count, then put that in the form you post to your rails app.

this is what I got so far:

import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "output", "vote_counter" ]

  initialize() {
    this.outputTarget.textContent = this.count
  }

  upvote(event) {
    this.count++
    console.log('upvoting', this.outputTarget.textContent)

      let formData = new FormData()
      formData.append("post[vote_counter]", this.outputTarget.textContent);

      fetch(this.data.get("update-url"), {
        body: formData,
        method: 'PATCH',
        dataType: 'script',
        credentials: "include",
        headers: {
                "X-CSRF-Token": getMetaValue("csrf-token")
                 },
      }).then(function(response) {
        // what do I put here
        // currently I got error messages in the console
        // post_controller.js:17 PATCH http://localhost:3000/posts/1 net::ERR_TOO_MANY_REDIRECTS
        })
    }
  

  downvote() {
    this.count--
  }

  get count() {
    return this.data.get("count")
  }

  set count(value) {
    this.data.set("count", value)
    this.outputTarget.textContent = this.count
  }
}

function getMetaValue(name) {
  const element = document.head.querySelector(`meta[name="${name}"]`)
  return element.getAttribute("content")
}

I found it.

I had to put format.json { render :show, status: :ok, location: @post } before format.html

  def update
    @post = Post.find_by_id params[:id]
    @post.update post_params
    @post.save

    respond_to do |format|
      if @post.update(post_params)
        format.json { render :show, status: :ok, location: @post }
        format.html { redirect_to @post, notice: 'Post was successfully updated.' }
1 Like