rails Archives - ProdSens.live https://prodsens.live/tag/rails/ News for Project Managers - PMI Wed, 03 Jul 2024 21:20:48 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 https://prodsens.live/wp-content/uploads/2022/09/prod.png rails Archives - ProdSens.live https://prodsens.live/tag/rails/ 32 32 New Rails 7.2: Added Rate Limiting https://prodsens.live/2024/07/03/new-rails-7-2-added-rate-limiting/?utm_source=rss&utm_medium=rss&utm_campaign=new-rails-7-2-added-rate-limiting https://prodsens.live/2024/07/03/new-rails-7-2-added-rate-limiting/#respond Wed, 03 Jul 2024 21:20:48 +0000 https://prodsens.live/2024/07/03/new-rails-7-2-added-rate-limiting/ new-rails-7.2:-added-rate-limiting

Ruby on Rails 7.2 added rate limiting to Action Controller. It’s cool and easy to use. What’s New?…

The post New Rails 7.2: Added Rate Limiting appeared first on ProdSens.live.

]]>
new-rails-7.2:-added-rate-limiting

Ruby on Rails 7.2 added rate limiting to Action Controller. It’s cool and easy to use.

rate_limit in Rails 7.2

What’s New?

Rails now offer built-in rate limiting using Redis and Kredis. This empowers you to control how often a user can act within a given time, putting you in the driver’s seat.

Before:

# Manually implemented rate limiting

After:

class SessionsController < ApplicationController

  rate_limit to: 10, within: 3.minutes, only: :create

end

class SignupsController < ApplicationController

  rate_limit to: 1000, within: 10.seconds, by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups!" }, only: :new

end

How It Works

  1. Setup: Ensure you have Redis and Kredis 1.7.0+.
  2. Usage: Add rate_limit in your controllers.

Why This Matters

This feature is great for preventing abuse and managing traffic, and it’s also highly flexible. You can customize limits per action and gracefully handle exceeded limits, making it a perfect fit for your needs.

Conclusion

This update makes managing traffic easier. It’s straightforward and effective. If you’re using Rails, give it a try!

For more details, check out the official pull request.

The post New Rails 7.2: Added Rate Limiting appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/07/03/new-rails-7-2-added-rate-limiting/feed/ 0
When to use `link_to` and `button_to` in Rails https://prodsens.live/2024/07/01/when-to-use-link_to-and-button_to-in-rails/?utm_source=rss&utm_medium=rss&utm_campaign=when-to-use-link_to-and-button_to-in-rails https://prodsens.live/2024/07/01/when-to-use-link_to-and-button_to-in-rails/#respond Mon, 01 Jul 2024 09:20:31 +0000 https://prodsens.live/2024/07/01/when-to-use-link_to-and-button_to-in-rails/ when-to-use-`link-to`-and-`button-to`-in-rails

This article was originally published on Rails Designer This article about forms inside forms triggered a question from…

The post When to use `link_to` and `button_to` in Rails appeared first on ProdSens.live.

]]>
when-to-use-`link-to`-and-`button-to`-in-rails

This article was originally published on Rails Designer

This article about forms inside forms triggered a question from a reader: when to use link_to and button_to in Rails? So I wanted to publish this short and quick article.

Rails has had both helpers from the very early beginnings. link_to likely from the first release, and button_to not much later.

With the release of Rails’ ujs, link_to could now accept data-attributes as seen in these older docs. This made many Rails developers belief this the right way to do it, but: you should use link_to for simple navigation and GET requests, and button_to for actions that submit data or require additional security, such as POST, PUT, or DELETE requests.

I’ve seen some nasty HTML-element abuse in pure JavaScript apps too. Something like this:

<div onClick="doClick()">Click me</div>

(that’s like using a spoon to cut your steak—technically possible, but awkwardly ineffective and a nightmare for accessibility!)

button_to should also, for better accessibility and semantic correctness, be used to open modal or slide-overs. I could present a counterpoint if the modal or slide-over could also be opened as a separate page.

Also when you replace a element with say, an inline-form, a button_to is the correct choice.

It’s as simple as that really. Is it GET request and does it change the URL? Go for a link_to. Otherwise choose button_to.

The post When to use `link_to` and `button_to` in Rails appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/07/01/when-to-use-link_to-and-button_to-in-rails/feed/ 0
How to Create an Undo Action with Rails https://prodsens.live/2024/06/20/how-to-create-an-undo-action-with-rails/?utm_source=rss&utm_medium=rss&utm_campaign=how-to-create-an-undo-action-with-rails https://prodsens.live/2024/06/20/how-to-create-an-undo-action-with-rails/#respond Thu, 20 Jun 2024 10:20:31 +0000 https://prodsens.live/2024/06/20/how-to-create-an-undo-action-with-rails/ how-to-create-an-undo-action-with-rails

This article was originally published on Rails Designer. An undo-action is a common UX paradigm in web-apps. You…

The post How to Create an Undo Action with Rails appeared first on ProdSens.live.

]]>
how-to-create-an-undo-action-with-rails

This article was originally published on Rails Designer.

An undo-action is a common UX paradigm in web-apps. You delete, archive or send something and can then undo that action. Often you see it with a flash/notification component like this one:

Notification component showing Email deleted text + an undo action

(example coming from Rails Designer’s NotificationComponent)

After the message was soft-deleted, or put in the “trash”, the notification confirms the action and gives the option to undo.

Some time ago Gmail added the option to “undo” sending an email. Of course, the sending wasn’t actually undone, but rather the sending had not yet happened.

Showing Gmail's undo send notification

Other examples could be:

  • Undo Archive;
  • Undo Merge;
  • Undo Mark as Read;
  • Undo Publish;
  • Undo Schedule.

How each action is actually made “undone” is different on the back-end side. Undo Archive might simply remove the archived_at timestamp, similarly with Undo Mark as Read. But Undo Merge might be quite more complicated to pull off.

So let’s go over the product bit, that’s the same for each action. For this article I’ve chosen an “Undo Email Delete”.

Undo Action or Confirm Action?

But when to choose an Undo-action or a Confirm Dialog?

  • Undo action: do the action, then give the option to undo it;
  • Confirm action: ask before doing the action.

They both have there place, depending on the action and the consequence. This is the rule-of-thumb I typically use:

Use a Confirm Dialog when the action simply cannot be undone. You actually delete the record from the database or the action is so destructive to one or many rows it’s impossible to reverse.

And an Undo action can then be used for non-destructive actions: you soft-delete a record, you add the sending to a background queue with a delay or the action simply sets a timestamp (eg. archived_at).

Next to that, with the examples of deleting or archiving a record, there might not be a need for an undo action. Most users are familiar with these patterns. Also if you rename the action to Move to Bin instead of just Delete they certainly will know where to find the record and move it back.

One last point when building a new feature: I typically would default to the Confirm Dialog’s, especially because Rails Designer makes it super easy to add beautiful ones. This is the kind of lean-building I’ve been practicing for over eight years. Your users will let you know once it annoys them!

Build an Undo Action with Rails

Note: I’m assuming you have access to Rails Designer’s UI Components Library.

With that all out of the way, let’s build the Undo Email Delete with Rails. Let’s first set the stage:

You have a model named Email (app/models/email.rb) that has a deleted_at timestamp. You could also use something like the Discard gem (my preferred choice).

Let’s focus on the controller to soft-delete the email.

# app/controllers/emails_controller.rb
class EmailsController < ApplicationController
  before_action :set_email, only: %w[destroy]
  # …
  def destroy
    @email.update(deleted_at: Time.current)

    redirect_to emails_path, notice: "Email deleted successfully."
  end

  def set_email
    @email = Email.find(params[:id])
  end
  # …
end

Currently it’s your standard Rails controller. Onto the turbo stream response.

# app/views/emails/destroy.turbo_stream.erb
<%= stream_notification "Your message was deleted", primary_action: {title: "Undo", path: restore_email_path(@email), method: :patch}, time_delay: 10, progress_indicator: "progress_bar" %>

For this to work, grab the Undoable action variant from the NotificationComponent. If you don’t have Rails Designer (get it here!), check out this article about more advanced flash messages in Rails.

If you wonder where stream_notification comes from: it’s a helper that’s included in Rails Designer.

The astute reader noticed the restore_email_path helper. Let’s create the route first and then controller:

# config/routes.rb
# …
resources :restore_emails, only: [:update]
# app/controllers/restore_emails_controller.rb
class RestoreEmailsController < ApplicationController
  def update
    @email.update(deleted_at: nil)

    redirect_to emails_path, notice: "Email was successfully restored."
  end

  private

  def set_email
    @email = Email.find(params[:id])
  end
end

Again, your standard Rails controller. Let’s create a Turbo Stream response for the update action.

# app/views/emails/update.turbo_stream.erb
<%= stream_notification "Moved from the Bin" %>

Based on your app’s set up you can extend this response by injectin the previously-deleted email in the email list. This could be with a Turbo Stream append action (turbo_stream.append("inbox", partial: "emails/email", locals: { email: @email })) or by refreshing if it’s a turbo frame (turbo_stream.turbo_frame_reload(#inbox); this is using the turbo_power package).

And that’s how simple adding an undo action is with Rails and Turbo. Again: undo action can be an elegant UX, but keep in mind the context it’s used for and at what stage your product is.

Anything that’s missing or unclear? Let me know below.

The post How to Create an Undo Action with Rails appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/06/20/how-to-create-an-undo-action-with-rails/feed/ 0
Rails Designer v0.11.0 released: new Command Menu https://prodsens.live/2024/05/06/rails-designer-v0-11-0-released-new-command-menu/?utm_source=rss&utm_medium=rss&utm_campaign=rails-designer-v0-11-0-released-new-command-menu https://prodsens.live/2024/05/06/rails-designer-v0-11-0-released-new-command-menu/#respond Mon, 06 May 2024 00:20:10 +0000 https://prodsens.live/2024/05/06/rails-designer-v0-11-0-released-new-command-menu/ rails-designer-v011.0-released:-new-command-menu

Version 0.11.0 was just released with three new components! Let’s jump right into what’s new at Rails Designer:…

The post Rails Designer v0.11.0 released: new Command Menu appeared first on ProdSens.live.

]]>
rails-designer-v011.0-released:-new-command-menu

Version 0.11.0 was just released with three new components!

Let’s jump right into what’s new at Rails Designer:

📦 New

💅 Additions

🎷🐛 Bugfix

  • Fix icon comment on Modal Component variant

From the Blog

Other things

Last week I released a new gem: rails_icons. It is an icon library-agnostic gem. And will soon be included by default in Rails Designer.

Interested in sharing Rails Designer with the Ruby on Rails community? Become an affiliate (and get 25% commision)! 🤑

The post Rails Designer v0.11.0 released: new Command Menu appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/05/06/rails-designer-v0-11-0-released-new-command-menu/feed/ 0
How to Add Disabled State to Buttons with Turbo & Tailwind CSS https://prodsens.live/2024/05/02/how-to-add-disabled-state-to-buttons-with-turbo-tailwind-css/?utm_source=rss&utm_medium=rss&utm_campaign=how-to-add-disabled-state-to-buttons-with-turbo-tailwind-css https://prodsens.live/2024/05/02/how-to-add-disabled-state-to-buttons-with-turbo-tailwind-css/#respond Thu, 02 May 2024 09:20:50 +0000 https://prodsens.live/2024/05/02/how-to-add-disabled-state-to-buttons-with-turbo-tailwind-css/ how-to-add-disabled-state-to-buttons-with-turbo-&-tailwind-css

This post was originally published on Rails Designer. If you’ve used Rails’ UJS, which is now defunct, you…

The post How to Add Disabled State to Buttons with Turbo & Tailwind CSS appeared first on ProdSens.live.

]]>
how-to-add-disabled-state-to-buttons-with-turbo-&-tailwind-css

This post was originally published on Rails Designer.

If you’ve used Rails’ UJS, which is now defunct, you probably used the data-disable-with="" attribute in the past. It worked like this:

<%= form_for @post do |f| %>
  <⁠%= f.submit "Save", data: { disable_with: "Saving…" } %>
<% end %>

When the form was submitted, the submit button would be disabled and shown “Saving…” instead of “Save”. Nifty! No JavaScript required!

But the world has moved on and is now using Hotwire. Initially, when Hotwire was announced in December 2020, there was no alternative for data-disable-with. But forward to November 2021, button’s submitted “through” Turbo now (prior to turbo:submit-start) get the disabled attribute and it is removed prior to firing turbo:submit-end.

Sidenote: this PR added the option to mimic the previous UJS behaviour. I’d like a bit more control, so I prefer the method outlined here.

That is all you need to show some fancy-pants submit buttons. The PR above shows how you can do it with vanilla CSS. Like so:

button .show-when-disabled { display: none; }
button[disabled] .show-when-disabled { display: initial; }

button .show-when-enabled { display: initial; }
button[disabled] .show-when-enabled { display: none; }

Which works great, but my preferred tool is Tailwind CSS, so I can create a component just for a button and have everything contained in one file.

Let’s look how this can be done with Tailwind CSS’ utility classes instead. Let’s use the same HTML from above:

 class="group/button">
   class="block group-disabled/button:hidden">Submit
   class="hidden group-disabled/button:block">Submitting…

With some extra styles attached the button looks like this when enabled:

Image description

And like this with the disabled attribute added:

Image description

It works using the group/button (always name your Tailwind CSS’ groups!). Then it’s just using the disabled modifier-utility.

You can have a play with it in this Tailwind Play (try adding the disabled attribute to the button).

You are of course not limited to plain text in the disabled-state, you can add icons, change the style and add transitions.

Check Rails Designer’s ButtonComponent for inspiration. ✌

The post How to Add Disabled State to Buttons with Turbo & Tailwind CSS appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/05/02/how-to-add-disabled-state-to-buttons-with-turbo-tailwind-css/feed/ 0
How Rails Powers PopaDex for Simplified Financial Planning https://prodsens.live/2024/03/31/how-rails-powers-popadex-for-simplified-financial-planning/?utm_source=rss&utm_medium=rss&utm_campaign=how-rails-powers-popadex-for-simplified-financial-planning https://prodsens.live/2024/03/31/how-rails-powers-popadex-for-simplified-financial-planning/#respond Sun, 31 Mar 2024 05:20:19 +0000 https://prodsens.live/2024/03/31/how-rails-powers-popadex-for-simplified-financial-planning/ how-rails-powers-popadex-for-simplified-financial-planning

The current landscape of personal finance management is full of bloated apps, half of which have a business…

The post How Rails Powers PopaDex for Simplified Financial Planning appeared first on ProdSens.live.

]]>
how-rails-powers-popadex-for-simplified-financial-planning

The current landscape of personal finance management is full of bloated apps, half of which have a business model based on selling user data. PopaDex is a net worth tracking app that instead focuses on simplicity and ease of use to let users plan their finances. This article is about how Ruby on Rails, known for its ‘convention over configuration’ philosophy, has enabled PopaDex to rapidly develop a user-friendly platform that enables financial planning for its users. PopaDex uses some of Rails’ latest features like solid_queue, and deployment tools like Kamal, to enhance user experience and maintain a robust, scalable service, all while getting to market quickly.

SolidQueue for Efficient Background Processing

One of the key challenges in any applications is managing long-running tasks without affecting the user experience. PopaDex leverages the solid_queue gem to handle background processing efficiently. This “DB-based queuing backend for Active Job” allows for tasks such as report generation and notifications to be processed in the background, ensuring the application remains responsive. The beauty of solid_queue lies in its simplicity and efficiency, obviating the need for more complex solutions like Redis or Sidekiq for background job management. This choice offers several distinct advantages:

  1. Reduced Complexity: This simplicity is invaluable, especially for smaller teams (or a team of 1 in this case) and projects where resources are limited. By keeping the technology stack simple and manageable, more time and resources to can be dedicated to enhancing the core features of the app, rather than getting bogged down by complex infrastructure management.

  2. Lower Surface Area: Without the need to integrate and maintain additional infrastructure like Redis or Sidekiq, PopaDex benefits from a leaner stack. This not only simplifies deployment but also reduces potential points of failure, making the app more robust.

Kamal for Effortless Deployment

Navigating the deployment landscape can be a daunting task for any developer, particularly those going it alone. PopaDex uses Kamal as its deployment solution, due to its straightforward and efficient nature. This tool is designed to facilitate the quick deployment of Rails applications across diverse environments, streamlining what can often be a complicated process. Kamal’s appeal lies in its ability to demystify deployment, removing the hurdles commonly associated with Docker and app deployment in general. A couple of the main benefits of Kamal:

  1. Simplified Deployment Process: Kamal’s user-friendly approach means that even developers working solo can confidently deploy their applications without the need for extensive DevOps knowledge.

  2. The robustness of being able to deploy swiftly to various Virtual Private Servers is also a great feature. PopaDex can maintain a high level of service availability and performance, as it allows for rapid scaling and redundancy across different hosting environments.

Omakase and CoPilot

The “omakase” approach of Rails works exceptionally well with tools like GitHub Copilot. Copilot thrives on context and convention, making Rails’ standardised environment an ideal setting. Given Rails’ convention over configuration ethos, Copilot can more accurately predict and offer suggestions that align with the Rails way of doing things, therefore accelerating development time even further. For example, when working within a Rails application, Copilot can quite accurately suggest controller actions or model validations, streamlining the development process.

Moreover, the “omakase” nature of Rails means that most of the gems and tools recommended by the Rails community are widely adopted and well-documented. This widespread adoption provides a rich dataset for GitHub Copilot to draw from, meaning that its suggestions are accurate and adhere to community best practices, more often than not. Naturally, this means solo developers can write more efficient, secure, and clean code for a huge variety of applications.

The Rails Architecture: An Ideal Tool for Solo Developers

Rails is a solo entrepreneur’s best friend. Seriously, its monolithic and cohesive nature makes it a dream for anyone trying to hit the market fast and without a VC-funded engineering team. Using gems like solid_queue for those behind-the-scenes tasks and Kamal for fast and efficient deployment means that PopaDex keeps things running smooth and allows us to focus on adding value to our users. It’s (famously) like having a Swiss Army knife; Rails equips developers with a curated set of features that streamline the development process, making it possible to achieve more with less.

Finally, Rails is only getting better. With Rails 8 on the horizon, progressive web apps will be first-class citizens and a whole host of other juicy features are setting the stage for an even more mature framework. Rails is going to reinforce its position as a mature and forward-thinking framework, and it has made all the difference in taking PopaDex to market.

The post How Rails Powers PopaDex for Simplified Financial Planning appeared first on ProdSens.live.

]]>
https://prodsens.live/2024/03/31/how-rails-powers-popadex-for-simplified-financial-planning/feed/ 0
Data-Attributes Magic with Tailwind CSS & Stimulus https://prodsens.live/2024/03/28/data-attributes-magic-with-tailwind-css-stimulus/?utm_source=rss&utm_medium=rss&utm_campaign=data-attributes-magic-with-tailwind-css-stimulus https://prodsens.live/2024/03/28/data-attributes-magic-with-tailwind-css-stimulus/#respond Thu, 28 Mar 2024 20:20:32 +0000 https://prodsens.live/2024/03/28/data-attributes-magic-with-tailwind-css-stimulus/ data-attributes-magic-with-tailwind-css-&-stimulus

This article was originally published on Rails Designer Changing the way some, or all, UI elements on an…

The post Data-Attributes Magic with Tailwind CSS & Stimulus appeared first on ProdSens.live.

]]>
data-attributes-magic-with-tailwind-css-&-stimulus

This article was originally published on Rails Designer

Changing the way some, or all, UI elements on an user interaction (click, hover, etc.) is really common. So you better have a good process to use this common flow.

There are basically three ways to go about this:

  1. add the CSS class(es) to every element that needs changing;
  2. add CSS classes to the parent element (eg. ) to enable cascading of additional styles from it to child elements;
  3. add a data attribute to the parent element (eg. ) to enable cascading of additional styles from it to child elements.

I don’t think I need to go over option 1, as that would never be a maintainable option. So let’s check out option 2 first, followed by option 3. As I use Tailwind CSS exclusively that’s what these examples use.

 class="group/nav nav-opened">
   class="hidden group-[.nav-opened]/nav:block">
    
  • Item here
  • I’m using named grouping, opposed to just group (which is a good thing to do any way).

    I don’t think this solution is really bad, especially if you name the CSS class (.nav-opened) well. But now check out the option with a data attribute.

     data-nav-opened class="group/nav">
       class="hidden group-data-[nav-opened]/nav:block">
        
  • Item here
  • Quite similar, right? Actually it is a bit longer than the CSS class option. But the more important point is that it keeps concerns separated (CSS classes for aesthetics and data-attributes for states and behavior).

    Quick pro-tip. The following works just as well with Tailwind CSS. It uses the open-modifier.

     open class="group/nav">
       class="hidden group-open/nav:block">
         class="text-orange-500">Item here
      
    
    

    So far, the examples shown were really simple. But there’s no reason you cannot expand what happens when the parent’s “state” changes. A common thing you see is a chevron that changes rotation. Something like this:

     data-nav-opened class="group/nav">
       class="flex items-center gap-1">
        Open
         xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="w-3 h-3 transition group-data-[nav-opened]/nav:rotate-180">
           fill-rule="evenodd" d="M12.53 16.28a.75.75 0 0 1-1.06 0l-7.5-7.5a.75.75 0 0 1 1.06-1.06L12 14.69l6.97-6.97a.75.75 0 1 1 1.06 1.06l-7.5 7.5Z" clip-rule="evenodd"/>
        
      
    
       class="hidden group-data-[nav-opened]/nav:block">
        
  • Item here
  • Notice the svg inside the button that flips 180º when data-nav-opened is added? From here on out, it’s only your imagination that is the limiting factor.

    How can we combine this with Stimulus?

    Stimulus is a great choice for user interactions like this as it’s really declarative, this works great together with the Tailwind CSS setup explained above.

    The following Stimulus controller is one I use often (and comes together with some of the Rails Designer Components).

    // app/javascript/controllers/data_attribute_controller.js
    import { Controller } from "@hotwired/stimulus";
    
    export default class extends Controller {
      static values = { key: String };
    
      disconnect() {
        this.element.removeAttribute(this.keyValue);
      }
    
      toggle() {
        this.element.toggleAttribute(`data-${this.keyValue}`);
      }
    }
    

    That’s the whole controller! With the above example, this is how it’s used:

     data-controller="data-attribute" data-attribute-key-value="nav-opened" class="group/nav">
       data-action="data-attribute#toggle" class="flex items-center gap-1">
        Open
         xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="w-3 h-3 transition group-data-[nav-opened]/nav:rotate-180">
           fill-rule="evenodd" d="M12.53 16.28a.75.75 0 0 1-1.06 0l-7.5-7.5a.75.75 0 0 1 1.06-1.06L12 14.69l6.97-6.97a.75.75 0 1 1 1.06 1.06l-7.5 7.5Z" clip-rule="evenodd"/>
        
      
    
       class="hidden group-data-[nav-opened]/nav:block">
        
  • Item here
  • And that’s how, by combining two—really developer-friendly—tools, you can create usable (Rails) applications with a minimal amount of code.

    The post Data-Attributes Magic with Tailwind CSS & Stimulus appeared first on ProdSens.live.

    ]]>
    https://prodsens.live/2024/03/28/data-attributes-magic-with-tailwind-css-stimulus/feed/ 0
    How to Properly Structure Stimulus Controller https://prodsens.live/2024/03/21/how-to-properly-structure-stimulus-controller/?utm_source=rss&utm_medium=rss&utm_campaign=how-to-properly-structure-stimulus-controller https://prodsens.live/2024/03/21/how-to-properly-structure-stimulus-controller/#respond Thu, 21 Mar 2024 20:20:41 +0000 https://prodsens.live/2024/03/21/how-to-properly-structure-stimulus-controller/ how-to-properly-structure-stimulus-controller

    This article was previously published on Rails Designer. Over the years many articles have been written about organizing…

    The post How to Properly Structure Stimulus Controller appeared first on ProdSens.live.

    ]]>
    how-to-properly-structure-stimulus-controller

    This article was previously published on Rails Designer.

    Over the years many articles have been written about organizing and structuring your Ruby (on Rails) code. Many Rails developers hold some (very) strong opinions on the best way to go about it.

    But when it comes to JavaScript it’s a lot quieter in the Ruby on Rails Blogosphere, because JavaScript: “yuck”, “meeh”, “no good”, “run”, “painful”. But I’m here to tell you that JavaScript has become a pretty good language that’s quite a joy to write.

    So I wanted to lay out some guidelines/tips to help you write better Stimulus controllers (which makes the little bit JavaScript you have to write even more joyous). This is unlikely the-best-way™, but it’s á way that helps you keep your Stimulus controller consistent. This takes away some decisions and helps the future-you debug or change the code.

    Keep public functions to a minimum

    Keeping the public API minimal is a rule (of thumb) many Ruby developers know. You can use the same rule in your Stimulus controllers. So next to the lifecycle methods initialize(), connect() and disconnect(), only add the functions for the actions that you create (ie. those that are fired based on event listeners, eg. data-action="click->tooltip#show").

    Make non-public functions truly private

    With Ruby you can make methods private by placing them below the private keyword. In JavaScript you can do the same. By using the #-symbol before the function. When you prefix a function or property name with # in a Stimulus controller (or any JavaScript class), it becomes accessible only within that class’s methods and cannot be accessed from outside the class.

    #setupTooltip() {
      // logic here
    }
    

    Use getters to set or transform certain values

    In a Stimulus controller (and any JavaScript class) a getter, allows a computed property to be defined that is dynamically generated each time it is accessed. It doesn’t take any arguments and you call them similar to properties, like so: this.#validatedPositionValue.

    I like to use getters. Mostly to validate if a certain value is passed correctly, like this:

    export default class extends Controller {
      // …
      static values = {position: {type: String, default: "top"}};
      // …
    
      get #validatedPositionValue() {
        if (["top", "right", "bottom", "left"].includes(this.positionValue)) {
          return this.positionValue;
        } else {
          console.error("Invalid position value");
    
          return "top";
        }
      }
      // …
    }
    

    Instead of using this.positionValue I now use this.validatedPositionValue.

    Keep the order for all functions consistent

    You don’t need to copy this exact same order, but what ís important, is that you stick to a consistent order for every Stimulus controller. So why not just copy this one—yet another thing not to think about!

    // Lifecycle functions first
    initialize() // optional, if needed
    
    connect() // optional, if needed
    
    disconnect() // optional, if needed
    
    // Public functions, anything that is called with the event listeners within `data-action`'s
    show()
    
    hide()
    
    // The following line is purely for visual purposes, but it is
    // similar how Ruby classes work, and that is why I add it
    // private
    
    // All functions that are called by the public functions (by order of being called)
    #privateFunction()
    
    // Functions that are called by the private functions
    #privateDetailFunction()
    
    // Lastly all `getters` and `setters`
    get #someSetting()
    

    Example

    And finally, let’s look at a real component from Rails Designer. I’ve only kept the essential bits to highlight how all above guidelines are used in a real Stimulus controller.

    import { Controller } from "@hotwired/stimulus";
    import { computePosition } from "./helpers/position_computings";
    
    export default class extends Controller {
      static targets = ["tooltip"];
      static values = {content: String};
    
      disconnect() {
        this.tooltipTarget.remove();
      }
    
      show() {
        this.#computePosition();
    
        this.tooltipTarget.removeAttribute("hidden");
      }
    
      hide() {
        this.tooltipTarget.setAttribute("hidden", true);
      }
    
      // private
    
      #computePosition() {
        computePosition(this.element, this.tooltipTarget, {
          placement: this.#validatedPositionValue
        });
      }
    
      get #validatedPositionValue() {
        if (this.#allowedPositions.includes(this.positionValue)) {
          return this.positionValue;
        } else {
          console.error(`Invalid position value.`);
    
          return "top";
        }
      }
    
      get #allowedPositions() {
        return ["top", "top-start", "top-end", "right", "right-start", "right-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end"];
      }
    }
    

    The post How to Properly Structure Stimulus Controller appeared first on ProdSens.live.

    ]]>
    https://prodsens.live/2024/03/21/how-to-properly-structure-stimulus-controller/feed/ 0
    Why choose ViewComponent over Rails partials https://prodsens.live/2024/03/07/why-choose-viewcomponent-over-rails-partials/?utm_source=rss&utm_medium=rss&utm_campaign=why-choose-viewcomponent-over-rails-partials https://prodsens.live/2024/03/07/why-choose-viewcomponent-over-rails-partials/#respond Thu, 07 Mar 2024 20:20:05 +0000 https://prodsens.live/2024/03/07/why-choose-viewcomponent-over-rails-partials/ why-choose-viewcomponent-over-rails-partials

    ViewComponent, inspired by React, was introduced at RailsConf 2019. It gives better data flow, easier to test views…

    The post Why choose ViewComponent over Rails partials appeared first on ProdSens.live.

    ]]>
    why-choose-viewcomponent-over-rails-partials

    ViewComponent, inspired by React, was introduced at RailsConf 2019. It gives better data flow, easier to test views and cleaner view code.

    There were a few other options before ViewComponent (and few after), but with the backing of GitHub, ViewComponent is my tool of choice (heck, Rails Designer is built with it!).

    Is it also something you should consider? After all, Rails comes with partials and helpers out-of-the-box. Why add another dependency?

    So, first off, the pros and cons of View Component:

    Pros:

    • improved code organization
    • performance improvements
    • extending and composing

    Cons:

    • another dependency
    • over-engineering trap
    • learning curve

    Why not use partials and helpers?

    Partials and helpers are first-party citizens in any Rails app. Developers know them and have used them for years. I’d say: use them. I default to partials. And only move to a ViewComponent when I need to some more advanced wrangling of data. This is where Rails conventions usually dictate to use helpers, or—more fancy—decorators or presenters.

    The biggest issue with helpers is that they are global. That name-method you defined in UserHelper is available in all views and partial, not just to magical user object only. Conventions on naming could help here, but that’s not ideal.

    Improved performance

    ViewComponent are noticeable faster than partials. This boost is primarily attributed to the pre-compilation of all ViewComponent templates at application startup, as opposed to the runtime (like partials). The improvements are most notable with lots of embedded Ruby.

    ViewComponent claim they are ~10× faster than partials in real-world use-cases. Testing components are quicker too (or maybe testing traditional Rails views are slow!).

    It must be said that if you run a small to medium-sized Rails app, this doesn’t apply to you.

    How does ViewComponent work?

    Typically components live in app/components and look like this (examples taking from the ViewComponent docs):

    class MessageComponent < ViewComponent::Base
      erb_template <<-ERB
        

    Hello, <%= @name %>!

    ERB def initialize(name:) @name = name end end

    And are instantiated like so:

    <%= render(MessageComponent.new(name: "World"))
    

    A test could then look like this:

    require "test_helper"
    
    class MessageComponentTest < ViewComponent::TestCase
      def test_render_component
        render_inline(ExampleComponent.new(name: "Hello, World!"))
    
        assert_text("Hello, World!")
      end
    end
    

    More advanced UI components

    As ViewComponent are simply just plain Ruby objects, you can extend them or use composition. Making your components more reusable and dry up some code. Next to you can use “slots”, ”collections” and “conditional rendering”.

    Slots

    I’ve come to really use slots quite often. Once you know when to use them, you see ways to use them all the time (if you checked the components from Rails Designer you know what I mean).

    Some examples:

    • PageHeadingComponent; with optional page actions (think: “Create” and “View”)
    • ModalComponent, optional heading element

    ViewComponent comes with two flavors: renders_one and renders_many. Take a look at the following example:

    # blog_component.rb
    class BlogComponent < ViewComponent::Base
      renders_one :header
      renders_many :posts
    end
    
    <%# blog_component.html.erb %>
    

    <⁠%= header %>

    <⁠% posts.each do |post| %> <⁠%= post %> <⁠% end %>
    <%# index.html.erb %>
    <⁠%= render BlogComponent.new do |component| %>
      <⁠% component.with_header do %>
        <⁠%= link_to "My blog", root_path %>
      <⁠% end %>
    
      <⁠% BlogPost.all.each do |blog_post| %>
        <⁠% component.with_post do %>
          <⁠%= link_to blog_post.name, blog_post.url %>
        <⁠% end %>
      <⁠% end %>
    <⁠% end %>
    

    This is a very simple example. Take a look at the docs for more details.

    Collections

    Just like Rails’ partials, ViewComponent supports collections too. Take this example:

    <%= render(ProductComponent.with_collection(@products)) %>
    
    class ProductComponent < ViewComponent::Base
      def initialize(product:)
        @product = product
      end
    end
    

    I tend not to use collections all too much. Imagine the ProductComponent template like this to go with the above component class:

    <li>
      <%= @product.name %>
    
    

    Besides not being valid HTML. I now need to remember to manually wrap the component with

      -element. Potentially missing some important CSS classes too. No good. I prefer to instead loop over the collection manually inside the component. Keeping things tidy and contained.

      Conditional rendering

      This is a feature I often use. Instead of wrapping a partial in a conditional:

      <% unless Current.user.subscribed? %>
        <⁠%= render partial: "subscribe_form", locals: { user: Current.user} %>
      <% end %>
      

      You instantiate the component:

      <%= render SubscribeFormComponent.new(user: Current.user) %>
      

      and add the render? method in the component class:

      class SubscribeFormComponent < ViewComponent::Base
        # …
        def render?
          !Current.user.subscribed?
        end
        #…
      end
      

      Based on the conditional the component is then rendered or not. This clean up the view quite a bit, don’t you think?

      These are a few of the upsides to me of using ViewComponent instead of partials. It’s not an all-or-nothing situation, most of my Rails apps still use a fair amount of partials. But they need to be simple; no extra view logic needed. I otherwise move it to a ViewComponent.

      The post Why choose ViewComponent over Rails partials appeared first on ProdSens.live.

      ]]> https://prodsens.live/2024/03/07/why-choose-viewcomponent-over-rails-partials/feed/ 0 Publish/Subscribe with Sidekiq https://prodsens.live/2024/02/21/publish-subscribe-with-sidekiq/?utm_source=rss&utm_medium=rss&utm_campaign=publish-subscribe-with-sidekiq https://prodsens.live/2024/02/21/publish-subscribe-with-sidekiq/#respond Wed, 21 Feb 2024 09:20:54 +0000 https://prodsens.live/2024/02/21/publish-subscribe-with-sidekiq/ publish/subscribe-with-sidekiq

      Introduction Our Rails application is an old monolith that relies heavily on after/before callbacks to trigger code that…

      The post Publish/Subscribe with Sidekiq appeared first on ProdSens.live.

      ]]>
      publish/subscribe-with-sidekiq

      Introduction

      Our Rails application is an old monolith that relies heavily on after/before callbacks to trigger code that has side effects. This usually means that one model can change records of another model. I suspect that we all have come across this “callback hell” at least once in our professional life.

      We needed to introduce a new service for search. As we settled on using meilisearch, we needed a way to sync updates on our models with the records in meilisearch. We could’ve continued to use callbacks but we needed something better.

      We settled on a Publish/Subscribe (Pub/Sub) pattern.
      Pub/Sub is a design pattern where publishers broadcast messages to topics without specifying recipients, and subscribers listen for specific topics without knowing the source, promoting loose coupling.

      Exploring Pub/Sub Solutions

      While searching for a solution we came across these:

      • ActiveSupport::Notification: A Rails component for defining and subscribing to events within an application​.
        -> Active::Support is synchronous by default. It does not handle errors and retries out of the box. We would need to use a background job handler (like Sidekiq) to make it asynchronous.

      • Wisper: A Ruby gem providing a decoupled communication layer between different parts of an application​
        -> I personally dislike wisper. I used it in the past and dislike the way of defining subscribers in a global way. I wanted topics to be arbitrary and each class to define what to subscribe for itself.

      • Dry-events: A component of the dry-rb ecosystem focused on event publishing and subscription​
        -> This gem looks like a framework on top of which I would need to create my own Pub/Sub. It also does not do asynchronous execution out of the box.

      We finally settled on Sidekiq!
      Sidekiq is a background job processing tool, which we already use in our application and we figured a way to use it for processing Pub/Sub messages.

      Implementation

      Implementation is quite straightforward.

      class PubSub
        class SubscriberJob < ApplicationJob
          queue_as :pub_sub
      
          def perform(message:, class_name:, handler:)
            class_name.constantize.public_send(handler, message)
          end
        end
      
        include Singleton
      
        # Publish a message to topic
        #
        # @param topic [String]
        # @param message [Hash]
        def self.publish(topic, **message)
         instance.subscribers(topic).each do |subscriber|
            PubSub::SubscriberJob.perform_later(message: message, **subscriber)
          end
        end
      
        # Subscribe a class + handler to a topic
        #
        # @param topic [String]
        # @param class_name [String]
        # @param handler [String]
        def self.subscribe(topic, class_name, handler, async: true)
          instance.subscribe(topic, class_name, handler)
        end
      
        def initialize
          @subscribers = {}
        end
      
        # return subscribers for the topic
        #
        # @param topic [String]
        # @return [Array] { class_name: String, handler: String}
        def subscribers(topic)
          @subscribers.fetch(topic, Set.new)
        end
      
        # Subscribe a class + handler to a topic
        #
        # @param topic [String]
        # @param class_name [String]
        # @param handler [String]
        def subscribe(topic, class_name, handler, async: true)
            @subscribers[topic] ||= Set.new
            @subscribers[topic] << { class_name: class_name.to_s, handler: handler }
        end
      end
      

      Usage

      Usage is also quite straightforward:

      class Search::Tasks::Indexer
         PubSub.subscribe('task.upsert', self, :on_upsert)  )
      
         def self.on_upsert(message)
           item = message[:item]
      
           MeilisearchClient.reindex(item.to_meilisearch)
         end
      end
      
      class TaskForm < ApplicationForm
        def save
          super
          PubSub.publish('task.upsert', item: self)
        end
      end
      

      Experience

      After 6 months of using this method, I can safely say that it works as intended.

      • It handles unexpected errors like network errors gracefully
      • It was easy to use where we needed it
      • Classes that handle the incoming messages are separated from the rest of the models and are easy to unit test

      📖 Sidestory: Rethinking Pub-Sub
      Recently Buha wrote a blog post how he gave up on the Pub/Sub approach. It is a good read to see the downsides of this approach.

      Conclusion

      We solved the problem of pub/sub messaging in our product with the help of Sidekiq. It proved to be reliable and of high quality, and the implementation itself is not complicated.

      What are your experiences with Sidekiq? Are you using something else?

      The post Publish/Subscribe with Sidekiq appeared first on ProdSens.live.

      ]]>
      https://prodsens.live/2024/02/21/publish-subscribe-with-sidekiq/feed/ 0