TL

Joined

2,390 Experience
15 Lessons Completed
0 Questions Solved

Activity

Posted in Server Administration with Cockpit Discussion

Chris, I get a not secure warning in Chrome when I try to access :9090 in my domain (I deploy with Hatchbox). It's sending the browser a self signed localhost certificate.

Let's say that I want to sanitize user generated HTML written by CKEDITOR or something similar.

CKEDITOR has a really nice hash-like syntax for allowing content:

config.allowedContent = {
    a: {
      attributes: 'href'
    },
    b: true,
    i: true,
    u: true,
    table: true,
    tbody: true,
    tr: true,
    td: true,
    blockquote: true,
    img: {
      attributes: [ '!src', 'alt', 'width', 'height' ],
      classes: [ 'align-left', 'align-center', 'align-right' ],
    },
    h1: true,
    h2: true,
    h3: true,
    ul: true,
    li: true,
    ol: true,
    figure: true,
    figcaption: true,
  };

It works like this:

  • only tags in the list are allowed;
  • if tag value is true, all attributes and classes are allowed for that html tag; otherwise, you can pass an object specifying which classes and attributes you want to allow for that specific tag.

I basically want to replicate that in the backend with Rails HTML Sanitizer.

What I currently have is a simple PermitScrubber:

class BlogPostScrubber < Rails::Html::PermitScrubber

  def initialize

    super

    self.tags = %w(
      p
      a
      b
      i
      u
      table
      tbody
      tr
      td
      blockquote
      img
      h1
      h2
      h3
      ul
      li
      ol
      figure
      figcaption
    )

  end

end

The problem is: if I specify @attributes in the PermitScrubber, it will allow those attributes for any element; also, I have no idea how to permit only specific CSS classes for certain tags.

Can anyone shed some light on a way to achieve this?

Posted in Video request: using message_bus gem

Message_bus is a really nice gem by Sam Saffron that powers Discourse and thousands of other sites; it offers a server-to-server channel based notification system (so, as far as I understand, your app can subscribe and notify itself of events, in an asynchronous way) and, the main feature for me, it provides for a real-time way of sending data to the front-end using polling, long-polling or long-polling + streaming, without having to configure Websockets, working out of the box with normal app servers like Puma and without having a huge performance impact.

It's also smart enough to share updates via multiple tabs so they don't all hit your app if you have multiple of them opened.

The gem is really powerfull and simple, but the documentation is lacking, so that's why I think it's a great candidate for a video by GoRails!

It could be used, for instance, to update comments our notifications in real time in the frontend.

I'm imagining a video where MessageBus is used to get updated JSON to the front-end, automatically feeding it into Vue or React!

Posted in How do I implement nested comments

I'm looking for a tutorial on how to implement nested comments in my project. The requirements are that the comments must be editable and deletable by owner as well. As a plus, we plan on using `act_as_votable` to make them votable, and have [+] and [-] signs to make them collapsible.

I was thinking in using Vue.js for this, since having a lot of comments and nested comments would implicate in having hundreds of unnecessary Rails forms (if we used plain Rails for that). 

Anyone know of a good tutorial on how to achieve this? If not, Chris, can you consider this as an episode request?
I'm using Rails 5.2.0 and Webpacker gem to deploy a Vue application.

The show.html.erb file is very simple:

<div data-behavior="vue-app"><MyComponent></MyComponent></div>

And then in my entry pack, packs/my_vue_app.js:

    import TurbolinksAdapter from 'vue-turbolinks';
    import Vue from 'vue/dist/vue.esm'
    Vue.use(TurbolinksAdapter);
    import MyComponent from '../components/my_app/index.vue'
    
    document.addEventListener('turbolinks:load', () => {
      
      var element = $('[data-behavior="vue-app"]');
    
      if(!element) { return };
    
      console.log('Initializing Vue');
    
      const app = new Vue({
        el: element[0],
        data: {
          
        },
        components: { MyComponent }
      })
    })

In development, everything works absolutely fine. The app is mounted and functional.

But in production, after the page load and JS runs, `<div data-behavior="vue-app">` is removed from the paging, leaving only `<!-- -->` in it's place.

In the console, there are absolutely no errors. I can confirm using DevTools that the pack js file is loaded, and it was parsed, since the console.log is printed in the console.

Heck, the proof that Vue is working is that the entire `<div>` where it was mounted was removed from DOM after JS parsing. 

The weirdest thing of all is that I could get the app to mount ONCE, by attaching a debugger on the console.log line and turning it off while the debugger paused execution. Even tough I saw the app mounting that time, I could not get it to mount later on, even fiddling with the debugger again ... it's really, really weird. 

These are the versions of package.json:

```
"vue": "^2.5.16",
"vue-loader": "14.2.2",
"vue-template-compiler": "^2.5.16",
```

The Rails app is brand new, with no config other than the default.

Webpacker gem is 3.5.3 and Rails is 5.2.0. 

After spending a really long time on this, I only found this github issue: https://github.com/rails/webpacker/issues/1520

I'm providing a link to the real, production app where this bug is happening: https://planilha.tramitacaointeligente.com.br/planilhas/ED2sUXz32-R9CJKdkmtf8Q

You'll see it's not mounting. Here's the same page in development:

[![screenshot of app mounted][1]][1]


  [1]: https://i.stack.imgur.com/ZZbk0.png
Let's say I have 400GB of uploaded images inside a 800GB VPN machine. So we're confortably using 50% of storage.

Even tough we have our hosting's backup enabled, we want to have an extra backup sitting on S3, just because we can't get enough of backups right?

So, using `backup` gem, we are already sucessfully backing up Mysql and storing it on S3, so now we want to create a second model to backup these files.

The question is: if we create a zipped file with all theses images prior to uploading to S3, we would run out of disk space right?

Is there a way to just upload all 400gb of images to S3 and incrementally update it, on a daily basis, without having to duplicate all files (zipped or not) on hard drive?

Is RSync better suited for this, in which case I should opt out of S3 and buy a node (like in Digital Ocean) ?

What are our options?
Thanks a lot Chris, exactly what we needed.
Do you guys know any gems to quickly build a 'Whats new' page like the one in https://www.hatchbox.io/announcements?

Posted in MessageBus Gem

Hi Chris. It seems MessageBus is a nice and much simpler alternative to WebSockets/ActionCable. Could you add one episode on this on the pipeline? 

Got here trough searching on google. devise_group does sound super useful!! Thanks a lot for finding this.

Thanks to Chris nice tutorials, we just rewrote a slow interface in our app to use Vue.js. Now Rails is just generating the JSON to be consumed by Vue.js in the view.

What I'm trying to achieve is to generate a cached JSON response using read_multi or fetch_multi, so all Conversations (there are 20 on each pagination) are loaded on a single call to memcached.

Le'ts say I have a model called Conversation, that belongs to an user, to a tourist, to a property, and it also has many replies.

class Conversation
  belongs_to :user
  belongs_to :tourist
  belongs_to :property
  has_many :replies
    has_many :holidays

    def generate_json
        {

        tourist: {
          name: tourist.name,
          email: tourist.email,
        property: {
          id: property.id,
          image_url: property.pictures.first.try(:image).try(:url,:thumb),
          property_code: property.property_code,
          title: property.title_br,
        },
        recent_replies: replies.last(5).map(&:generate_json) 
      }
    end


end

For the has_many associations, I simply use touch: true in the other side of the association, so model Reply is a belongs_to :conversation, touch: true, which takes care of updating Conversation's updated_at if it changes and the cache is automatically updated.

But every time I try to cache a model like this (wether in a view fragment or, in this case, in a JSON response) I end up with a huge and complex cache key due, mainly, to the belongs_to association on the cached model. Something like this for example:

Rails.cache.fetch(self.cache_key, self.user.cache_key, self.tourist.cache_key, self.property.cache_key) do
   ... generates the JSON for this model
end

Justin Weiss has a nice tutorial (altough a bit complex) of how to cache complicated JSON responses (https://www.justinweiss.com/articles/a-faster-way-to-cache-complicated-data-models/) but as far as I could understand he's using manually expiring cache keys.

I'm a little bit lost at this point. How would you guys implement caching for this type of JSON structure?

One idea is to use just the self.cache_key in the cache key for the Conversation, (1) rely on touch: true for the has_many associations and (2) write manual after_commit :touch_conversations for the belongs_to associations, something like this:

model Tourist

    has_many :conversations
    after_commit :touch_conversations

    def touch_conversations
        self.conversations.update_all(updated_at: Time.now)
    end

end

The problem with this approach is that some users already got + 200k conversations for example. Updating them all takes at least 4 seconds in my tests, and I'm afraid interfaces will start to get sluggish over time.

Posted in Tutorials on recommendation engines

Thanks a lot guys!

Posted in Tutorials on recommendation engines

Hi Chris,

I searched for videos on recommendation itens but couldn't find any.

We run a real estate website and would love to provide similarities recommendations (like "users that viewed this property also liked ...". There seems to be a handful of gems to acomplish this, but it's not trivial and it would be great to have a tutorial with your expertise. Any plans for a video like this?

Imagine a travel website where you have HotelOwners and Tourists. When they start a conversation, the app creates a join model named Conversation using has_many through. It's a classic many to many association:

class HotelOwner
  has_many :tourists, through: :conversations
  has_many :conversations
end

class Tourist
  has_many :hotel_owners, through: :conversations
  has_many :conversations
end

class Conversation
  belongs_to :hotel_owner
  belongs_to :tourist
end

Now we can use hotel_owner.tourists and tourist.hotel_owners. Also, the join model Conversation is also being used to keep some state on that association between them both (like, HotelOwner comments on Tourist and vice-versa).

But now we need a Reservation model. My initial ideia was this:

class Reservation
  belongs_to :hotel_owner
  belongs_to :tourist
end

But we also need to create the Conversation join model, since app logic requires that there cannot be a Reservation without a previous Conversation, even if a blank one. Also, the hotel_owner notes on tourist and vice-versa should be kept there and need to exist if a reservation exists.

After thinking about using manual callbacks to manually create the join model Conversation, I read that it would not be a good idea to add a belongs_to :conversation on Reservation because it could lead to database inconsistencies (like the problem if reservation.conversation.tourist pointed to a different tourist then reservation.tourist .. there should be a single source of truth to this association right?)

I then had the idea of using Conversation as a proxy to Reservations, like this:

class HotelOwner
  has_many :tourists, through: :conversations
  has_many :conversations
  has_many :reservations, through: :conversations
end

class Tourist
  has_many :hotel_owners, through: :conversations
  has_many :conversations
  has_many :reservations, through: :conversations
end

class Conversation
  belongs_to :hotel_owner
  belongs_to :tourist
  has_many   :reservations
end

class Reservation
  has_one :hotel_owner, through: :conversation
  has_one :tourist,     through: :conversation
  belongs_to :conversation
end

Since there is no belongs_to through in Rails to use in Reservation, other posts in SO suggest using has_one trough instead, just like I did above.

The problem is that conversation has_many reservations, and does not belong_to a reservation (like it does belong to a Tourist and HotelOwner).

It's not only semantics that bother me. If I do hotel_owner.reservations.create(tourist: Tourist.last), it does create the Reservation, but the join model Conversation is not created, leaving reservation.conversation nil.

After a simple hotel_owner.reload, hotel_owner.reservations return nil.

What is the correct database design and Rails association model for something like this?

Congrats for #200 Chris, you are putting out quality stuff here. Cheers!

Chris I'm a new subscriber and I'm loving Go Rails. The content is really usefull for real-world applications.

After implementing the "Like" feature in my website, I came across a doubt that keeps popping up in my apps.

Let's say I have a Favorites Controller that responds with create.js.erb and destroy.js.erb.

It all works great, but then let's say I have a different view called "Favorites" like the one you have here (https://gorails.com/users/:user_id/favorites) and I want to add a link to remove from favorites there.

The difference is that, in this view, when I remove something from favorites, I want the video to disappear from the list, and not only the heart to become gray, so the jQuery in the js.erb response is different.

It sounds silly to repeat the controller code and views, so I assume we should keep things DRY and use the same controller and actions.

But the question is: how to deal with this scenario when the HTML for the js.erb response is different, since we're dealing with 2 different views?

Right now I'm solvin this by passing data: {params: {source: 'name_of_the_view} } in the link_to, and using that params[:source] in the js.erb to render the correct jQuery accordingly using if/else statements. Do you agree with this solution? Is there another best practice?

Posted in Liking Posts Discussion

Hi Chris, just subscribed, really nice stuff here. Regarding this tutorial, I have a question about caching: how would you implement this like feature without having to use `current_user` as the cache key (which obviously would defeat the purpose of the cache in the first place) ? Would you recommend using a gem like `render_async` to do this?