TL
Joined
Activity
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 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?
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
Posted in Backing up uploaded files (hundreds of gigs) to S3 using Backup gem without duplication on HD
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?
Posted in Any gem to create a 'What's new' page like the one I see in https://www.hatchbox.io/announcements
Posted in Any gem to create a 'What's new' page like the one I see in https://www.hatchbox.io/announcements
Posted in MessageBus Gem
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 HotelOwner
s and Tourist
s. 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?