Dan LeGrand

Joined

3,190 Experience
21 Lessons Completed
1 Question Solved

Activity

Posted in Introduction to Stimulus Reflex Discussion

@Chris, since stimulus reflex is using action_cable behind the scenes which relies on a redis DB, do you have any idea if there are scale limitations for using stimulus reflex? ie, 1,000 concurrent users probably fine, but 100,000+ it starts crawling because redis is backed up, etc...

My understanding is that the default action_cable implementation had some scale concerns, and that's why any_cable (https://github.com/anycable/anycable) re-wrote some of the API in a faster language (I think they're using Go-lang?). I haven't encountered these scale issues in my own apps, so my knowledge is only "what the experts say", not based on real numbers.

I'm curious about the "production" viability of stimulus reflex before considering implementing in some of my systems.

Awesome video as always, and I'm looking forward to seeing more videos on stimulus reflex, keep them coming!

Posted in Migrating From jQuery to Vanilla Javascript Discussion

@Chris, I know this episode is a little old, but this is still something I'm dealing with today.

I understand the rationale behind the move to get rid of jQuery in web apps: jQuery was from a time when we needed cross-browser compatability, when vanilla JS didn't provide functionality, that vanilla JS is now good and fast, etc...

And I agree with many of the points.

However, in almost evey project I've written in the last 2 years that doesn't use jQuery, one of the first things I find myself doing is writing a JS class that has a lot of helper functions that look very similar to jQuery.

This is easiest to see with dynamically appending elements to the document using something like Rails' <action>.js.erb format.

Writing this:

const fragment = document.createRange().createContextualFragment("<%= j render(partial: '...') %>")

document.querySelector("#some-selector").appendChild(fragment)

Seems a lot more painful than this:

$("#some-selector").append("<%= j render(partial: '...') %>")

I end up writing a class like the below where I throw in all my helper functions, essentially mimicking jQuery:

# JS helper class attached to window.Global (using webpack)
export default class Global {
  static append(selector, html) {
    const fragment = document.createRange().createContextualFragment(html)
    document.querySelector(selector).appendChild(fragment)
  }
}

I understand if someone is using a framework like React/Angular/VueJS then they wouldn'd use jQuery, but that's because they're using another framework which abstracts complexity.

Do you find that you and other developers are writing your own helper functions to abstract some of the complexity of vanilla JS away? How do you handle trying to write less code that is easier to maintain with the move away from jQuery?

After some more digging and some answers on SO, I realized I've been thinking that the way I did things in sprockets can transfer to webpacker, which is not true.

Sprockets essentially combines all my required JS files into a single file with a global namespace, which is why I could reference classes/functions right after they were required (thus the order of files mattered).

Webpacker also creates a single pack file of JS code (or as many packs as you have), but the pack is not a combined file of JS, but rather a combination of es6 modules, such that each module is completely namespaced and separate from others. Thus, to reference my Datepicker class in another es6 module, I have to manually import Datepicker from "datepicker_file" in each separate class (noting that webpacker will ensure only 1 copy of the JS code is actually included).

That means only the bare minimum of entry-level JS code needs to go in the webpacker pack files. For instance, if I use the environment.js ProvidePlugin to make jquery global, you don't actually need to require("jquery") in the pack file.

And since my Datepicker class includes the pikaday library, I don't need to add pikaday at all to my pack file because webpacker will eventually include pikaday because it's a dependency.

I think webpacker will be better overall when I finally understand it, but it is going to require a LOT of refactoring for several of my applications, because they were written on the assumption that if a file was required it was accessible in the global namespace.

Posted in How to use Javascript via Webpacker in Rails 6 Discussion

@Chris,

Can you shed any light on how the require or import in the pack manifest file works?

Here's a real-life example from a project I'm upgrading to Rails 6 with webpacker to manage the assets.

I'm using the pikaday JS library for a calendar, wrapped with a Datepicker JS class (to make refactoring easier if I someday change the calendar library), and this class is used in a StimulusJS controller.

Because the controller file imports the Datepicker class, and the Datepicker class imports the Pikaday library, I don't need to import/require pikaday or my Datepicker in the application.js pack file at all, and I'm curious why that is?

Also, if I import Datepicker from "custom/datepicker" in the application.js pack file, why can't I reference Datepicker in other classes I import in the manifest, such as the controllers? Why do the controllers have to manually import Datepicker in order to reference it?

It seems like theres a lot of manually importing classes when I switched to webpacker...in the asset pipeline, I could reference these classes anywhere without having to manually import them all the time. Was it because they were somehow automatically being added to a global namespace?

I understand the principles behind avoiding over-populating the global namespace, but having to manually import every single class I want to use in every single file seems a little overkill. Have you happened upon anything that can help with this? (I've looked at webpacker's ProvidePlugin, and it doesn't seem to help with instantiating new classes, only if you're referencing the global itself, such as $ or jQuery).

app/javascript/packs/application.js

require("controllers") // loads the default index.js which loads all JS files in directory

app/javascript/controllers/assignment_form_controller.js

import { Controller } from "stimulus"
import Datepicker from "custom/datepicker"

export default class extends Controller {
  initialize() {
      new Datepicker(document.getElementById("some_id"))
    }
}

app/javascript/custom/datepicker.js

import Pikaday from "pikaday"

export default class Datepicker {
  constructor(element, options) {
    if (options == null) { options = {} }

    // Store DOM element for reference
    this.element = element

    // Do not re-run on elements that already have datepickers
    if (this.element.datepicker === undefined) {
      options = Object.assign({},
        this.defaultOptions(),
        options
      )

      const picker = new Pikaday(options)

      // Store picker on element for reference
      this.element.datepicker = picker

      return picker
    } else {
      console.log("Datepicker already attached")
      return
    }
  }

  // Overridden by `options` in constructor
  defaultOptions() {
    return {
      field: this.element,
      format: "M/D/YYYY",
      bound: true,
      keyboardInput: false,
      showDaysInNextAndPreviousMonths: true
    }
  }
}

I'm currently upgrading from Rails 5.2 to 6.0.1 and this gem was one of the blockers for me since it has a dependency on Rails 5.2. Really the only problem is that Rail's built-in store persists these values as strings and this gem typecasts the values for you. Once you handle that, you don't need this gem anymore and can write your own module or small gem.

See below for working version that doesn't use the gem. Obviously I haven't addressed null values and defaults, but those are fairly simple. Once I have a working module I may update this comment with the code so others can use it.

I don't think it's worth making a gem to add a few lines of code, so I tend to store something like this in a lib/modules/typed_store.rb file and then include TypedStore in my model file to use it.

Code from the typed_store gem (from my live project)

typed_store :recurring_rules, coder: DumbCoder do |s|
    s.integer :recurring_interval, default: 1, null: true
    s.string :recurring_frequency, default: "day", null: true
    s.integer :recurring_days, array: true, default: nil, null: true
    s.integer :recurring_day_of_month, default: nil, null: true
  end

Using built-in Rails store

If you're using PG with jsonb column types, you can use store_accessor directly and don't have to use the store method.

Just override the accessor methods to handle typecasting; that's also where you would handle defaults, nulls, etc...

store_accessor :recurring_rules,
    :recurring_interval,
    :recurring_frequency,
    :recurring_days,
    :recurring_day_of_month

  def recurring_interval
    super.to_i
  end

  def recurring_frequency
    super.to_s
  end

  def recurring_days
    super.to_a.map(&:to_i)
  end

  def recurring_day_of_month
    super.to_i
  end

I didn't even though about using the custom route names but then pointing them to separate controllers to keep the code clean. I got too stuck in the "all or none" mindset of only doing it one way.

Thanks for the input!

Chris,

These actions mostly touch the milestone model, but they update children associations (ie, when a milestone is activated, it's children task records are activated as well).

There is no separate Activation or Completion record, those were just names I gave to the actions I was taking on a milestone.

I also found that Github and Stripe both seem to use custom actions, so it made me feel a little better about moving to that approach.

Specifically with activate/deactivate, I have 10+ separate resources it can apply to, and it was much easier for me to remove those 10 separate controller files and just put the methods on the already-existing controllers.

It's always good to get input from folks in the community I look to for helping establish "best practices"!

I have a milestone resouce that can be activated/deactivated as well as completed/reopened.

Rails pushes the standard 7 actions in your controllers: index, new, create, show, edit, update, destroy.

These 7 actions work great for most things, but my use case didn't strictly fit into those 7 REST actions. I read an article a while back that some respected Rails developers follow REST conventions by creating resources such as:

  • activate => POST /milestones/:id/activations
  • deactivate => DELETE /milestones/:id/activations
  • complete => POST /milestones/:id/completions
  • reopen => DELETE /milestones/:id/completions

I used this approach for a while but I've found it to be difficult to work with.

It adds additional files, which sometimes leads to more complexity since there is now more to manage.

The biggest problem I encountered was it didn't make logical sense that the reopening of a milestone record was at the endpoint DELETE /milestones/:id/activations. It made more logical sense to me that it would be PUT /milestones/:id/reopen, since it is something we are doing to the milestone record.

I've been contemplating moving these non-standard actions to the milestones_controller.rb file itself and updating my routes accordingly.

I wanted to get some thoughts on these 2 different approaches and see how others had solved this problem of custom actions on resources?

Posted in How to use Devise Test Helpers Discussion

Hey Chris,

I've noticed that some of your recent videos about testing are using Minitest.

What are your current thoughts on Minitest vs rspec? I've been a fan of rspec for many years, but I'm considering using Minitest instead, since it's built-in and is thus the default.

One of the big considerations for me to move to Minitest is the addition of integration tests (a while back), but since I haven't tried it out yet I'm not aware of any "gotchas" with Minitest.

I always look forward to your videos!

Hey Chris,

Awesome episode as usual! Few quick questions:

  1. Since ActionText is storing a global_id reference to records in order to display the updated partial at render time, does that mean it's making a separate DB query to retrieve each of those records? For instance, if I @mention 10 separate users, will it make 10 separate calls? Especially if I'm creating a commenting system that allows @mentions, and there could be 10-20 comments, each with several @mentions, etc...
  2. I notice you used Zurb Tribute for the @mention library; you've done an episode before using AtJs, any benefits to Tribute over AtJs, or just preference?

I really do like the concept of storing a reference to the partial instead of the hard-coded HTML. I'm actually in a situation where I stored the HTML snippets in the text itself and now want to change it, but am struggling with how to do that using the Froala editor. I'll eventually migrate to ActionText in a few months after Rails 6 has been vetted in the wild.

Always appreciate your timely and very applicable videos!

Posted in Handling Inbound Email Parsing with Rails Discussion

Chris, at the 3min mark you talk about using subdomains such as reply.gorails.com instead of the root gorails.com to process incoming emails.

What's the concern or downside of not using a subdomain? Is it to avoid situations where you have an email like [email protected] that you do not want processed by the application?

Hey Chris, is there a way to use the Rails 5+ attributes API to store settings in a jsonb column instead of using the activerecord-typedstore gem?  Trying to see if I can remove external dependencies and use built-in Rails features.

Posted in Activity Feed From Scratch Discussion

It looks like the `lookup_context.template_exists?()` method now requires the prefixes to be an array, or an enumerable that responds to `.map()`, instead of just a string. This was changed as previous versions of Rails (< 3.1) accepted a string prefix. I'm not sure why it works in the episode, since you're using Rails 4; maybe it was a soft deprecation?

https://apidock.com/rails/ActionView/LookupContext/ViewPaths/template_exists%3f

Posted in Stimulus JS Framework Introduction Discussion

Chris, one question that would be awesome for you to cover is handling lists of elements.

For instance, I'm working on a notifications system right now (based off of some of your episodes!), and I want to have a data-controller to maintain list-level actions (like mark all as read) but also individual elements (such as mark an individual notification as read/unread, click the notification to view associated record, etc).

Stimulus has been updating their docs and they now have a section that talks about multiple data-controllers for lists, but I'm not sure what the "standard" is for high-level list actions vs individual item actions (if that makes sense).

Posted in Stimulus JS Framework Introduction Discussion

I'm compiling a list of problems I'm running into as I stumble my way through it! I use a lot of CoffeeScript classes to interact with my app and avoid spaghetti code, like you show in https://gorails.com/episode....

One issue I ran into already is that it seems the `data-controller="..."` attribute cannot have underscores (_) in the controller name. For example, `app/javascript/packs/transaction_record_controller.js` with `data-controller="transaction_record"` will not work, but once you remove the underscore it works fine. I couldn't find any documentation on this, and I can't think of anything my app is doing that would cause a conflict, so I'm assuming it's the way Stimulus works.

I've been slowly converting several of my CoffeeScript classes into Stimulus controllers, and so far I've found it to be a great way to take care of stuff like adding a datepicker to an AJAX form, etc...

Posted in Stimulus JS Framework Introduction Discussion

I'm literally figuring out StimulusJS right now on an app, and I was hoping you would do a screencast on it soon and save me some time! Awesome timing :)

I did find one "gotcha" with using a preferences hash like this. In my particular situation, I wanted a settings hash that stored a nested filters hash.

My use case is I have a page where I want to use URL params for filtering:

/contacts?first_name=John&last_name=Smith

I wanted to store the filters hash via:

# in controller
user.update_attribute(settings[:contact_filters], params.slice(:first_name, :last_name))

# What I want it to do...
settings: {
contact_filters: {
first_name: "John",
last_name: "Smith"
}
}

The problem is, that the nested hashes are stored as strings and it is nigh impossible to cast them cleanly to a normal Ruby hash (at least after 15 min of StackOverflow searching).

I was thinking that I could get it to somehow work with a combination of:

JSON.parse(user.settings[:contact_filters])

but no such luck. I do love the simplicity of the nested hashes, but it does seem to have some difficulties at least with nested hashes.

Posted in Workflow for TDD/BDD on Rails ?

I just want to throw in my 2 cents with regards to TDD and the workflow/practices of a development team. You're correct that "textbook TDD" states you first write specs that don't pass, then you implement them feature-by-feature following a red-green refactoring process. That sounds great, and it's a good tool to teach others.

However, the real world is hardly that simple. For example, out of the last 3 Rails projects I've built in the last 2 years, all of them had features that we didn't know the final specification until we first built versions that didn't work and our clients had a chance to review it and give us further guidance on what they really wanted.

Here's a real-world example from my current project. I have a task system that assigns tasks to users, and they can complete a task or reopen it. My first pass was to have completed_at and reopened_at columns that tracked when these 2 states changed and to base my logic off of them.

But then I talked to my client, and they also told me they need an inbetween state when a task was "submitted" and needed to be reviewed, but wasn't technically complete.

If I followed TDD, I would have written a bunch of specs that would have been fairly useless and have to be refactored constantly. And the reason they would have to be changed constantly is because I didn't know what the final core architecture or features of the project was going to be.

Thus, in my opinion, pure TDD only makes good sense if you have a clear understanding of what the final product needs to do. In cases where you have a general idea but are refining it constantly, I recommend holding off on the specs until you've solidified on key design. Some projects gather all the product specifications ahead of time and TDD works out great; other projects have general ideas and but the key architecture is figured out at "run-time" so to speak, when your development team actually implements the features and finds all the "gotchas" (most projects are like this for me).

I say this because specs are nothing more than code that also has to be maintained and refactored as time goes on. When you view your specs this way, you realize there is a cost involved with writing and maintaining good specs.

View specs as a guarantee that future code doesn't break existing features. So don't worry as much about having specs while you're building out features, but when you ship that feature to your production system, at that time I strongly recommend you have specs that ensure it all works.

As a final word, don't go crazy testing everything. Much of the core Rails code is already tested, you don't need to repeat what they're already doing. Test your logic and your projects unique features, don't worry as much about "does this core Ruby/Rails method actually do what it says it does?" Chances are it probably does.

This actually makes a lot of sense. I've had frustrations with putting all my preferences in a single column. It bothers me to have a bunch of columns for preference data, but as I think about it, there's no "real" reason it should bother me; it's my own "preference" (no pun intended). I would have access to all of ActiveRecord's power, and the data would be treated like a first-class citizen instead of delegated to the sidelines. You're right that migrations aren't something to be scared about, and if you use a single column hash you have to re-deploy changes anyway, which should run migrations by default.

Adding 10-15 DB columns for preferences may be a better solution, because then you have full index/query power, even though JSONB can be indexed (I think). No reason to complicate things though, IMO.

One use case I've had for using single-column hashes is for unstructured data that you don't know ahead of time, such as someone uploads a JSON file with unknown data and you have to parse it out. But preferences are 99% of the time structured data that you know ahead of time and are building core logic around (ie, "send emails if this setting is true").

Posted in Building out a mini-framework with CoffeeScript

A portion of the application I'm working on has a comments page with a form at top to add new comments and a list of comments beneath it. I've been working on using CoffeeScript classes to represent my DB models, as per this episode on using data-behaviors

I started breaking my CoffeeScript classes up into separate classes to mirror my Rails controller actions:

class Comments.New...
class Comments.Create...
class Comments.Update...

Since I'm using server-generated JS, my Rails actions correspond to the class:

# app/views/comments/create.js.erb
new Comments.Create("<%=j render(partial: 'comment', locals: { comment: @comment }) %>");
# comments/create.coffee
class Comments.Create
  constructor: (comment) ->
    @comment = $(comment)
    @newForm = $("[data-behavior='new-comment-form']")
    @comments = $("[data-behavior='comments']")

    @addComment()
    @resetForm()

  addComment: () ->
    @comments.prepend(@comment)

  resetForm: () ->
    @newForm[0].reset()

The comment partial has enough data attributes to allow my CoffeeScript code to know what to do with it.

To initialize on page load, I have a global.coffee that starts all my major events:

# global.coffee
$(document).on "turbolinks:load", ->
  $.map $("[data-behavior='new-comment-form']"), (element, index) ->
    new Comments.New(element)

I probably spend 75% of my time building out front-end interfaces, and technically I'm a "back-end" guy :)p I just hate a lot of the bloat/unused portions of some of the really large frameworks out there.

I'm puzzling over if this is a good approach, or how others solve similar problems? I'm finding that I want to run a decent amount of JS/CoffeeScript code for each of my corresponding Rails actions.

logo Created with Sketch.

Ruby on Rails tutorials, guides, and screencasts for web developers learning Ruby, Rails, Javascript, Turbolinks, Stimulus.js, Vue.js, and more. Icons by Icons8

© 2020 GoRails, LLC. All rights reserved.