# Gem Hotwire Native Rails - convert your Rails app into Native VERY FAST | SupeRails #219 - URL: https://superails.com/posts/gem-hotwire-native-rails-convert-your-rails-app-into-native-very-fast-superails-219 - Published: 2025-08-29 - Duration: 55:43 - Access: free - Topics: hotwire native, bridge (Strada) - Playlists: Hotwire Native # Quick recap Yaro discussed the use of Hotwire Native, a tool for wrapping web applications into mobile applications, and demonstrated how to use the Hotwire Native Rails gem to add helpers, routes, and controllers to a Rails application. He also explored the implementation of native tabs, user registration, path configuration, and the functionality of a mobile app. Lastly, Yaro showcased various features of a Rails application enhanced with the Turbo Native iOS gem, which enables creating mobile apps from web apps. # Next steps Meeting attendees to try using Hotwire Native for converting their Rails applications into mobile apps. Developers to explore the Hotwire Native Rails gem for additional helpers and components when building mobile-friendly Rails apps. Developers to consider using Yaro's fork of the Hotwire Native iOS demo application for additional out-of-the-box behaviors. Rails developers to implement proper navigation patterns, such as using turbo-action replace, when converting web apps to mobile. Developers to utilize bridge components like Button, Form, and Overflow Menu for native-like interactions in mobile apps. Rails developers to use the device format concern for rendering different templates for mobile and web versions of their apps. Developers to explore and implement the Review Prompt controller for requesting app store reviews from users. # Summary ## Exploring Hotwire Native for Rails Yaro discussed the use of Hotwire Native, a tool for wrapping web applications into mobile applications. He shared his experience with the tool, including tips from various talks and articles. Yaro also introduced the Hotwire Native Rails gem, which adds helpers, routes, and controllers to a Rails application for easier conversion into a native application. He demonstrated how to use the gem by running a Rails application locally and rendering it in Hotwire Native. Yaro also mentioned the need for additional behaviors and improvements, such as tab navigation and handling links within the app. He ended the conversation by running the application on his branch, which included additional bridge components and tab navigation. ## Implementing Native Tabs in iOS App Yaro discussed the implementation of native tabs in an application. He explained that the tabs are defined in the iOS application's scene controller and can lead to different paths. He demonstrated how to update the paths of the tabs without repackaging the iOS application, allowing for changes to be made without going through review rules. Yaro also mentioned the ability to change the system name for icons using SF Symbols and the potential to open the app to find icons. He concluded by highlighting the advantage of having tabs with the context of the current user. ## Mobile App User Registration and Path Yaro discussed the functionality of a mobile app, focusing on user registration and path configuration. He demonstrated how to redirect users to edit user registration paths and how to handle confirmation models. Yaro also explained how to configure the app's path, including which behaviors should open as a model and which pages can be refreshed. He highlighted the importance of not stacking navigation from a signed-in page to another signed-in page, and demonstrated how to replace the default navigation with a custom one. Yaro also mentioned the need to generate device views for using device in Rails. ## Hotwire Native Rails Implementation Yaro discussed the implementation of hotwire native rails in their application. He explained how to replace turbo actions only on mobile and not on the web, using the'replace_if_native' helper provided by the hotwire native rails gem. Yaro also demonstrated how to hide certain views in mobile and have them visible in the web, using the 'turbo_native_app' helper. He emphasized the need for different UI elements on mobile compared to the web, such as the native tab bar and header. Yaro concluded by stating that the 'turbo_native_*' helpers would not be deprecated. ## Setting Page Titles for Application Yaro discussed the importance of having different page titles for various pages in their application. He explained that the page title should reflect the organization name, not the application name. Yaro demonstrated how to set the page title in the controller and in the view using content for hotwire native title. He also noted that the page title currently only works with the native app and suggested adding it to the web app as well. Yaro concluded by emphasizing the need to set titles for each view of the application. ## Implementing New Feature and Components Yaro discussed the implementation of a new feature in their system, focusing on the use of native components and the integration of Hotwire Native Rails. He explained how to add a new organization button to the native navigation bar, and how to hide certain elements on the web version but not on the mobile version. Yaro also demonstrated how to use the Bridge Form com ## Transcript So, uh, as I mentioned, native is, uh, a way to easily wrap your, uh, web application into a mobile rep. And, uh, you can kind of launch this, uh, uh, app on the app store. And, uh, there have been quite a lot of, uh, talks about Native. So, uh, notably that was like a Talk on Rails World by Joe Otti, where he also gave a lot of, uh, tips that the rail specific on how to, uh, make, uh, your Rails app work well with the native. So different like rules that you would need to add or helpers that you would need to add. Also, there was nice talk by John Pollard on where he shared lots of tips about how they turned the Buzz Sprout, uh, this application, uh, from just a web app into a mobile app and the launch on the app store. Uh, so there were lots of tips there. I myself gave a couple of talks, but I also shared different, uh, other tips on, uh, uh, different helpers, different routes, uh, different, uh, navigation patterns that you would want to have in your Rails app, uh, so that folks smoothly as a native app. And, uh, there's like a lot of information online, uh, except of, uh, what has been talked about in the written form. Like, for example, a recent article by Joe 10 Tips from 10 years on Hot Via Native, uh, va. He has lots of like, uh, uh, things you'd want to add into your Rails app to make it work smoothly with Hot Hai native. And I myself wrote a bunch of articles about, uh, uh, hot v native and, uh, there's like so much knowledge all around and, um, I, uh, tried to turn it all all into the Gem Hot Hai Native Rails. It's a gem adds a bunch of helpers, a bunch of route and controllers into your Rails application so that you, you can more easily turn it into a native application. Um, let's start with having a quick look at Hot Native, uh, itself. So, uh, hot Native, there's like the website and, uh, uh, it is actually two repositories. It's, uh, the repository for Hot five native iOS, and then there's a separate repository for Hot five native Android. Now we are going to focus on Hot five native iOS. So, uh, it's, uh, like a library that you install into a new Swift application. So, uh, to work for that, you will need to have Xcode installed in Xcode. You can like create a new app and, uh, you would want to add Hot Native as a package, and, uh, you would be able to start creating, um, uh, hot Native App from Zero, but you don't want to go from zero. You want to have some kind of fundamentals like, uh, when you create a new Rails app, you can run Rails, generate new app, and, uh, as an example, they have, uh, this demo application. So, uh, here I ran, uh, I cloned hot fire native iOS on my local machine. I opened, uh, that demo application, uh, uh, in Xcode. Uh, I did it by navigating to, uh, hot Fire Native iOS. Uh, I went to demo, then I typed open and open Demo Xcode project. So it opens the demo app in Xcode, and then I click on launch button and it, uh, uh, launches the application in the virtual iPhone. Now here, as you see, it's launching just and run the web app. Uh, if we go into this demo file, we see that it is, uh, rendering the, uh, content from this URL. So, uh, here's this, uh, app, the way it looks on the web and here is the same app wrapped in a mobile app. So you see this like header is actually mobile, and in this example app there are a lot of different interactions you can play around with, like load. Uh, page model is, so click on it and you see it opens a native iOS model. It has also a native, like done button that would, uh, dismiss the model. And there are lots of other behaviors like, uh, a form, uh, component that, uh, hides the, uh, H TM L submit button that you would have. Uh, here. So here we have the H TM L submit button, and here we don't have the H TM L submit button, but instead we have the native submit button that, uh, submits, uh, the hidden, uh, H TM L submit button. So, uh, bridge components or previously started components is a way to add additional native behaviors to your web app, uh, that is rendered in a hot wire native wrapper. There's another example of a menu. I click to open the menu here, and it opens an action sheet. Uh, another example of, uh, overflow plus menu here. So you see the high end HTML menu. I click on the button in the top right, and it opens the action sheet for different actions. So, uh, here is the example website for native, and, uh, we don't want to run it just on the example website. We want to run it on our Rails application. So what I would do, I would, uh, uh, use the local URL, so it would be a local host, uh, three a thousand. And I would, uh, said, you're out to be not remote, but local. And now I would rebuild the application, but, uh, before doing this, I need to run some kind of rails application locally. Now, um, I'm going to do it as an example on, uh, uh, boil plate application that I recently created. It is mangon and multitenancy boilerplate app. So I cloned the, the Mangon application. I opened it, uh, locally. I ran bundle, created the database run bin Dev to run the Rails app. And here I have the rails, uh, app running and, uh, it's like just a basic Rails application where you can, uh, register as a user. You can create, uh, multiple organizations. Uh, you can, uh, invite other users to be a member in the one of your organizations. Users can be invited as admins or as members. And, uh, everything is scoped to an organization. So an organization can have projects, can have tasks within it. So here has a basic application. It kind of looks okay-ish, uh, on a small screen. So let's try rendering this app. Uh, in Hot Via native, I, uh, again, set the local URL. So we're going to try to render local host in this, uh, hot Via native wrapper. Uh, clicking on run, Let's, uh, see. So the app is launching and it should be making request to our, uh, server. So here I have the app, uh, running. Let's just see our server. Here's the app. So I navigate a different pages, I refresh, and you still have, uh, uh, stuff happening in our Rails console. So our, uh, rails app is rendering as a hot fire, uh, native application. So it already kind of works outta the box, but there is a lot of, uh, stuff that we can do to make it, uh, uh, better. So, uh, first of all, I'm going to install the Gem Hot Ative rails. Uh, where is it here? So Hot Ated Rails. In my Rails application, I will run bundle at Hawaiian native rails, And I will run Rails generate AYA native, and it'll generate a bunch of different files. So let's have a quick look at what it has, uh, generated going to the mannequin group repository. So we've added the G to the GEM file. It, uh, added the hot wire bridge to our, uh, JavaScript. So if you're using, uh, uh, yes build, it'll work if you're working with the input maps, uh, it is going to pin the hot wire native bridge to input maps. Then we added a bunch of tests to test, uh, native interactions. And, uh, let's have a look. So in the GEM file, it also injected the gem browser. The gem browser let's us have, uh, a variant for H TM L templates that will work only on mobile. We have also, uh, controller for native tabs, uh, controller to manage different other interactions, uh, that will be available on the Onfi and Native, a bunch of bridge components, um, updating some things in our application layout, uh, and so on. But we're going to go through all of these, uh, step by step. So we've added all these files. Let's just try running the tests in our, uh, repository. Is adding this, uh, code break any tests? Uh, yes, it is. So, uh, actually know what it is. Basically in the application, H tml, ERB, uh, we have, uh, the head outsourced to another file. And, uh, it was actually trying to change the title and view port. So I'm going to cope with this. Uh, here. I'm just going to have like the regular, uh, title, viewpoint and so on, that you would have in any rails up. I will run the generator once again. And, uh, you see it changed the title. So title is dynamic. You have this page title that is defined in, uh, uh, our native helper, and we have this view put meta tag also defined in native helper. So now I can, uh, I deliver it here or move it, uh, back to our, uh, abstracted head partial. So, uh, uh, now I've kind of run the generator and we have all the files, uh, added to our, uh, rails app. Let's, uh, save our changes. So at Jam put via native rails, I'll just save all these changes. Let's just double check that. The test app passing this time. Okay, yeah, that person. So let's, uh, add the files and, uh, let's have a look. So going in the read, there is, uh, also a recommendation to use my folk of, uh, this, uh, native iOS demo application. So the demo application like gives you a bunch of default behaviors, but, uh, there are some things that don't work, uh, so well. For example, you would most likely want to have a tab navigation on the bottom. And, uh, the default application doesn't provide it. Then, uh, in the default application, if you, uh, click, if you press on a link for the loan, it tries to open it, uh, uh, not inside your app, but uh, uh, in a popup browser as a preview. And, uh, this kind of breaks the experience of using a native application if you can open a link, uh, uh, inside a native application in the browser. So, uh, uh, if, uh, you kind of launched demo application based on my fork, uh, it is, uh, the visit. So I will click here based on my fork. Then, uh, you will have a lot of additional behaviors outta the box. So I'm just going to do it. I have hot five native, IS my fork, and I'm going to, uh, open the branch Super rails extensions. So, uh, again, ov native iOS, uh, and here I have this, uh, uh, branch. I'm not planning to merge this branch. The code is just going to leave for now as a branch so that we can, uh, easily track changes versus the main hot fire native demo application. And, uh, let me run the same app, but based on my branch. So here we have a bunch of additional bridge components here. If we go to the scene controller, we have tab navigation. Uh, let's try running the app. Okay, it says, uh, build failed, missing package by native. Let me try, uh, restarting. Um, I will open demo export project, and I will try running it once again. Now call, start in, uh, uh, what project can take, uh, some time. It can be like one or two minutes, so we'll have to wait. No, like him, it looks like it might be a bit faster. And so it's going to try to launch, uh, our local host, URL. Let's see if it is making any request to our, to our app yet. Uh, actually our app was not running, so it might, uh, not work. I will, uh, restart once again because the web app wasn't running. So the mobile app actually doesn't have anything to render. Okay, I see Jose and, and, uh, Jose joined. Hey, guys, nice to have you here. Okay, so, uh, I run the application, uh, on my branch, and you see out of the box I have this, uh, uh, tabs on the folder of the application. And, uh, we also have like a, a dark color of, uh, the nav bar and the footer. So, uh, everything else looks more as before, but we have our native tabs. Let's try clicking a few tabs. And, uh, at the moment, they're all leading to our root path. Now let's have a look at how the tabs work. So in the iOS application, I will go to C controller and here have the tabs defined. So here are the URLs of the three tabs. We have three tabs, and you can have maximum five tabs. Uh, the first is linked to root url. The second is linked to the web apps slash hot native slash tab one slash native slash tab two. And, uh, that tab titles are defined here. So when we just cold booted the app, uh, it was set both here and playlist, but then we actually visited them. The titles have been updated to, uh, Mon Gun Mongan because the H TM L title of the application is mangan. But we're going to change it for each of the tabs. And we also have the system name for the icons. You can change the system name for the icons using sf uh, symbols, uh, uh, app to find the icons that you want. So, um, you'll be able to open this app and the copy name of an icon that you like and, uh, paste it here, uh, then restart the application and the icon would be updated. But anyway, so, uh, let's get back. We have three tabs and we want, uh, three tab to delete different pages. Let's, uh, keep the first, uh, page leading to the root TRL and the second will lead to organizations path. And the third will lead to, uh, edit my profile. So, uh, root path, uh, list of organizations, and edit my profile. Let's, uh, go to our web app. And, uh, yeah, just going back here, we have, so this path slash by native slash tab one by native slash tab two. So these path are defined in our roots. If you go to our roots have, have this new, uh, file for roots hot via native. So in our roots we have draw hot via native. And, uh, inside our, um, config, we have a new folder roots, and we have all the roots for hot via native in a separate file, uh, hot via native roots. So here we define the rules for the tabs, tab one and tab two. And, uh, we can open the, uh, tabs controller. Now that was also generated by the hot native rails jam. So tabs controller, you have tab zero that is, uh, route path by default, and we're not going to override it. And we're going to update tab one and tab two. So we're going to update the path of this two, uh, tabs. This will lead to organization spot. And this one will lead to edit user registration path. So, uh, if I refresh, or actually maybe I don't even need to refresh, uh, I go, here is the root path here. It's organizations. Here it is, uh, it should be, uh, the path to edit my page. Yeah, it is. I just had to refresh 'cause maybe that was ced. So you see, we updated the native, uh, uh, tabs without actually having to, uh, repackage our iOS application. So I can make th the third tab work, uh, by going to organization spot, I will, uh, try to refresh. So see, uh, I don't have to like resubmit the tab to review, uh, the rules, the new version of the iOS app. And I, I change the location of the tabs. And another good thing is that, um, if you just hot coded the tabs, uh, in the iOS, you would not have the context of like current user or current organization. But here, you can, uh, uh, have tabs with the context of current user. If, uh, there is a current user, for example, uh, uh, if, uh, a user signed in redirect to added user registration, path else, redirect to root path or to new user registration path, let's, uh, try signing out and see if, uh, this works. So I will log out and you see, uh, the confirmation model, the kind of confirmation model you would have on the web works out of the box, uh, in the mobile, you just need to have, uh, uh, data tobo confirm. So data tobo confirm. And you see, I click on logout, it works. So now I will click on this, uh, link. Okay, maybe I need to refresh. Hmm. Now I wonder why this doesn't work. Uh, let's try another pot, the pricing. Maybe this doesn't work. Maybe like the path needs to be, uh, maybe the path was cashed or something. Okay. Anyway, let's leave just, uh, one path now. So edit user registration path. Let's, uh, sign back in. So now let's, uh, see all the other helpers, uh, provided by the gem. So we have the tabs, and let's have a look at the, the heart of the, uh, configuration of, uh, mobile apps, the path configuration. So here we have the path configuration file. And, uh, here we can, for example, uh, define which, uh, behaviors should open as a model, which pages can be refreshed or not. So here, for example, I can refresh the organization's page. If I click the organization, you see outta the box, it opens as a model. Uh, why it, so, because in the rules we say that all the paths that are slash new slash edit by default will be opened as a model. Uh, and here separately, I explicitly said that the, uh, edit user registration path. So this path shouldn't be opened as a model. And, uh, you can also like set whether page can be, uh, refreshed by pulling the, the screen up. So maybe if, uh, there's a page where user can manually draw something, you wouldn't want him to be able to pull down while join and refresh the page and have his progress gone. So let's say I want to, uh, open this added user, uh, page in a model, I would, uh, just command this, uh, out. Then I would, uh, uh, just make sure that the changes are reflected, the straight, uh, away I will restart the, uh, application. And now I expect when I go here that the app should be open, the, oh, yeah, I need to send you in the again, And used all the set to remember me in the, uh, mobile. Okay, so see, the app has opened it in a model. So now whenever you click on this, uh, tab, it would, uh, kind of try to open in the A model. Now see, we have this very weird navigation. I'm kind of already sent in, but, uh, I can, uh, navigate back and, uh, I see the sign in page. So this is a strange behavior and uh, we don't want to have it. And, uh, how this can be solved is, uh, okay, let me just undo this. So, uh, whenever you sign in, you don't want to be able to navigate back to a page where you're not sent in yet. So I'm going to, uh, replace the navigation. Now, let's, uh, have a look again, I will just copy the password. So this is, uh, an important navigation pattern that I'm going to show you. Now, log in. So see I'm logged in, but if I click back in the navigation history, I'm not logged in anymore. So it feels kind of weird and have all this where the interactions. So I'm going to add the, the turbo action replace, but default, we advanced navigation. So the navigation gets stacked like in a browser. You go to the next page and you have the previous page in the history. And we don't want to stack it here from an under sent in page to ascend in page. So, uh, they're going to, uh, say that data tur action should be replaced. Let's generate a device views. We're using device here. Um, rails generate device views. I've run bin Dev once again. Let me just, uh, save the device views. Okay, and now in device, uh, sessions new here, we're going to say Data turbo Action. Replace data turbo Action, uh, replace. So now if I, uh, log in, let me try logging out. And now I will, uh, log in. Let, lemme just, uh, be sure that the, uh, H TM L has updated. I will refresh so that the cache is, uh, updated. Now I'm, uh, logging in and the expectation is that the, uh, the previous page, this one is not saved in the, the browsing history. So log in and you see, if I go back, the send in page is not available and the, I see only the pages with the context of a current user. So, uh, uh, there will be many pages in your application, uh, especially the ones that open in a model where you would want to replace navigation instead of, uh, advancing it advance, uh, uh, data term action advance is the default. You don't usually type it in, you would want to replace it, but here's a catch. Often you would want to, uh, have this turbo actions replaced only on mobile and not on the web. So, uh, hot via native, uh, helper provides you with this, uh, uh, helper. Where is it? Um, replace if native. And, uh, here you would just say not to action replace, you would say just turbo actions replace if native, and it would, uh, replace the navigation if native or advance. So the default behavior, if it is, uh, not, uh, native, and, uh, that's, uh, one of the helpers provided by the native, uh, rails gem. And you would want to use this kind of behavior in all the device forms, definitely maybe in other forms in your application. Now let's have a look at, uh, some more stuff. So I will go to a page in the app. Um, now the tabs don't seem to be working well. Let's look at the tabs. So tab bunch direct to organization's path, and, uh, it doesn't seem to be working. Why is that? Maybe we need to restart the mobile app. Okay, restarted. So let's try to create a new organization here. We have this, uh, model pop up and, uh, there's, uh, a lot of stuff that we don't want and we want. So first of all, we don't want to show the header, especially in the, uh, model views. Also don't want to show like all this foot navigation in the, uh, mobile views. Uh, then maybe we would want not to have this great organization button, uh, in the html, maybe we want have the native submit button on the top right of the screen. So lot of things that we can, uh, improve here. And, uh, again, the titles, the titles don't make sense here. You would want to have the title new organization or create organization and you don't want maybe to show the title here. So all of things that you would want to have different for a native screen, let's do it step by step. Um, first of all, let's, uh, start with hiding some views in the mobile and having them visible in the web. So you see for the web, we have like a couple of, uh, HT ML uh, links in an HTML footer. We want to hide them in the, uh, the native application. There are a couple of ways to do it. So if I go to our application, HTML have, have the footer and foot enough. So, uh, the easiest way is to hire them if it is, uh, a native application. So we have this helper, uh, by default, uh, UN unless TUR native app, you can use either TUR native app or hot fire native app. They are equivalent. So, uh, we will display the footer if it is not an hot, hot by Native app. And same with the footer navigation. Let's, uh, refresh and you see the footers are gone. We can also, if we won't hide the, uh, head navigation, let's try doing that. We can, uh, hide it with the, unless less ative app. Let's, uh, refresh. And now the screen looks much cleaner on the mobile, but if we go on the, the web, actually nothing has changed. So you see we have some specific, uh, UI elements on mobile different than on the web because, uh, like mobile, they have the native tab bar and native header. And, uh, in the web they have to add them both with the HDMO. So, uh, hiding elements for the, uh, the turbine native app patient mode that turns either true false helper. Now let's go next. Uh, we have, uh, the page titles. So here, uh, here we want to have a different titles on all the pages. Like I go to the organization page, I want to say the organization name in the title, not uh, uh, the name of the application. So, uh, if go to Hot native Helper, there is this page title helper. And in the application layout, we have the title sub dynamically, the page title, and, uh, that's all this conditions. So by default, try to render the hot file native title or try to render the, uh, title from the, uh, HTML or from the controller or the application name. And it is a bit simpler for the web applications. So let's try updating the title of a page. Let's first try doing it on the, um, native just by setting the page title in the controller. So for example, on the organization show page, I will have the title be the organization name. I will go to organizations controller. And here in show I will say page title equals organization name, refreshing. And here you see the name of the organization is the title looks, uh, uh, better. Uh, it is one day of setting the title. Another way is, uh, with, uh, uh, content four or native title sign in, or whatever you want. Let's try doing that one. So for the same organization, I will try setting the title in the view using content for I will go to organization show template. And here I will say Content for native title, uh, organization. And, uh, at organization.name, refreshing. And you see the title from the HTML defined. The HTML overrides the title, uh, if there is one set in the controller. And now if we go to the same organization on the web here, you see we have the title, that is the application name. I will go to open one organization, uh, ref Refresh. And, uh, the title is Still Money Gun. Why? Because, uh, uh, we have this page title that folks at the moment only with the, the native app. So maybe we want to actually add this page title to also work for the, um, web app. Let's refresh. Okay. And you see the title has been dated, and we can also manually set a title, uh, in the HTML for the web. So, uh, there'll be one title for the native application and a different title if you want one for the web application, web organization. Refreshing. Here we have web organization, but in the native application we still have a different title. So usually on the web you would maybe be able to have longer titles. And, uh, ideally in native, you'd want to have a short title that would fit on your narrow screen. Okay, so like this, you'd want to set titles to each view of your application so that it displays nicely here. And, uh, let's go further. So here we have the list of the organizations and, uh, here we have organizations, organizations. We don't want to display this organization's, uh, thing here. And in fact, what if we move this, uh, a new organization button to the, uh, native, uh, n bar. We can put it right here. And for this we're going to use a bridge component. And um, it'll be called button component or button controller. So we are going to put a button to create a new organization. Uh, here is the example of the stimulus controller. And, uh, a few examples of how you can render a button. So we are going to use an icon button, just an icon, um, and I would open our organization index. And, uh, let's see. So we want to update this LinkedIn new organization. We are going to add data. Uh, controller will be bridge bottom, uh, pay attention. So all the bridge stimulus controllers are in additional folder. So to files in this additional folder, you would need to have not just dash, but dash, dash and, uh, uh, bridge, uh, title new organization and, uh, uh, bridge iOS image. Uh, uh, let's start with Play Circle and then we can change it. Okay, so, uh, let's try refreshing our page. And you have a native button show up, I click it and it opens the model to create in organization, isn't it? Cool? Let's just, uh, uh, change this play circle to plus circle. I think there exists as a button. Okay, wonderful. So we have this plus button, we click it and it opens up a model to add any organization. Now we can hide this, uh, element completely, um, but we don't want to use, uh, uh, unless turbo native app because it is going to completely, uh, not render the component. And we want to render the component. We just, uh, um, don't want to, to display it, because if we don't render this component, then it is, uh, not going to show up here. There's nothing to render. So, uh, I will actually, uh, I can actually just say, uh, tur native hidden, I think it's turbo dash native or Hot five dash native. Let's see, in tailwind config js. So again, another thing that was added by the Gem Hot native rails. If you're using tail wind, it is going to add the plugins for variance Hot five native and not hot five native. So you can apply different styles only for hot five native or only for hot five native applications. So I will say hot five native, hidden, refreshing. And you see it's hiding the H TM L new button, but it is displaying the, uh, mobile, uh, version of this, uh, button. And we also don't want to show this organization's, uh, title here. So we can, uh, uh, go to that page component where it is defined, And, uh, the whole element with actions and the title, we can say, uh, native hidden, and then we don't need to manually hide it here, but let's leave it. So refreshing. And you see the screen is much cleaner now because we removed everything that is not needed on native, but we do still need it all on the web. So going to our list of organizations here, here is how it looks, uh, in the web. Here is how it looks on mobile. You see, uh, there are differences. Um, yeah. So let's, uh, go to the next, uh, step. We have cleaned up our ui. We understood how to use, uh, native titles. Uh, let's go to the form. So here we have the form and uh, we have the H TM L submit button. And, uh, we want to hide this HTML submit button and put it here. Uh, um, for the submit of the forms, we're going to use a different component. It is, uh, the form, uh, component, so we have it here. And, uh, you would need to manually apply like data, uh, controller bridge form. You would have to add the, uh, target on the submit button and so on. So, uh, there is an easier way. The Gho vantive, uh, uh, rails provides, uh, uh, helper named bridge form width that is going to automatically apply the form controller on any form. You just replace form width to bridge form width. So they have organization slash form. They're going to replace form width with bridge form width, and it should show, uh, native, uh, submit button here. Let's, uh, refresh. It does not work. Now this is a big catch. This is really important. I struggled with this a lot myself. So you need to have, uh, the title of the button defined. Uh, otherwise it won't work. Uh, it'll not work with like the magic lookups that are done by Rails. You need to have a title of, uh, button defined. So let's try once again. And here you see I have the save text. I can put, uh, something else. I can update the text. I will dismiss the model. Go in here again. You see the text has been updated. Okay, so let's try, uh, click and save. And you see we have the, like validation errors. And, uh, you see it also automatically hit the submit button so I don't have to manually say that I want to hide the submit button. It was done, uh, automatically by the bridge form builder. It added the, uh, class native hidden. Okay, so we can submit a form, let's, uh, create a new organization, save it. You saw the interaction, it closed the model, and then it navigated to another page. And, uh, it didn't look like a very nice, uh, interaction. Uh, so we can maybe improve it. Uh, the way to improve it would be the same as we did with the sessions new. We added this data to action replace if native. So it's something you would usually want in all your models, uh, most likely. Uh, here I would additionally say data turbo action replace if native. Now let's create a new organization. And, uh, uh, I don't think it was smoother, But nevermind. We can have a look at this later. Another problem I see now I just navigate to the organization and I don't see any action buttons. It is because, uh, uh, in the page component, I said native hidden, uh, everywhere. I will just do it, uh, not everywhere. I will do it on the title of the page. And, uh, so, so the action buttons can still be visible. Uh, so like here, the manual hit the action button to, uh, add new organization, but, uh, we are not hiding this buttons anywhere. So, uh, the font displayed. Okay, uh, actually thinking about these buttons, let's try adding another bridge, uh, component. Uh, the action sheet, that would be like a menu from the bottom or a UI menu. That would be add dropdown from the top. We could use either of them. Let's, uh, try. So, um, let's have a look at our catalog of, uh, bridge components that they have. Let's try adding the, um, overflow menu, uh, controller. I will just go with this, uh, um, HTML example, paste it in the organization's show. And, uh, let's see. So we are going to, uh, have most of everything the same, but, uh, on each of these, uh, uh, links, we are going to say, uh, data bridge menu target item. Okay? And, uh, we're going to add, uh, this wrapper around the links. Uh, okay. Uh, so we have the bridge menu, we have the bridge overflow menu button to open the menu. And, uh, each of these, uh, links and buttons should open up as an action sheet. So now if I refresh here, you see I have this button pop up. All these links are hidden now because they have this hot fire native hidden, uh, given to us by the example that we copied. Um, yeah, in the example we had hot fire native hidden. So the buttons are hidden, and we have the native action sheet. Wonderful. So if we have organization open, we can have all the real organization data here. Let's open like organization show. Here. We are going to have the content of organization like the an image or like, uh, all the post tags, whatever you have inside an organization. So the content. But we have the native menu of actions, so we can open the model to edit the organization. Uh, we can, uh, transfer the organization. We can, uh, destroy the organization. Let's try to destroy the organization. Okay, you see, we related back to all the organizations. So see we use the another which component that is, uh, overflow menu. That gives us, uh, n button to open an action sheet. Okay, let's, uh, go further. Uh, what are the helpers have we got here in the Hot Definitive Helper? So, uh, if you put metatag, it, uh, ensures that a user cannot zoom, uh, the screen with, uh, his fingers. You don't need to do anything with it. Then I mentioned previously you should update the page titles for all the pages that users can navigate to. So the title is set correctly, um, uh, replace if native, uh, to replace the current screen, especially models, and not be able to navigate to the page. It was like, uh, before the model. Then, uh, I override the link too because if you, let's say, try to put Target blank on an internal link. Let's just try putting a target blank on an internal link in our application. I will go to Organization Index and, uh, on a single organization link, I will add Target blank. So try to open it in a new tab, target blank. Okay? Now I'll try to open it in a new tab. And you see it tries to open a link in our application in, uh, in, in app browser. This looks horrible. We don't want this. Uh, but in some cases in the web, you want this, uh, target blank to be set on internal links. So by having, uh, this, uh, way of overriding link to default behavior, now I'm going to refresh and, uh, it's kind of supposed maybe like, uh, I have some changes discussed or something, but, uh, it's supposed to not open, uh, uh, internal URLs in, uh, uh, and in a browser it's supposed to open them, uh, normally, so without trying to open the, in a browser. So it would kind of set target blank only if it's not a native app. Okay? Then we have the bridge form width that, uh, adds the native submit button and hides the H TM L submit button. Uh, couple of helpers that we don't really need. Uh, let's see what else was added by the jam? So we have, uh, yeah, we have device format. Okay, this one is a very interesting one. So let's open device format. So we can have a new template variant that is mobile. And, uh, it would, uh, uh, if you are opening in a webpage from a mobile, uh, browser or from a native, uh, app, then it would be able to render different templates. For example, let's go to organization index. So you have the organization index, and what if you want a completely different table, uh, template for, uh, mobile, I would just go with this. I would have index html plus plus mobile, and, uh, I would just say, uh, this is mobile. Now, uh, let's go back. I will refresh. And here you see it is rendering a different template. It is rendering the template with plus mobile, uh, on mobile. But if we go to the web, it is rendering the old template. So if you have like really big differences, uh, in the, the way you want to render on mobile, uh, on native on web, then you can use, uh, this, uh, device format concern. Again, you can have like a different, uh, variant for mobile browser and for native, uh, app. It could have like a request variant, mobile request variant native if TUR native app, and, uh, variant mobile. If, uh, the browser is a mobile browser. So let's, uh, say this is going to be plus native refresh. It works. Uh, let's just copy what we had before. Okay, so we have like this additional text, this mobile plus the, uh, general layout of our page. Uh, let's see what else was added by the gem. So we have device format. We have the tabs controller that you can set the tabs. Um, path configuration, uh, mostly like the most used thing. It will be defining which pages should open up in a model and which pages you want to refresh on. The, uh, pulling down alternative help, we had to look button component. We added the button here, form component, the added the bottom here menu and the overflow menu. Uh, we have it here. Let's actually try something different. Let's try, uh, nav, uh, component. Here's another component is, uh, provided by the Hot Bay native, uh, uh, rails starter. We have this n co controller. And, uh, the idea is that instead of, um, opening this action sheet, it's actually going to open a dropdown from the, uh, right here. So let's try adding this kind of dropdown. I will, uh, maybe try adding to another page. Like, uh, for example, on the homepage, I will have, uh, a button to, uh, sign out and a user icon. Let's try adding this. So I'll go to Static Index, the homepage, this one, and here I will have this, uh, uh, link to I know organizations, uh, or link to edit, uh, my account. Uh, edit, uh, user. What's the path? Edit, user registration path. It's going to be use a circle and a link to sign out. Uh, and this is going to be Destroy User Session Path. Uh, data, uh, we will say, uh, turbo Action should be, uh, delete data, uh, turbo Action. Do we have Turbo Action defined anywhere else? Um, yeah, we use it previously to, uh, uh, advance or replace navigation. And here it's going to be Turbo Action. I think it is, uh, destroy, ah, it should be Turbo Method. Turbo Method. Uh, destroy or delete method. Uh, delete. Okay, let's, uh, refresh. Okay, here I have this, uh, uh, Uh, dropdown. I can go to all, uh, uh, to edit my profile, edit profile, or I can sign out. Let's try again. So here I click edit profile or sign out. And here I've signed out. Uh, so yeah, it works. We've, uh, tried another component. Let's see what else was added by the jam. So another review, prompt controller. So I think you can ask a user to leave a review, uh, three times in a year. So use this one wisely. Uh, best of all, if, uh, a user just had a positive interaction with your application, and, uh, you could add this, uh, metatech anyway in your application. Let's try adding, uh, uh, for example, in, uh, yeah, on the homepage. Let's try adding it and see if it works. I will refresh. So, uh, it doesn't focus because, oh, yeah, here it is here. It's wonderful. So here we asked a user to leave a review for our application. Fantastic. Um, yes, uh, that's, uh, that's basically it. So we went through all the additional features that, uh, the Fin Rails adds, and all the additional, uh, uh, elements that, uh, my for of Fin iOS demo app, uh, apps. And this can really speed up the way you, uh, can make your Rails app, uh, converted into a mobile application and launch on the app store. --- # #240 Stop Making Ugly Rails Apps – DaisyUI + Tailwind Will Change Your Life - URL: https://superails.com/posts/240-daisyui-an-essential-component-library-for-tailwindcss - Published: 2025-08-22 - Duration: 12:30 - Access: free - Topics: tailwindcss, css, frontend BootstrapCSS has amazing components. TailwindCSS has amazing utility classes. DaisyUI is a component library on top of Tailwind. It let's you use Boostrap-like components. If you are using Tailwind, I think Daisy is a must-have layer on top. Apart of components, it also provides semantic dark mode friendly colors and easy theming. Install DaisyUI in a Rails app: https://daisyui.com/docs/install/rails/ Episode source code: https://github.com/corsego/240-daisy-ui/commit/a0db2b85bdeb8930aae0ab9d311be5a57be9ebc1 ## Transcript 10 years ago when I was just starting to work through bone rails, everybody was using Bootstrap. And Bootstrap is actually great. For example, if you want to have a button in your application, you would just type, uh, button, button primary. So you see there are these semantic names that are easy to remember and you don't need to write, uh, a bunch of custom, uh, styles, or you don't have to use, uh, utility classes as TNT proposes. Nowadays, everybody's using tnt. It is the most popular, uh, I think, uh, c success, uh, framework for Ribbon rails. And the, the approach with NT is that you don't use, uh, components. You have utility classes. Utility classes are basically, uh, more comfortable to write, uh, styles. So, uh, radio stem instead of both radios than pixels. So the syntax Verizon is much easier and you can quite easily remember the names of the ion utility classes. But the problem, the VIN is that if you want to create a button, you would need to write maybe like 20 utility classes. Whereas with Bootstrap, it's just a very nice API button button primary. So here you would want to reuse a button, you need to copy paste the classes, and it's so uncomfortable. Then there are all these Ribbon Rails component, uh, libraries, and they want to componentize the CSS so that, uh, you can reuse it in a more comfortable way. Let's see how different, uh, component libraries use buttons. Let's see here we have button. Uh, so nitric button, you have nitric button, the default one, instructed primary button, destructive button. So you would need to write less, uh, uh, less custom CSS. You would just use their buttons. Let's see how it is done by Polaris, for example. Let's see, uh, Polaris, uh, button. So you have, uh, uh, Polaris, uh, button. You have, uh, Polaris button, uh, variant, primary and Destructive. So you see they have their own, each of these libraries have their own APIs to more the same components and need to learn these APIs. Uh, but I think, uh, from my experience at least, the best, uh, of both worlds between, uh, the utility classes from VIN and the components from Bootstrap is offered by de ui. So these are, this is a component layer on top of VIN CSS and, uh, let's see how you would want to create a button with TA ui. So you see the API is more or less the same as bootstrap button button neutral button primary. So it is, uh, short, it is semantic, it is easy to, uh, remember, and it is just a layer on top of end. So you could still apply the classes like with full, and it is, I would say easier to remember than, uh, right in the, the like Ruby Nitric primary button or Polaris button. And, uh, add in the variance. So, uh, let's try using DA UI in RA's application. Um, here I have a new Ribbon Rails application running. Let's open it. Let's, uh, create a basic scaffold. Now, before I do it, I will show, so installed Tevin CSSI create this application that Tevin css by default using Rails, you my app, uh, C equals tailwind or Vin CSS. I don't remember one of these, but basically I already have VIN CSS installed. And now if I create a scaffold, let's say rail generates scaffold, post title body text, it is going to generate that scaffold with already some, uh, vin, the CSS here to style everything. I think this vin SS was, uh, approved by Adam Bean, the creator of VIN CSS. So, uh, if I go to the application, I go to Route and set Route to Post Index. You see it's already kind of styled with some VIN CSS and uh, let's look at this nt. So going to index, you see, for example, the links and buttons. It has a bunch of, uh, visibility clause for the buttons, and, uh, it'll be pain to copy paste it, uh, all around. Let's see, are these tool the edit and show the same? Yeah, this tool are the same, but Destroy is different because it's using different colors. And for this, we can try replacing these, uh, buttons with DA ui. So let's try to install DA ui. I'll go to DA UI install, uh, for Rails. So already having your Rails app, I already have a VINs installed by default. I'm going to cope with these tool scripts. It's going to add couple of Gs files into my application. So, uh, it'll add, uh, couple of Gs files, Daisy theme and Daisy, I, the Gs. And now I will go to application CSS in the tail folder, and I'm going to cope with this. I don't need this source, nothing just like that. And now I will restart the server just in case you see it says DA UI is running. And now let's try using some DA UI components. So I will cope with this. I'll go back to my application and I'll try adding this, uh, button primary here. And you see here we added a button primary and it already works just like that. So now I can, uh, try to reuse the, uh, buttons around my application. I will use, uh, button primary for new post B-N-B-D-N primary. And you see here we're using the, uh, component instead of, uh, just, uh, typing in the custom, uh, uh, the custom, uh, utility classes. Let's see what it offers us for links and, uh, destroy buttons. So it'll be just the default button here. We have button with the error variant. And you see, just like that we're already not using this long, uh, utility classes that they would want to copy paste, see what's the difference, which you can just use all components that have been created for us by people who know CS as well. And, uh, we can just reuse them. So, uh, another thing I would want to do in any application is set the default background color and text color. So going to application HTML here, I would go to head to HTML and set background base on hundred and text based content. So I refresh. You see, it's more the same. Nothing has changed, but, uh, uh, now it is going to work well in dark mode. So if I don't add these, um, why is it work? Okay, uh, maybe I have something, uh, cached, but uh, dark mode is not supposed to, uh, to work. Let's add background based on hundred. Let's try to see if it works. I will set it to 300. Okay, the color has changed. Yeah, so, uh, if you set the background and the color on the application, just like that, we already have basic dark mode working, and there's really a bunch of, uh, really nice AI components that you can use. For example, uh, doc, previously I created a wireless video about photo navigation on mobile and how to create this kind of fuer that would work nicely on mobile. And this Azure has this component doc that lets you create this kind of mobile, uh, photo navigation. Let's try cope with this, uh, element and put it in our application. So on the main here, I'll put the dock and let's see if it, uh, renders. So here, just like that we have a dock. We can use our own, uh, link. So, uh, link to posts. And Let's see. So now these are actual links in our application. Uh, we have only two real links that we can use, uh, posts and, uh, new post. Both of them are active at the moment because they have, uh, been given, uh, dog active. We can say it can be conditional. So now we have, uh, photo navigation. And you don't want this foot navigation to be there on large screens. You want it only for mobile. So we're just going to add the Devin CSS, uh, large screen hidden. And now you have foot navigation on mobile version, just like that. So you don't need to write your own custom. CSS need to reinvent the bill for this component. So I highly recommend you to, uh, get acquainted with the components, uh, that DGLI provides and use them by possible inside your application instead of writing the custom CSS. It helps a lot. Um, another couple of things that this UI offers is themes. So let's try adding themes. I will, uh, add this. Uh, so by default it is, uh, light for light mode and dock for dock mode. Uh, if I refresh the page now, uh, nothing is really going to change. Let's, uh, go to light mode. But, uh, let's see what themes, uh, this has out of the box. So it has a luxury theme. For example, it would be a quite nice dark mode theme. So I will set, uh, luxury for dark mode. Now going back to my application, I will set dark mode. And here you see I'm using already the s luxury theme. And, uh, for light mode, let's change the theme to something like, uh, uh, autumn Or maybe lemonade. No, let's, let's do autumn. So I will set autumn refreshing, and you see it's already a different theme. And just like that you can actually also create your own, uh, themes. So if you have your own, uh, Figma with your own colors, with your own board radios, you can set it all here, export it into application and use your own theme. And another thing I like doing was adding a prefix as du So it lets you, uh, have du button and du button primary. Uh, when I was just starting to learn Daisy ui, it helped me to, uh, know which component is, uh, a daisy component and which is not a daisy component. If you have your own components, uh, if you have your own CSS components in the app. So I will comment this out. You see, it's not using da UI anymore. Uh, okay. And another thing, so if you're using DA ui, they have, uh, an LLM helper. Let's search for LLM. So, uh, there's this instruction. Uh, press command shift, p command, shift P and uh, add new custom docs. And you would add the, this URL, uh, maybe just like this. So it'll add the Da U Eye docs. And now your lamb knows how to use DA U Eye. You see, I added a docs for D UI and for, and, uh, you can also when doing prompts, uh, reference, uh, DA Z ui. So it'll know to look in the DA Z-U-I-L-M prompts for the ZI components that it can use to create your, uh, CSS. So if you're not using DA UI yet, but you're using nt, I highly recommend you to try installing DA ui and step by step, try to replace your existing buttons and other components with Daze I components. And they're semantic, they've written really well, and, uh, they work well in dock mode and light mode. --- # #239 Feature flags and A/B testing - URL: https://superails.com/posts/239-big-boys-use-feature-flags - Published: 2025-08-02 - Duration: 12:19 - Access: free - Topics: Gem, admin features Any mature Rails app needs feature flags. - Usecase #1: Product & QA people can test features before releasing them. They can enable/disable features without developer involvement. - Usecase #2: A/B testing (partially release features) Flipper is a great gem to manage features. It provides handy backend methods, but most importantly a great UI to manage features. - [Flipper docs](https://github.com/flippercloud/flipper) - [PR adding Flipper to a Rails app](https://github.com/yshmarov/moneygun/pull/292) - [Text walkthrough](https://blog.superails.com/feature-flags-with-flipper) ## Transcript Hello friends, all the serious applications that I worked with, meaning, uh, that there are lots of users, lots of, uh, developers working on the application. So all these applications were using feature flags to manage feature releases. Like let's say developers built a feature and product people or fault assurance, people want to test the feature before releasing it to users. So you would use feature flags or if, uh, you want to ab test features, let's say you release the feature to part of the users, but don't release the feature to another part of the users. So it'll still be using feature flags. And in all these cases, if we're using the Gem Flipper to manage, uh, features in the Rub rail application. So in this episode, we're going to install Flipper in Urban Rails application and see how it works. And we're going to do it, uh, in my case, on top of my Mono Gun Ribbon Rails poly application. You can use it or you can add this, uh, flippy jam to your own Ribbon rail application. So let's try to install Flipper. Uh, they have really nice docks. I'm going to docks. And in getting started, they're going to install not the cloud version first, but they're going to install the cloudless. So like just the gem that you can use. So bundle add Flipper, active record, Uh, then Flipper set up So it adds an, uh, initializer and adds immigration. So Flipper features are the names of all the features like, uh, search or I know, uh, billing, whatever. And Flipper Gates is, uh, whether a feature is enabled globally or not, whether it is enabled for a specific user or not. So like all the enablings of a feature. So we've kind of installed a flipper, let's say Rails did Beam migrate and see how it works in the console. Let's say Rails Console. Let's open Flipper, flipper dot. Uh, let's create a new feature. Let's name it Search Flipper. Search. Do you see State is off by default. Enable, let's again, call Flipper. Search State is on. So it is enabled. Let's check it if it is enabled. Enabled, it is true Search Disabled Enabled is false. So we created a feature flag and we can use it in our backend or front end. So let's, uh, start the rail server and try to, uh, hide something behind the feature flag. Let's try hiding search behind the feature flag. Now, um, in many cases you just hide in the front end behind the feature flag is enough. Uh, but you can sometimes also hide backend, like sometimes you have different calculations in the backend, uh, based on the feature flag. But, uh, now we'll just hide the, the front end search, then going to hide the backend part of it. So let's go to our NEF bar And uh, we have our search. So I will say if flip search is enabled, we can do it for this syntax or we can do it for the the Syntax Flipper. Um, search enabled, uh, either should work, uh, so if flip a search is enabled, then show the search. So you see now it is disabled, uh, I will enable it, flip a search, enable I refresh, and now it is, uh, enabled. So now we globally enabled or disabled, uh, uh, feature. But, uh, the whole point of installing this kind of gems is not that the developers can, uh, uh, enable all disabled features, uh, in the Rails console or in the code. The whole point is, uh, to let the non-developers see a list of features and, uh, they can enable or disable features as they see fit. So let's say the product person, then you hand over the feature to him, he, uh, test the feature and says, okay, the feature is good to go. Uh, I'm going to enable it. So the developer is already not responsible for enabling the feature. It's already like handing off the responsibility to, to another person in the team. So, uh, you would want to have a UI value, uh, product. People can see all the features that, uh, exist and they can enable or disable the features as they see fit. And here Flipper starts shining because they have this, uh, uh, separate flipper ui, uh, gem. Here's the link to the Gem Flipper ui. Let's try installing it. You see, it'll give you a list of features and you can see whether the feature is enabled or disabled and the non, uh, developers can manage, uh, the features. So let's add the gem, flip a UI, and we'll need to edit to our roots. Uh, now I can edit to my roots, but here I already have, uh, a separate folder for admin route. So inside my route I have a file named admin and here already have this, uh, um, concern Authenticate user, uh, user admin. So this authenticate user is provided by, uh, device and it checks if the user has a admin bullet set to true. And I'm going to add the Mount flipper to, not flipper, but feature flags. This is less branded. So I will, uh, now restart the server and uh, go to slash uh, feature flags. And here I have, uh, uh, the list of our features. So I have the feature search, let's start designing it in the console. I refresh here. You see it is disabled and I can now manage the feature from the ui and it is much more comfortable than managing these features through the console. So again, I will go back here. Now it's disabled. Now it is, uh, enabled. And now let's try enabling the features, not globally, but for specific users or for specific organizations. So let's say we are going to enable search only for, uh, one specific user for, uh, this user and disable search for all the other users. Let's say we want to enable it only to like the person who is testing the feature. So, um, I'm going to find this user, user, uh, find by email.id and it's the user with ID four. And I want to enable, uh, search only for user of ID four. So going to Flipper, uh, docs, we have this Flipper Enable Actor search for, uh, user find Four. Okay, and uh, going back to the Flipper ui, uh, um, yeah, I'll disable it globally and enable it only for this, uh, user. So that's again, run this command and you see it is enabled only for user with the ID for if I refresh. You see, it is not enabled. Why is not enabled? Because I need to register, uh, this kind of actors in the our Flipper Initializer. So going to our flipper, initializer, I'm going to say, um, flipper Register users, um, actor. And here I'm going to say value starts, uh, with users. And yes, it also gives me an example of how we can register features on an organization level. So, uh, now we can enable features for specific users or specific organizations. I will restart the server And see if this user has the feature. He doesn't have the feature yet because I need to check if it's enabled for the current user. So I add current user to this, uh, check, and now it is enabled for the current user. I can, uh, also do it here, search current user. So like there's a two different syntaxes that should both work and you can use either, uh, that you find better for yourself. So you see now search is enabled for this user. Let's just double check that it is disabled for another user. Let's, um, try sending in as another user. Okay? And for this user, uh, search is disabled. So like this, you can generally manage features for specific users or for specific organizations. Let's say, uh, we have an organization and you want to hide projects. So, uh, uh, I know let's go to Project Controller and uh, uh, set. Yeah, we can, um, add this set feature flag or whatever you can name it in any way. Uh, uh, return, uh, redirect to, uh, route Path, uh, unless Flip Projects enabled for Go organization. So this I can enable projects only for a specific organization. I refresh. Now you see it redirects me back to organizations each time I, uh, go to uh, projects. So now I will, uh, enable projects for a specific organization. Um, let's, uh, go, uh, and find organization id. Um, yeah, we can find it by this identifier Organization. Find id. Uh, so let's enable the feature flag. Uh, named search. Oh, named Project for a specific organization. Let's, uh, create, uh, projects and enable it. Uh, let's first try globally. So now I go back, you see projects works here, I create an organization, projects works here. Now I will disable it. So now it is disabled. And now I will enable, uh, organization. I think, uh, It should be organization and the idea of the organization will be five. Okay, now going back here, projects FE and here projects do not work. So you see this where I can generally enable, uh, features on a user level, on an organization level all globally. So, uh, you see we have, uh, both features partially enabled. Now let's enable search globally and everybody has access to search. So this is, uh, the basics of using the, uh, feature flags with the Flipper. Now another small quality of life improvement you can make is hide the marketing information in the Flipper ui. So, um, to do this, we can go to our Flipper RB and uh, we can see what, uh, is the configuration for Flipper ui. So flipper ui dot configuration. And here's a bunch of different configuration that we can manage. So I will say Flipper ui, configure the config. Fun, false. Uh, so like the text is going to be less if there is any cloud recommendations, false, uh, and show feature flag descriptions if there are descriptions of, of feature flag control. So, uh, I will restart once again, refreshing. You see the marketing information is, uh, missing and it looks more professional. So that's, uh, it I really recommend using Flipper. It's a great gem. --- # #238 Deploy Rails 8 to Render.com. Solid Trifecta on Postgres - URL: https://superails.com/posts/238-deploy-rails-8-to-render-com-solid-trifecta-on-postgres - Published: 2025-04-23 - Duration: 25:32 - Access: free - Topics: deployment, solid (cache, queue, cable) These days Render is my go-to tool for deploying Rails apps. For example, SupeRails.com has been hosted there for 2 years now. Render is as easy as Heroku to maintain, but cheaper to scale! Try Render: https://fnf.dev/43PHC4F Timestamps: 0:00 SupeRails on Render 1:58 Switch Sqlite to Postgres 03:50 Solid on single database 08:36 Solid Queue and Mission Control 13:36 Scaffold Posts 14:13 Deploy to Render 19:57 Render console 20:50 Render.yml Blueprints 22:15 One-click deploy P.S. A big thank you to Render.com for sponsoring this article! It gave me the opportunity to dive deeper into deploying Solid with PostgreSQL and explore Render. ## Chapters - 0:00 SupeRails on Render - 1:58 Switch Sqlite to Postgres - 03:50 Solid on single database - 08:36 Solid Queue and Mission Control - 13:36 Scaffold Posts - 14:13 Deploy to Render - 19:57 Render console - 20:50 Render.yml Blueprints - 22:15 One-click deploy ## Transcript Hello, friends. Today we are going to talk about deploying a Rails Aid application to production on gender.com. Now, uh, super Rails itself, uh, has been running on gender since forever, since I launched Super Rails. It's always been on render. Let's first have a look at how Super Rails is set up to work on gender. So first of all, let's have a look at the project, and we have, uh, web service, the rails, uh, application, uh, voca and the database. So I'm not using Redis because I'm using the gem. Good job for, uh, active jobs for all my jobs. And, uh, it is Postgres based, so I don't have Redis as a dependency. Let's a look at the web application. The setup is, um, very simple. So going to the settings I have, uh, the build command. This is mostly all the default, but in the end I also run bundle execute trails, db, migrate. So on each push to production, I also run the migrations if there are any missing migrations. And then they have just bundle start rail server, uh, going to environment variables. Also, nothing specific, nothing important, just the database, URL and the Rails Master key. These are important, uh, the database, URL, you can get it from render. Again here, if I open my database, click on connect here, I will have a database, URL, I can copy it and paste it there and go to the worker. Again, it's the same web application deployed where I also add the database, URL, the real master key. But I have a different run command going to settings here. I start command to start. Good job. So when will execute, good job start. And that's basically it. That's my whole setup of, uh, heavy rails running on the production. Nothing sophisticated. And if I have a look at the billing, let's see here. So, uh, uh, you see I'm paying like $21 for running the, uh, web application, the VO and the database that is shared among those two. Now, how can we deploy a new Rails application, uh, rails eight application to, uh, production. Um, let's start from a new application here. I've just created a new Rails eight application. And let's look at our database setup and out our GEM file. So we have, uh, gem Escal, and we have, uh, uh, escal as a database for production and for development. First of all, I'm going to change the database from escal to, uh, Postgre Scale. So render, it runs with, uh, uh, Postgre Scale. You can create a new Postgres database and connect to it. And, uh, generally speaking, I don't, uh, feel confident yet in deploying my applications, uh, uh, with having Skel Light as a database. So, uh, first of all, I'm going to change the database to PostgreSQL. We can run this command reality system change to PostgreSQL, uh, allow, okay, it'll, uh, let's say the changes. So in the GEM file, we replaced, escalated with Postgres in doca file. It was also replaced, and we updated our database via. Now let's have a look here. So we are using Postgres. And, uh, here you see if a production, we would be creating four separate databases, one for the main application database, one for solid cache, solid queue, and solid cable solid tri factor. Okay, let's run bundle And let's say Rails db, uh, create. You see in development, we created just, uh, one, uh, database. Uh, but, uh, with the current setup on production would be creating four separate databases. Let's say Rose BIM Migrate. Okay, and let's save our changes. So, um, what was the command, uh, change to Postgre scale? Okay, now, uh, we don't want to pay for four separate databases, uh, then deploy into production. We want to, to maybe use a bunch of database for all these services. So we want to solid Q cable and cache to use the same main postgre scale database. Andrew, um, Atkinson, the Postgres guy, he wrote nice book about Postgres, uh, wrote about, uh, using the sold queue and sold cache with Postgres. I commend these, uh, articles. Good stuff. So, uh, let's try, uh, running sold queue, uh, the, uh, job adapter on development using our main, uh, database. So, uh, to do this, uh, we can go to the documentation of soq. And here we'll have something about, uh, single database configuration. This is what we need. We want to use not, uh, three databases for each of them bought just one. So copy the contents of DB Q schema, uh, into normal migration and delete this content. Uh, let's say rails generate migration. Uh, solid q it creates, uh, the sold queue migration. Let's, uh, copy all this, uh, stuff into the main migration. Okay? Delete the queue schema. And, uh, we can also do something similar with the, uh, cable and with the cache. So solid cable And solid cache. Let's try running all the three solid, uh, uh, gems in, uh, the same Postgres database. So, uh, solid cable and, uh, solid cache. Okay, delete these other schemas. So we don't need all these, uh, through additional schemas. Let's run the migrations rails DB migrate. Okay, uh, let's, uh, see our changes. So in the schema, we've added all the migrations for all the solid tools. Uh, looks good. Now let's update our database configuration. So going to database via ml, um, for production. Uh, we can remove all the settings for using a separate database and going to, uh, queue via ML here. We are going to, no, we actually don't need to say anything. Let's go to cable. And, uh, here we are going to say, uh, connects to database write. And, uh, it's not going to be write, it is going to be the, uh, main database. Let's see, it's going to be primary. Um, we have primary defined, uh, here in database v ml. So this is the primary database. And for cache in production, we're also going to use the primary database. Okay, going back to the solid, uh, seconds. Let's, uh, go to development, RB and, uh, active job. Uh, do we have a queue adapter set here? No, let's go to production. So here in production, the queue adapter is set to solid queue. Uh, we are not going to need this line for using a separate database anymore. We can just remove it from production. I think it's also set, uh, in the sold queue. Yeah, remove config, sold queue connect tool from production. So we're removing this line. And, uh, yeah, we want to run it sold queue also locally on our, uh, development database, not, uh, in line. So I will say config, active job queue adapter is sold queue because by default it's in line, uh, in development mode. So we're going to run, uh, sold queue in development. And, uh, let's, uh, now, uh, try to trigger a job. Let's, uh, save our changes. Um, so solid, uh, soldier effect with one database. Okay, now let's try to trigger a job. Uh, let's say rails generate job, uh, hello world. Let's open the, the job. Let's say put hello world and let's, uh, run this job rails, uh, console before. Hello, will job perform later. And let's, uh, maybe try in queue the job for, uh, running like one week, uh, from now So we can say hello. Will job, uh, set, uh, what's the syntax? Wait v perform later. Okay, now, how can we see if, uh, we have these jobs? Uh, n queued to check this, the best way is to install mission control, uh, GitHub Mission control. So, uh, it's uh, also a jump from Rails for, uh, having a dashboard of all your jobs. Let's, uh, add this, jump to our jam file next to other, other solid jumps. 'cause it's kind of related to sold queue. Okay, now we need to add the jobs to our roots. And, uh, is there anything else we need to do? Let's start the rail server and see if it works. Okay? We cannot start the server via not, um, address already in use. Okay? I might be already running a rail server, uh, elsewhere. So let's say port 3 0 0 3. Okay, let's go to slash jobs and, uh, it doesn't work because we need to, uh, yeah, maybe try disabling this H two B basic, uh, uh, authentication. Let's, uh, disabled in development. Just have the jobs dashboard available quickly. Okay, so here you see, I have one, uh, scheduled job that is scheduled to run, uh, in seven days. Let's try scheduling another job. So refreshing. You see, I have two schedule jobs and, uh, why are they not trying? I have the rail server running where the job's not trying because I need to start the solid queue. So, uh, there are a couple of ways of starting solid queue. Um, one way would be to, let's open our profile. Oh, I don't have a profile because, uh, I'm not running, uh, like Vin CSS, it wasn't generated, uh, automatically. So let's just start a new process. And, uh, uh, they're going to say Bin Rails. Solid queue start. Okay, so you see, uh, the one job has been processed. If I go back, uh, you see finished jobs, there is one, uh, let's, uh, again, try running one job now without setting wait time, perform later, and still have two jobs that have been performed. So we have sold Q running with re locally. Now if we go to Puma, do Aby, that's something really interesting. You see, we have this, uh, plug and sold queue. Uh, So if we have environment variable sold queue in Puma enabled, then we are going to be able to run sold queue in line without having a separate process. So I don't need to run this command sold queue start. Uh, I can just say something like, if Rails equals development, then we will also run it, uh, uh, in line. Let's, uh, now just at the rail server, I'm not starting the VOCA separately. And, uh, let's have a look at our jobs. So we have two jobs finished, perform later there, three jobs finished. So see, uh, this plugin sold Queue is working with Puma and Works. Fantastic. Okay, so, uh, let's see. We have, uh, mission control and we've got jobs, uh, running. So now we can be sure that, uh, sold Queue is working, uh, uh, locally with our Postgres database. Now, if you are thinking of, uh, improving solid cache, for example, uh, its performance with, uh, Postgres, then you should also read this article and it gives some guides on how, how to set up your Postgres properly. So let's save our changes. I think we have everything they need to go into production, uh, Uh, run jobs. Uh, okay, let's just, uh, create a basic migration of both into our application. Uh, let's, uh, go and say Rails generate, scuff post, uh, title Rails to be migrate, just so that we have some kind of data to play with. Let's set the route to post index, and let's go to local host. So we have posts, we can create posts, okay, so now we have a basic application that we can deploy to production and play with. Okay, we are ready to push to, uh, render. So, uh, where are we going to start? Let's go and add a new, we can start with a new web service, but I would suggest starting with a new, uh, database, it can take some time to generate the database and we'll need the database, uh, URL for our web service. So, uh, let's give it a name. I know, like, uh, uh, render, uh, PG one. Uh, we don't need to set anything here. Uh, latest version, obviously. Um, there's actually a free plan that you can also try running, and it was a nice surprise for me to learn. That Run has some free plans. So if you're just experimenting and starting out, then you can, uh, try with a free plan. Let's go with basic, let, So 15 gigs of storage by default. That's, uh, uh, fine. Again, uh, I can try like five gigs of whatever. You can see the price in here. Let's go with, uh, 15, create database. And here, if you go to Connect, you have this internal database, UL uh, so external. If you're going to, uh, let's say host your web application, one place and your database on render internal, if, uh, you have the web application and the database both on render. So I will copy this and you see the status of the database is creating it. It can take air vial. Let's go and create a new web, uh, service. And here I'm going to connect a web, uh, uh, a GitHub repository. Let's, uh, see, I have this GitHub repository. I will, um, copy the URL. Uh, yes, it's a public GitHub repository. Uh, let's see. Yeah, maybe I have just created this repository so it's not, uh, uh, found yet. Let's click connect again. Um, let's try given access. Okay, so I went to the settings, configuring GitHub, and here I authorized this render eight, uh, repository. Okay, let's take this repository. Oh, yeah, maybe because it was, uh, private, I didn't notice. Okay, so, uh, language here I need to select, uh, Ruby, uh, build command. I will also say, uh, rails. D my grade bundle exec rails, DB my grade. Uh, and start command, uh, can be, it can be this one, it can be bundle exec rails, Server. Uh, again, that's also a free version, uh, for the web application, but we are going to run on the start plan. Good to start. So here I need to add my rails, master key. Uh, do I have one generated? Let's see here. I have a master key. Let's see if, uh, it works. Um, editor VM rails credentials, edit. Yes. So I can access the credentials with this, uh, master key. And I will also need a database, URL. Now, if you go to database, v ML here says that, uh, uh, yes, you can manually say, say all this, but the database CRL is, uh, takes precedence over anything. So we're going to use database URL. And, uh, in the meantime, let's go and have a look at our Postgres and cope with the database URL once again. Okay, so here's the Postgres, URL, uh, advanced. So, uh, the health check part is slash up in New Rails applications. If you go to slash uh, up, you see there's a green screen. So it's an default health check pod that exists in Rails eight, uh, or to deploy. So whenever you have a new change pushed to the main branch in, uh, GitHub, it's going to automatically deploy the latest updates. Uh, and I think they're all good. We can go and deploy the web service. Yeah, one thing I forgot, just one thing. Uh, going back to our Puma settings here, you see we have the salt queue in Puma. So I'm also going to go to environment for arrivals, and I will set the cell queue in PTO through, okay, save, rebuild and deploy. And now let's wait for our application to build and see if it works. Okay, so took couple of minutes and you see going back to the list of services, I see that the, the web application has been deployed. Let's try open. So here is the, uh, generated URL, and you see it's running. Let's try creating the post. Uh, hello. And yeah, the post has been created. Let's try viewing our jobs. Um, so I cannot view the jobs because I, uh, disabled authentication on mission controlled jobs only in development. So I can also do it in production. Again, I don't recommend the disabling this, uh, uh, job authentication in production. It's just for, uh, us to quickly be able to view, uh, and maybe trigger jobs in production. So let's, uh, uh, push the changes. And you see, I just pushed the changes and, uh, we are automatically redeploying. Okay, so now I have redeployed. Let's have a look at our application. So you see the jobs dashboard works. Let's try, uh, extend the render console. So going to Shell. And here we can, uh, now when it loads, we can maybe run Rails Console. Okay, let's say Rails Console. No Rails Console. Let's see if it connects. Okay, let's see. Uh, post count. Uh, let's now trigger a job. Uh, what was the job's name? Hello, world and Job Perform Later. Let's look at our jobs and see. We have one job that has been, uh, performed. It has been performed because we have our jobs, uh, running in the same process as our, uh, web application. So that's amazing. And yes, that is basically it. I deployed a Rails eight application using so trifecta and Postgre scale to production. Now you see, I manually had to create the, the web application. The, uh, database and render has an actually better way to deploy applications via blueprints. So imagine as in like Kamal, Kamal, uh, file, um, deploy via ml. So here has the Deploy ML file for Kamal. And it list a list of things that, uh, you would need to fill to, uh, deploy application to production. And render has a similar file render by ML that you can fill in that would automatically set up the database, the s and everything for your application. So, uh, for example, on, um, Mon Gun, my, uh, boil plate application, I have this random ML file. Let's have a look at it. And, uh, I said that we create the A database in Frankfurt region. On this plan, or on this plan, I set the disc size to then gigabyte by default, I create one web service, uh, the build command, the start command. So you see everything that I manually selected in the web view can be set up in this, uh, render dot v ml file. And, uh, using this render via ML file, I can even, uh, have add one click, deploy, uh, button. So clicking on deploy to render will, uh, open my repository, the blueprint, and uh, I can give it a name. So let's name it Money Gun, uh, branch Main. And there is one key that is required. It is the Rails master key. So, uh, let's have a look. I will get my master key Config master key. I will paste it here, deploy Blueprint. And, uh, going back, let's look at our, a list of our projects. So here has, uh, the database that it's created outta the database. It is going to create the web service, and it's going to connect everything. And, uh, using blueprints, you can, uh, uh, set up everything in code, how you want your application to be running. Maybe you want to have readiness, maybe you want to have, uh, uh, aed, web, uh, service for, uh, good job or anything else. You can set it up all in Render yml. And you don't need to manually click through the, uh, web fuels of, uh, render. So I think this, uh, is a wonderful way to keep track of all the services you need to correctly deploy application to production. And if you're using render, I would actually recommend having this kind of random I ml file for your deploys and, uh, update it as your Apple has new environment variables as it, uh, has new services. And yeah, deploy via render I ml. So yes, that's basically it. This is how you can deploy Rails, modern rails applications with render to production. Now, another thing I usually add to my applications when deploy into production is Glock. So it helps to reduce the member usage of the application to add to render Avenue, to set this LD Preload environment arrival. So going into an environment and this environment for arrival. And I will set it to this. Thanks Mark for the prompt Save and redeploy. And I think, uh, now it's going to work. Now previously to add the Glock to an Rails app that I was using on ku, I would need to set the environment for Glock enabled to through, and I would need to add the add build back of, uh, G Lock like this. And, uh, yeah, nothing I kind of like about Trend is, for example, let me go and create a new, new web service and, uh, you can add add disk. So going into advanced, you can add the add disk, you know, with Heroku, like, uh, if you redeploy everything you've been doing that is not still in the database is, uh, lost. And here you can have some storage, uh, for your ongoing stuff. So that's, uh, nice. And yeah, there's kind of a bunch of, uh, uh, reasons to use Run Over Heroku. Like, uh, there's, uh, means for HDP requests versus 30 seconds on Heroku, uh, render is, uh, cheaper than Heroku, much cheaper than, uh, Heroku if you are scaling a lot. And finally, big shout out and thank you to render.com for sponsoring this video, for giving this motivation to find the best approach to deploy a Rails eight application to production and deliver it to you. --- # #237 Solid Queue, Cache, Cable with Postgres in Production - URL: https://superails.com/posts/237-solid-queue-cache-cable-with-postgres-on-production - Published: 2025-04-13 - Duration: 24:16 - Access: free - Topics: moneygun, deployment, solid (cache, queue, cable) 1. Make Solid Queue, Cache, Cable work with Postgres 2. Add MissionControl to monitor Jobs 3. Deploy to production and troubleshoot PR with code changes: https://github.com/yshmarov/moneygun/pull/220/files ## Transcript Hello. So previously we deployed Mon Gun locally and set up Stripe to work correctly with webhooks so that we can actually subscribe organizations to real, uh, plans. And now we're going to deploy to production. And it is going to be a bit tricky because, uh, now we're using Postgre scale and we have, uh, slow Q, so cache and solid, uh, uh, cable installed by default. And, uh, they are set up initially to work with multiple SQI databases. So we are going to have to make them work with PostGresQ. So let's have a look at our database. I ml here you see we have a Postgre Scale set up the database because we initial generated the application by running Rails new, uh, gon, uh, database equals Postgre scale. So it set up a postgre scale as the main database. But if you go to our production, you see we also have, uh, uh, separate cache database, separate queue database, and a separate cable database. And, uh, we will want to use the same, the primary database for all these three. Now let's see if we have a, uh, sold cache, queue and cable installed correctly. Uh, to check this, we are going to run the install command for each of them. So I will, uh, run, uh, bin slash rails, uh, solid, uh, cable install and see if there is any diff. So let's go to, uh, gi. And you see there is a diff it added the cable schema. Let's also run the same command for solid, uh, uh, uh, queue, uh, run for solid queue cable and run for solid cache. And you see it added the three schemas. So the idea is that, uh, rails wants to create multiple databases, uh, the primary database and three databases for cable cache and q. And we don't want to have the three separate database. We want to have these tables all in the main, uh, schema in the same, uh, main database. So we are going to, uh, add this as migrations to our main database. I will run a command rails, uh, generate migration, uh, add solid, uh, cache, Queue and cable. Uh, now we have three empty, uh, migrations will go to DB migrate. So we have this add solid cache and they're going to copy the, uh, info from cache schema. They're going to copy this great table and um, add it here. So it's the solid cache fund. Uh, then we'll do the solid, uh, queue the of tables and indexes here. So adding solid queue and finally solid cable. Okay, now I'm going to delete the other three, uh, schemas. We will not need them. I can draw rails to B migrate and in database via ml. Here. I'm going to remove these three separate, uh, uh, database definitions for production. And here in primary, uh, I'm going to use the, just the primary database. We have primary setup here and, uh, I think in queue here. Uh, yeah, we're just using default in, um, Whether we have the cable. So here we will say that the writing is for database, uh, primary, but also I think in theory you can just command this out, uh, and it'll use primary default. And for cache, we are also going to say that the database is going to be not the cache database that we just removed from database via ml, but the primary database. Okay, uh, what's next? So we kind of set it up to use, uh, the main postgre scale database. Now, let's, uh, set up solid queue to work, uh, locally. So if go to production, uh, solid. So cto, solid C store, uh, Q adapter is solid queue and connects to database right in queue. We need to command this out. They're just, uh, going to use the main database. We can just comment it out. And, uh, we have the queue adapter, solid queue. Let's see what we have in development. Queue adapter. Um, it's not set at all 'cause it is in line by default. So the default value is inline. We don't have to actually state it, but we're not going to use inline. We're going to use, uh, sold queue as the, uh, queue adapter. Uh, now let's, uh, try adding a job and see if, uh, we can trigger the job. But, uh, before adding a job, let's see, let's add the, uh, dashboard to see jobs. Let's add the, I think it's called, uh, what's the name of the tool? Let me see. Mm. Mission control. Yes. Mission control jobs. A dashboard to see all our jobs for solid queue. So let's add the Gem Mission control jobs to our gem file. Let's add it next to the other solid gems 'cause it's, uh, directly related. Uh, now we'll run bundle And uh, we are going to mount the jobs, but we'll make it so that only admin, uh, user can see them. So we need an authenticated admin and they'll also have to disable the default basic HTP uh, authentication. So I will go to application rb and I'm going to disable this basic auth authentication because we already have this, uh, user authentication here for the jobs. So let's, uh, uh, run the server bin Dev. Yeah, I think we already have it running. I'll just restart it because we added a lot of changes in environments and we'll go to slash uh, jobs. It is not present 'cause the current user is not an admin. So let's make an an admin rail. C user first. Update Admin. True. We'll refer this page and you see we have access to the jobs. Okay, so we have installed mission control. Now let's add a job. Um, let's create a job named the Hello World. So Rails generate job. Hello world. Uh, let's open the job. Let's say puts Hello World and let's, uh, uh, schedule this Job Rails console. Uh, hello, job, the job perform later. Let's go to our jobs dashboard and see we have one Penant job that, uh, needs to be performed and it hasn't been performed yet. Uh, why? Because we don't have, uh, so queue running. So we need to run, uh, solid queue. Let's see how to do it. Uh, there is it in the docks, maybe have it noted somewhere. Let's see. Ah, it should be stored. The command is, uh, sold to start. I think finally I can find it in the box. Let's try manually running, uh, rails. Solid queue start. We'll do anything. Yes, you see it, uh, pinned. Hello world. And if we go back to our dashboard, you see there are no pending jobs. We have, uh, one unfinished jobs. Hello will job. So, uh, we managed to start solid queue and, uh, it processed the job. We can move this to our profile Profile dev. So I will say Worker is run by sold q start Now. We don't need this terminal anymore. I will restart the main pin dev to also include the worker and the let me schedule another job performed later. You see we have two jobs that have been performed. Uh, it has been performed instantly, instantly. So it's not in the queue anymore. Let's schedule a job for later. Let's, uh, not perform later, but let's say, uh, set wait one week dot perform later. Now we'll go here. You see, we have, uh, one job that is scheduled to run in seven days. So, okay, salt Queue is running locally, but Salt Queue has another really great feature. It can plug it in directly into Puma. So if you open Puma rb, you have this, uh, environment Salt Queue in Puma. So if you enable it, then salt queue will run in the main, uh, process. And, uh, you don't need to create a separate, uh, worker for it. So I will, uh, uh, it, I will, uh, uh, disable the worker or remove the worker from the, uh, profile. Let's try removing it. And, uh, instead I will, uh, go to Puma and, uh, run. So queue if Rails environment is development. So again, I will start the rails, uh, server, just the rail server. I don't need to start the pro profile. I can even add this back to the pro profile. I'm just running the rail server and, uh, let me run another job Rails console. So hello Rail job perform later. Now we have two jobs that I finished. I will add this job and we have three jobs that I finished. So you see, I don't need to even, uh, add the, I can just, uh, say that, uh, I will run sold queue inside Puma in the development environment and in production. I would need to, uh, enable this environment. Sold queue in Puma equals through. So, uh, yes, it looks like we have, um, everything running locally. We have, uh, mission control installed. Have, uh, sold queue that focus locally inside the main web process. Now we need to deploy to production and we're going to deploy to production in this case to, uh, render. Uh, I like render. I am actually hosting cpr.com on, uh, render. And it also has, uh, accessibility to host a, uh, database and, uh, a web service for free. Nowadays, not many, uh, platforms as a service have this free option and render does have a free option. So we're going to try to host, uh, uh, money Gun for free. Uh, let's go and create a new, uh, web service. I will use, uh, Monon. You can also define the public GI repository. You can just go to, yeah, we slash monon. I'm going to paste it, uh, right here. Connect. Uh, now the language is going to be Ruby. Uh, the branch is not going to be main, it's going to be the branch where I did all the changes. So I will create a new branch, let's say, um, solid production render. Let's, uh, save all these changes. So we added the job. We removed HP basic authentication from mission control. Uh, set the primary database to be used in the sold cable sold cache. Uh, added our API keys for Stripe in the previous episode. Uh, removed the settings from, uh, the production database v ml to use the main database. Uh, in development. We're not using inline, we're using solid queue in production. We are also using solid queue and we're using a separate database. Uh, we are using, uh, inline, uh, solid queue in Puma in development environment. Uh, again, we are using the jobs, uh, dashboard. We previously set up the stripe prices. We added the migrations to the main database for solid. And, uh, yes, that's it. So let's save and let's go back here. Now it doesn't have this, uh, brunch that I've been playing with, so I will need to, uh, restart because, well, this branch wasn't present. Okay, let's see. The brunch, uh, what's the name of the brunch? Solid production. Oh yeah, the brunch isn't there yet because I just, just, uh, created it. Okay, so I, uh, refreshed and now have the solar potass around the branch. Uh, I'm going to use the language. Uh, Ruby, it is important. Now, rule directory doesn't matter. Bundle install here in end, I'm going to say bundle exec rails, DB migrate, run the migrations in the end. Um, here I can just, uh, say bundle, execute the rail server. I don't think I need all this stuff, but let's try going with it. Let's like the free option for now and we need to set up the Rails master key. So I will add the master key that we set up here. Then I will add the, the environment variable sold queue in Puma to True. And we'll also need to set up the database and we, the database, your Alpha this. And we need to separately create a database in render. So in the meantime, let's try to, uh, deploy the web service. I think it'll fail. Yeah. Here I will also set the health check part to slash op. Let's click on Deploy, but it'll fail because it don't have a database, uh, connected. In the meantime, I will go and create a new database. So new Postgres database, um, Monken Postgres. Uh, we don't need to set anything up, uh, except for this. Let's use a free database for now. Oh, okay. Create database. And, uh, when the database is created, we should be able to get an, uh, internal database. CRL here. Yeah, we already have this CRL available, but it'll not work yet because the database is still, uh, in the creation phase. We can, in the meantime, go and, uh, add this database, URL to the Money Gun Web Service, going into environment variables. Here I will go edit, uh, new variable database, URL. Um, let's just save for now without redeploying because, uh, it doesn't make sense until the database is, uh, fully created, legislate a bit. Okay, so the Postgre Scale database has been, uh, created and we have a failed deploy on the main repository. Let's just have look why, uh, yes, because, uh, we don't have PostgreSQL set up. So now we have it, uh, connect. We have the database URL set up, and now we can redeploy. So I will clear build, cache and deploy, and let's see if it works this dime. Okay, so now it says build successful. Let's go back to the dashboard. And you see, uh, Postgres is okay, I will refresh and, uh, money Gun seems to be running. Let's try to open it. And yes, it, uh, loads. Now let's try signing in or create a new account. Let's try to create an organization and let's try to subscribe. So get started. I think, uh, they're going to have a problem because we need to set up web hooks to work with the, the new created production URL with Money Gun on gender.com. So anyway, let's try going through. Let's, uh, pay. In the meantime, we can have a look at our dashboard. So this is the new created customer that we already created from production, not from our development environment. And, uh, let's see if the payment gets processed. For some reason, Stripe is, uh, running quite slow for me today. Okay. It's been processed, so I'm redirected back and, uh, going here, I will go to the customer here. He's subscribed. He has an active subscription, but, uh, here I don't see him having an active subscription. But if I go here, uh, to reinitialize the subscription, it'll make an API request to Stripe to check if he's subscribed and it'll say that he's subscribed. And, uh, I think we will redirect back showing that we have an active subscription. You see it as a free instance, so it might be the reason why it's running. So, uh, slow. Yeah, you see now we have an active subscription. So now let's set up Stripe Webhooks to deliver to the production. Uh, URLI will go to developers, uh, webhooks. And, uh, here are previous levels already setting up some kind of URL money gun on render.com. I think it's going to be more or less the same. URL mangan on render do com, pay Webhook, Stripe. And I will just need to use, uh, this, uh, webhook key, uh, and add it to my, uh, credentials. So I will go with this and I will add this, uh, webhook sign key to my credentials. So, uh, I need to add this, uh, webhook secret. This is the one that I was using for development, and this is the one for my production environment. But again, I'm using the, that test keys for both production and development at the moment. Let's, uh, save. Now actually, if you open the webhook endpoint, you will see I'm listening to 19 different events. And these are all the events that we need to listen to according to the page, GM documentation, and then trading and new webhook. You don't need to add them all manually. Like if I go here, click on add endpoint, I would be, uh, suggested to add all these events manually. Uh, instead there is better thing that they can do. If you go to the money gun repository and, uh, find the webhooks here to enable web hooks, you can click here for that development environment or here for production environment. So I will click here and, uh, it has, uh, all the web hooks encoded in the UL brands, so you don't need to do it manually. Here you see all of the 19 endpoints have been, uh, added, and all you need to do is add the production URL. So I'll go to dashboard render.com. What is the production? URL? Right now I have redeployed the application. So here it is. And, uh, I need to have slash pay. Uh, what was it? Let's see. In the docs, read me, uh, pay a web Hooks, stripe, Okay, at Endpoint. Now you can copy the silent secret, add it to your credentials, save. And, uh, I'm going to push these changes. And now whenever I'm pushing changes, uh, uh, Randy is going to redeploy my application. So, uh, let's have a look here. You see, it is deployed once again because, uh, I made some code changes if I go to events, you see, uh, just now when I pushed the latest change with the credentials, it is redeploying the application. Okay? So now we redeploy the application with the, the added web hook key. And now let's try, uh, seeing if the webhooks are delivered. So I will create a new organization. Again, I'm on the free plan, so it can, uh, take some time. Now going to billing, uh, subscribe. And the expectation when I subscribe is that the web hook will be delivered and the, the user will be subscribed right away. Here. Redirects back to the application. Yeah, processing the payment. Stripe is running all its magic. We can already maybe go back to our customers and see there is this new other customer. Let's have a look at our web hooks. So you see previously we had a bunch of web hooks that were not delivered. And, uh, the arrive now, okay, yeah, I'm back in this, our organization. And, uh, you see the subscription is active. So I was just iterate back and the subscription is active. And if I refresh the web hooks dashboard, You see, uh, all the web hooks that we have sent after and the web hook key have been delivered. Now, yes, we have, uh, money gone running on agenda. Everything is good. The only thing that is, uh, imperfect is that we are using the same Stripe, API keys for, uh, development. And for production we are using the test keys. And you would want to use the test keys for like, the test mode for development, maybe for station, but for production, you would want to use the production keys. And here we have everything set up in this credentials file just for, uh, one Stripe environment. So you would be able to add the, either like a separate, uh, folder for production and the development here, or you can, uh, go with the Rails credentials approach to have separate credentials for development. For test, for production, something like this. We can run real credentials, edit environment development Here we would be able to add our development keys. We would create a separate, uh, one for production, add production keys here, and uh, it would create the folders, so the folder for credentials, development and production. And you would just add the master key for production or master key for development, uh, in your environment. And this way you would be able to have different stripe keys for development and production. Okay, so that's it. This was quite a lot of work. We set up solid TriFactor to work with PostgreSQL, and we deployed the application to production. Thanks for watching and see you in the next. --- # #236 Run Moneygun SaaS locally. Configure Stripe Plans and Webhooks - URL: https://superails.com/posts/236-run-moneygun-saas-locally-configure-stripe-plans-and-webhooks - Published: 2025-04-13 - Duration: 09:06 - Access: free - Topics: moneygun Build your next B2B SaaS app (software as a service) with Moneygun: https://github.com/yshmarov/moneygun It's the EASIEST way to add Teams and Subscriptions. Focus on Your unique app features, not stupid boilerplate. ## Transcript Hello friends. In this episode, they're going to set up, uh, mono Gun, the Rails eight software as a service power plate application to run locally. So here is the public GitHub repository. I'm going to clone it locally. I already have it running in my local GitHub. I'm going to open it in, uh, my code editor. I will run, uh, bundle. It's going to bundle all the gems. Now I can say Rails to be, uh, crate. I have already created the database. So I'll run Rails to be migrate and I can run Rails to be seed. Or if I have already privileged run seed, I've already run them with the same seed replant. And if I look at my seeds, I already have an example user that belongs to a few different organizations. So let's run the server bin slash dev and let's log in as this user here, I have a basic homepage. Let's go to log in, uh, signing in. And you see I'm part of a few different organizations. I can open one of the organizations. It has an empty dashboard. It has, uh, empty projects. I can switch within the organizations. I can add the users to an organization, or I can change the organization settings And see, I don't have billing set up yet, so I don't have like possibility to subscribe an organization to pay the plan because I don't not yet set up Stripe. So let's set up Stripe to work with these organizations with this, uh, application. So here I have a new Stripe account and I'm going to set it up. I will go to developers here. I have API Keys and I will need to add them to my application. So let's open the credentials. Now. Uh, I will, uh, open the credentials. Let's, let's say in, uh, VIM editor, um, editor Vim Rails credentials edit. Uh, you see it, uh, tried to open the credentials, but we didn't have Master key present because they just blown the repository. So it generated a new master key, but it, uh, the new master key doesn't match the existing credentials, so it cannot decrypt. So I will need to delete the existing master key, delete the existing credentials. And again, I will open the rails credentials. You see it opened. So I have the default credentials with some empty stripe key data generated from credentials by mail. From this template file, I have, uh, an example credential. Now, um, if you are not a fan of Vim, maybe you would want to add the credentials in this code or cursor. I will with the credentials here, and I can, uh, export, uh, cursor and open the credentials in Cursor. So you see now it's going to be a bit, uh, easier for me to add the, the credentials. I will go copy with the publishable key, uh, copy with the secret key, uh, save and exit. And now let's try, uh, seeing if, uh, they can deliver web hooks. So I will go to add an endpoint test locally. Uh, log into Stripe. I have already previously installed the Stripe CLI. Okay, open allow access to the, uh, workspace. Okay, uh, I have, uh, connected my Stripe, CLI. Now let's start delivering in sound up hooks, as it says here. And to add the Stripe listener. So they're not going to listen to this endpoint, but they're going to listen to the one from, uh, profile dev, local host 3000 slash pay webhooks Stripe. It is where we deliver our webhooks. So I'm going to listen to this in another terminal. I will run the rail server and in another one I will, uh, try to deliver a hook. So I'm going to cope with this trigger a payment then succeeded. And let's see. So in our Ruby process, we get some kind of, uh, uh, error, uh, No signatures found mentioned the expected signature for payload. And in Stripe we get four, four or 400 errors. So the web hook was not delivered. Why it? So, because when the setting up the credentials, we did not add add a, a correct web hook key. So let's, uh, go back and add a web hook key. Again, I will, um, export cursor. I will run rails credentials. And here I need to add the sign-in secret. So here from the example, I need to add this, uh, specific sign-in secret. Let's add it. And uh, now save exit. I will, uh, restart the rails server. 'cause I've updated the credentials. I don't need to restart the stripe process. I think let's, uh, try triggering an event again. Okay, so you see something that's happening in the successful in the Ruby process and in Stripe, you see there was a successful, uh, response. So we have set up, uh, web hooks and stripe to work well locally. Now let's, uh, go and have a look at the application. I will, uh, refresh it. Okay, I need to sign in once again. Okay. And you see now I have the billing module set up and I have some kind of, uh, default prices. Now, where does this come from? 99 per month. 999 per year. If in Stripe we did not set up neither any customers, nor did they set up any products, they'll need to set them up. And the default, uh, values come from our settings, v ml file settings, v ml. It is where we have the stripe prices and the, the display values defined. So let's create some stripe prices. I will create a product, let's say Mangan Pull Pro. And we are going to add a couple of products. So, uh, prices. So there'll be a month plan and a yellow plan. And the price will be in Euros, for example. So let's make it 50 euros, uh, per month. And we'll add one more price within this product. That will be, uh, Euro 500 per year. So kind of two months free, uh, billing period is going to be yearly. Okay? So we have a product and it has, uh, two prices. And now I will go to one, to the smaller price of this. And, uh, I will set upsells. So upsells to the yellow plan. And now I need to add this, uh, updated price data to our prices in our settings. So first of all, we are going to have, uh, the price be not 99, uh, a hundred, so not $99 per uh, month, but 50 euros per month. So 50 euros, zero 0 cents. And here 500 euros, zero 0 cents. And the same here and here. And we need to set up the newly credit price IDs. So, uh, we are looking at the 50 per month price. I will copy it here. And, uh, for now, we can just top the same price ideas for production and development. So, um, investing up the idea of the monthly price. And now let's have a look at the yearly price. We'll go the, ID paste it here and here. Now if, go back here and refresh. You see we already have 50 euros per month and 500 euros per year. Let's try, uh, subscribing this organization. I'm going to subscribe the bus sprout organization to a monthly plan. Okay? I will, uh, pay with card, uh, pay and subscribe. And, uh, I expect, uh, you see web hooks are coming in. I expect us to redirect back to the organization and have it subscribed. So that subscription status should be green. And here we are. You see it says subscription active Next, pulling data. So it, uh, works. I can have a look at all my organizations and you see that the status of the bus product organization is, uh, green. So it works. Okay. So I've set up, uh, Stripe to work, uh, locally. Now, to make it work in production, you would want to have, uh, different stripe, uh, API keys for development mode and production. And you would maybe want to have, uh, uh, AWS uh, three storage for images like, uh, uh, editors of organizations here. See, you can change the logo of an organization. So, yes, that's it. See in the next one. And they're going to talk about how to deploy mon gun to production with post posts scale, and with the solid trifecta. --- # #235 How to hide IDs from page URLs in Ruby on Rails. The smartest approach! - URL: https://superails.com/posts/235-how-to-hide-ids-from-page-urls-in-ruby-on-rails-the-smartest-approach - Published: 2025-03-29 - Duration: 05:49 - Access: free You definitely want to hide **IDs** from your app. It's a very basic security consideration. There are many Ruby gems that let you hide ids, but neither gem is plug & play: Some gems require adding a slug attribute to each model. Some gems require updating your finder methods. I don't want to "decorate" my **internal** resource slugs. I don't want to care about UUIDs or ULIDs. I just want to say "hide my IDs" and it just works. The best plug & play solution that I found is in Bullettrain source code: http://github.com/bullet-train-co/bullet_train-core Here's how you can hide ids in your Rails app Pull Request adding ID obfuscation: https://github.com/yshmarov/moneygun/pull/209 ## Transcript Hello, friends. So here I have a basic application and let me create another organization. Let's name it a fourth organization, create organization. And you see, I see the idea of this new organization. And this is, uh, considered not a good security practice to have your ID visible in the URLs or if you like, uh, hover on, uh, a link. You say, go to inspect. And I see the idea of this resource because, uh, a competitor can, uh, know how many organizations I have in total. And you can check a month from now and see what is the growth rate of my application. So, uh, this is the most simple example why I would want to hide ideas from, uh, your URLs and your markup. Now, uh, there are a few different ways to hide ideas. Uh, the most simple way is, uh, to use friendly id. I love this gym. I use it in many of my applications. But, uh, I don't want to add the, uh, slug attribute to all my models and, uh, update them based on the name, based on another attribute of the model. Then there is SL Fire Bill by Ramis, but the gem also, uh, requires adding a slug migration to all the models, and I don't want this. Then there's this hash I rails gem, but it hasn't been updated in five years. And this gem, uh, does not, uh, require you to, uh, add, uh, additional SL attribute to the model. Uh, but, uh, the finders, uh, don't work outta the box. So you need to update the finders, uh, all around the application to use hash, uh, this find by hash id. Then there is this Id jam that is very nice, but, uh, again, it does not, uh, uh, support, uh, the default rails find method. So you would need to use, uh, article, uh, uh, find the, instead of article do find all around your application. And I want to just, uh, drop in solution that works, uh, out the box when you just add it to the application and you can, uh, obfuscate all the ideas around the application. Then there is this prefixed ideas jam by, uh, uh, excite and, uh, it's an G. But, uh, the ideas that are generated can get, uh, really long and also not all find the methods, uh, work, uh, out the box. So you would need to use, uh, find by prefix ID in some places instead of just using, uh, find and, uh, while exploring bullet train. Uh, it's an, uh, open source, uh, boilerplate. I notice their id. You see, uh, I'm like, uh, navigating different resources. And here is, uh, an example of an obfuscated, uh, uh, hashtag ID of the restore. So if I like hover anywhere, you see, uh, I will have this, uh, obfuscated ID and not the real ID of the record. And looking into the source code of, uh, bull chain, that's actually open source, I highly recommend you to, uh, have a look at it. Uh, I found that this obfuscated IDs in bull chain are defined, so they have this concern, obfuscated the id. Uh, let's try adding this, uh, concern to our application and see if, uh, it can be a drop in solution to obfuscate, to hide our ideas in our application. All, uh, advanced with minimal installation. So, uh, I will go to my, uh, models. Mm. And, uh, in models inside concerns. I will create this, uh, uh, obfuscates id, uh, file. I will, uh, copy it from, uh, bullet chain. I will also add a source like attribution where I took it, um, extract it from, uh, uh, bullet chain. Okay, we have this obfuscates id. So, uh, it, uh, will need the hash id. We will also need to install this jam. Now you see it over as the find there has many finders, uh, and we'll be able to use, uh, uh, to pre to see the hash, the id, the obfuscated ID of the record. So let's, uh, uh, study rails console and, uh, let's say organization First and organization 0.2 per, uh, now this, uh, just says the ID 12, but, uh, we want to use obfuscated id. So we would need to add this obfuscated ID to our application record. So here I will say, include, uh, obfuscates id. Now I will restart the rails console. Uh, define the organization, organization to parm. I have this error. Hash ID is new, so I need to, uh, add, uh, hash IDs. I will add the gem to the gem file, gem, uh, hash IDs bundle bin. Uh, yeah, no need to run dev. Let's first try in the console, uh, defining the organization. Organization do two parm. And you see, instead of the ID previously we had the id. Here we have this, uh, hashed id. So if I start the server, Let's go to our application. I refresh, uh, search by Id still works. Uh, but if I go to an organization, you see, I don't have the ID here anymore. I have the, uh, obfuscated, the hidden id. If I go to projects, I go to show a project. You see, the organization ID is hidden and the project ID is hidden. And it is kind of a shop in solution already is, uh, uh, used the obfuscated ID concern from bullet train and, uh, in initialize it in the application record. And I needed the gem hash IDs. So yes, that's basically it. This is the, I think, easiest, uh, most lean way. You can hide the ID of all the records in your application with the just one drop in tiny solution. Thanks for watching. --- # #234 Vibe coding basics. Adding Dark mode to existing app with only prompts?! - URL: https://superails.com/posts/234-vibe-coding-basics-adding-dark-mode-to-existing-app-with-only-prompts - Published: 2025-03-29 - Duration: 07:59 - Access: free - Topics: AI productivity, moneygun These days I wouldn't hire someone that does not use any AI in their daily coding. Stubborn? Stuck in the past? Here's a very basic introduction to "vibe coding" with Rails and Cursor. - As a product designer, write what you want to build in the chat. The better your prompt is, the fewer iterations you will need. - "Vibe": Accept all changes & hope for the best. If you don't like the result, iterate in the chat. You are now a prompt engineer, not a dev. Try Moneygun, an open source minimalistic Rails 8 SaaS Multitenancy boilerplate. It's the fastest way to launch a B2B SaaS with Ruby on Rails: https://github.com/yshmarov/moneygun ## Transcript Ah, for the viable weather for Vibe Coding. Hello, friends. Now, if you don't yet use AI in your code editor, I really recommend that you should. It'll definitely be much more productive. Previously, I used to use GitHub copilot first, uh, the Autocomplete feature than when it be the chat feature. And now I'll move to Cursor. I paid for the subscription and I've never been happier. So in this episode, we're going to try to use, uh, cursor and do some vibe coding to add a feature to an existing application. Specifically here have this, uh, money Gun Material Multitenancy boiler plate. Uh, you can, uh, see it here on GitHub and they're going to try to add dark mode with only adding prompts and not writing any actual code ourselves. So first of all, I have installed Cursor locally on my computer. I have, uh, pulled my, uh, repository and, uh, I have it open now, just double check. I will go to the cursor settings. So settings, cursor settings, uh, features. And as I've just, uh, imported this repository, I will double check that it has been indexed. So, going to the features, um, here, you see the code base has been indexed, the still syn in. Let's re-index it. Okay. It has been indexed. Uh, now I'm going to have a look. So, okay, I, oh, close it. Let's open it on scan. Um, okay, so here is my, uh, application. Let's, uh, run the server bin slash dev. And you see at the moment, it just has a light mode, no dark mode. If I open appearance, um, I see light mode. And dark mode is usually different in the browser, but the same inside my application. And, uh, many people, especially me in the evenings, I really prefer looking at dark mode and not having a very, very bright screen. So, uh, let's try adding the dark mode. Uh, first of all, I'm going to open a new chat. Now, uh, you can open it, uh, by open the command line and search for new chat, or you can use the command shift, uh, uh, uh, search and uh, search for new chat. And here I'm, uh, going to add some context. I'm going to use the agent, uh, model, not ask, not edit agent is the one that is going to itself. Add all the changes in the files. And um, I can use the model that is auto. But, uh, uh, each model can provide further results. So you can experiment with all of them. Now I'm going to do some front changes. So let's go to application html. Application HTML here has application HTML. And uh, I'm adding this file to the context. I will also add the, uh, sidebar navbar footer. Um, I have some more sidebar related files. Okay. And, uh, uh, this CSS file. So application, uh, Devin, c, s, s, and, uh, uh, the better the prompt you give, uh, the more context you give, the better the result is, uh, likely to be and the fewer iterations you'll need to make. So I'll make a prompt, uh, add the dark mode to this application. Um, take inspiration from linear. Um, the changes should be minimal. Um, use only in line television CS uh, update the buttons. So I have some buttons defined in application CSS here. Then, uh, I can say that I'm using TNT four. And TNT four is configured in application CSS, not application tel js. Um, and I can also say that I don't want to add a toggle button to toggle light and dark mode. I want to do it only based, uh, on the, uh, browser's preferred color scheme. Uh, okay, so here's more or less a prompt. Let's, uh, send, and let's give it some time to generate a result. So it's going to have a look at the all the files. It's going to edit the files, uh, one by one. And, uh, in the meantime, while it is generated the code, you can already start reviewing the changes. So here you see it's trying to add dock mode by default in the whole application. So if you set dark in htm me, then it's going to use exclusively dark mode. This is actually a change I would, uh, reject 'cause I want to use the, uh, settings set here. Then, uh, here you see it sets a default background white. And if the dark is background gray, it shows background gray, uh, on its own. Let's, uh, accept the file for now. Go to the next file. Uh, so it's updated in the N bar and so on. You see, uh, the idea kind of with vibe coding is that, uh, you don't, uh, you don't even have to review the changes. Uh, you kind of trust, uh, the chat and you click around application and, uh, uh, you tell the chat what you don't like. And it would be, uh, just updating everything based on the what you like and what you don't like. So I'm not really going to, uh, review each of the files. I will just click on accept. All right. Here, let's, uh, just refresh that page and see if it works. Okay, uh, going to doc mode. And you see part of the application has been set to dock mode. If I toggle the sidebar, you see the other sidebar has been, uh, made to work in the dock mode. Just one of them, not the other sidebar, because I have different sidebar layouts here. So I will say I add the dock mode two, uh, vault that files side by the two columns. I will jug the name of the file here and, uh, also the navigation links. Yeah, they look fine here. Uh, yeah, some of them are style differently, but that's fine. Let's, uh, updated. So see, I'm not even, uh, kind of activating my brain and thinking about the code. I'm working more as a project, uh, as a product designer telling what the specifications I want, saying what I like and what I don't like. And code kind of just generates itself. So let's, uh, see the result. And yes, it looks, uh, not that bad. There are still some tweak that we can make, uh, like, uh, the backgrounds, uh, the backgrounds of the buttons here, but it's already a wonderful leap forward. And looking at the GI changes, you see there are not so many changes that have been, uh, applied and uh, it was quite fast to add dark mode. So I encourage you to try, uh, this approach to adding features to your application before doing something yourself. You can try to let the AI do it and then review the I AI changes and work incrementally on top of it. --- # #233 Build your next B2B SaaS with Moneygun SaaS Multitenancy boilerplate! - URL: https://superails.com/posts/233-build-your-next-b2b-saas-with-moneygun-saas-multitenancy-boilerplate - Published: 2025-03-24 - Duration: 16:56 - Access: free - Topics: moneygun Previously Moneygun was just an example app of how to implement secure route-based Multitenancy without any gems. Now I've added Subscriptions on Organization level. Meaning, it's now a legit template for building B2B SaaS apps. B2B app - app where a team of users with different roles have access to shared resources. Feel free to use this as a template for your next app, or just as inspiration! PR adding subscriptions: https://github.com/yshmarov/moneygun/pull/197 ## Transcript Hello friends, it's been quite a while, but I have some nice news. So remember previously I made a video about Multitenancy, and I also created a basic multitenancy boilerplate named Monon, and made a couple of videos about, uh, how it, uh, works and now have improved monon. So it is now not just a multitenancy boiler plate, but it is a software as a service multitenancy boiler plate, meaning that it accepts subscriptions. So here's the actual pull request where I added the g pay to make money on be not just more 10, but more 10 subscriptions. Lemme demonstrate what this means. So at the moment, let's try to create a new account, just the device sign in form, and I can create an organization. Let's create an organization. Let's name it super. Let's add a logo. Okay? So I've created the organization and there's the dashboard. There's like a project page where this will be the models within the organization. And now we have this pay vault page, and I cannot access the pay vault page because I need to be subscribed to access this, uh, uh, controller or controller action. So here is the pricing page that is, uh, visible in the billing. And, uh, let's hook up, uh, payments at the moment. If I click on Get Started, uh, you see there's no price and, uh, I need to create the prices and connect my stripe. API. So here I have a new Stripe account. I'm going to develop a mode, and, uh, I'm going to, uh, see my API keys. So, uh, I will go and, uh, add my API keys, I will say rails, credentials, edit, environment development. Uh, I need to say, uh, editor. So I will say, uh, editor equals Vim. Okay, here I can access and edit my credentials. And you see, by default, I already have Stripe credentials set. Now, there's also a template, so credentials, VML t that shows which credentials you need to run the application. Here I have already added the stripe, uh, uh, private key. So this one, the public key, this one, and, uh, web hook secret. Now, uh, development doesn't matter much, but I took it from going to Web hooks add endpoint. And here you have this example, endpoint secret. So I added it, uh, here. Okay, let's click. So I've got the credentials, and now I need to ensure that I have, uh, the plans available in my application. Now, uh, where do these, uh, plans come from? They actually come from, let's open our, uh, pricing. Here it is, it comes from, uh, okay, let's add down using tabs. It comes from Rails application config for settings. So I will go to settings, and here I, uh, inputted the plans. So the price id, they amount the currency, here's the currency, uh, the interval, and most importantly the price. Uh, id. So I have two prices a monthly and the early plan. Let's create this kind of prices in our Stripe dashboard. So here I'm in the test mode. I will go to product catalog. I will, uh, say pro subscription, And, uh, I will create a recurrent price. Let it be in the USD, let's make it, uh, $99 per month. Add the product. Uh, and I will create one more price within this product. So, uh, it'll be a yellow price. Let's, uh, add recurring USD 999, and let's make it, uh, yearly. Okay, create price. Now I have two prices and I can update, uh, my setting for these prices. Uh, so we have this 99. I will cope with the price id. Uh, it is $99, so I need to also have the cents here. The currency is, uh, USD for both. And I will go with the second, uh, price for the yearly. Let's open it. Here's the price id, I will paste it here. Okay, so I have updated the plans that I'm going to use locally. And, uh, let's refer this page. I'm going to click on get started on the monthly plan. And you see I have this checkout page. Now, uh, I want to be able to upgrade from the monthly plan to the yellow plan directly on the checkout page. So to do this, I'm going to go to, uh, the product, uh, the monthly plan, and here we can add an upsell. So the monthly plan, upsells to year plan, that's update it. And now if I go, uh, back and, uh, initiate a new checkout session, You see, uh, it offers to, uh, upsell automatically in the checkout, uh, from monthly to yearly. I can add promotional codes. Uh, tax ion is included by default. Also does this, uh, I'm positioned as a business check that, uh, lets business customers to subscribe to it, for example. Okay, let's try subscribing to the yellow plan. Now, the email here is the email of the owner of an organization, okay? Pay and subscribe and, uh, each send the, uh, VA hook to the organization model and update its subscription to be active. You see paper hooks are happening, okay? You see I have, uh, now subscribed. So subscription is active, he has the price, he has the next billing date. Looks wonderful. Now I can click on this pay vault page. And you see, uh, congrats. If you see this, you are subscribed. So wonderful there. Subscription, uh, works. Uh, there are a few different subscription states. So let's try to unsubscribe now. Okay, inval request error. Um, I need to configure the billing portal. Okay, let's configure the billing portal in that test mode. Um, no safe changes. Let's, uh, try once again. Yeah. So I just needed to like, uh, initialize this billing portal, and now you see I can, uh, manage my, uh, uh, subscriptions. So let's, uh, say I want to cancel the subscription, and, uh, I think it's going to cancel, uh, at the period end by default. So here, you see, we can allow customers to switch plans. Um, Yeah, you see there are many second things that the customers can, uh, have or not have in the billing portal. This is, uh, really cool. So you don't have to manage all this, uh, in your app. You, let's try to manage it for you. Anyway, let's cancel the subscription. Let's go back to the application. And you see, so the subscription is canceled, but you will lose access still in a year from now because, uh, you have already paid for a year and you'll be able to, uh, still use the application for a year. Just the subscription will not be renewed at the end of that subscription. And you see the status batch has been updated to kind of orange. So, uh, standby. Okay, uh, let's go to our pay page. And you see now, uh, I can still access it because, uh, this subscription is active. It's just not going to be renewed. Let's go and create another organization. So, uh, uh, let's name it avo. I'll add, uh, logo. Okay, so here I have two organizations. If I have a look at both organizations, you see this one is, uh, uh, unsubscribed but active. This one is, uh, uh, not subscribed at all, and this organization doesn't have access to the, uh, pay page, so it directs to the pricing page. Um, let's have a look, uh, a bit deeper at how the pay, uh, rails GEM is, uh, installed. So, uh, first of all, this is a multitenancy organization, uh, application. And the, the subscriber is, uh, not just a user, it is the organization that the user belongs to. So the subscriptions are on organization level, not user level. Let's go to our organization, rb. And uh, here we will have, uh, pay customer. So the pay customer is not a user, it is their organization. And, uh, in this application, we allow just Stripe and, uh, there are a couple of Stripe attributes that we, uh, send. It's their pay customer ID and user id. Um, let's, uh, yeah, here we could actually have organization id. This would be better. Okay, if we go to our Stride dashboard, let's have a look at the customers. So here's the customer. Here we have the pay customer id and the, well, this should be the organization id. I have just updated it so we know which, uh, records this, uh, Stripe customer references in our application. We can go and have a look at the pay customer. Let's run the rail console, Uh, pay customer. There are two. All you see there is, uh, a pay customer on an organization level. Okay? So, uh, the pay customer is the, on the organization level. And then we have this, uh, subscriptions, uh, controller or the billing as we say it here. So let's open it. Let's open subscriptions. Uh, controller it, the route are defined. The here in the application. So there are just three routes. The index, the checkout page that the opens and you checkout session. So lets you pay. And the billing portal that lets you manage an active, uh, subscription. So, uh, going back to the subscriptions controller, we, uh, check if the user is subscribed or not. And based on this, we can display, uh, different page. So here we have avo. Let's open the S organization, go into Billion. You see in uh, s and in avo, we have, uh, different states. Here it is a scandal subscription. Here it is, uh, uh, not initialized subscription. So not, not even, uh, started to subscribe. Never was subscribed. So different states, uh, we can have a look at subscription Index. Yeah, so if the user is subscribed, then we see their current subscription. Uh, otherwise there is their pricing table and the features are shown to everybody to see the value that you have if you're subscribed or that you can have, if you will, uh, subscribe. Now, uh, here is the index. Now check out, uh, again, uh, we find the price by the ID from the bottom here. You can see on the button we have PERS price id. So we find that Stripe Price and Initialized a checkout session with this Stripe price. And there are all these additional settings like, uh, enable Automatic Text, allow promotional codes or not. You can actually disable all of these. Uh, nothing will change. You just have to keep success. URL cancel, URL, the line items and the mode subscription. So it initializes checkout session and directs to it. And this has to be, uh, data turbo false, uh, for it to work. Let's, uh, try without Turbo False. Let's go to pricing. Uh, here has the subscribe button. So, um, got started and yeah, actually it worked on the phone, so it doesn't always have to be data top of Falls. Okay, great. Um, going next, uh, back to the subscriptions controller. So here has the checkout, then we have the billing portal. This one is super easy. Uh, we just initialized the billing code portal and direct to our Stripe, uh, subscription management page, this one. And also there's a bunch of logic here all around to ensure that only organization admins have access to subscriptions logic that, uh, says that the subscriptions should be, uh, Syed and billing should be enabled. Uh, meaning if you don't have any Stripe credentials, then uh, these, uh, billing pages will not be visible at all in the, the application. So you can use it, uh, you can use Money Gun as a ancy application without the subscriptions model. If you don't have a stripe, uh, key present. Let's try moving the Stripe key. Um, Okay, let's edit our credentials. I will comment out the Stripe private key. Now save, leave. I will, uh, restart the server. So see if there is no Stripe public, oh, private key present. Then the billing logic benefits from the application. Now I will add it back in. Okay, so the subscription logic has been added back. Now, there are actually three different, uh, states of payments, uh, not, uh, subscribed subscription, uh, not renew and active subscription. So let's create another account of it and active subscription. I will create the I know, bus route. I'll add the logo. I will, uh, go and subscribe. Okay, subscribe. In this organization, pay is doing its magic with, uh, standing the web hooks, updating the pay customers pay subscriptions, so we don't have to manage about all this in our application. And here we have an organization with an active subscription. So let's have a look at all organizations. Once again, you see three organizations in different, uh, states. Let's open all of them and navigate to the billing pages. And see, we have three different states of billing. So, uh, not subscribed, actively subscribed and renew and, uh, actively subscribed, but, uh, not renewing as subscription. So these are like the three most basic states. That's, uh, about it, about the, uh, payments and software as a service implementation. So yes, you can now have, uh, uh, billing on organization level, and you can have multiple organizations. I think it's wonderful. And another tiny thing I did is, uh, I updated the layout. So now, uh, it is more responsive and, uh, it works, uh, fine on the small screens and big screens here has a small screen version. You see, we have this, uh, sidebar looking outside will close the sidebar. And also it is responsive. So if you have like a lot of links, a lot of organizations, it's going to work fine. And another thing, so if you, you can switch the layout. So, uh, let me demonstrate. There are two different sidebar layouts. Uh, this one that looks a bit like Slack. And, uh, this one, that is just one column. So you have two layouts to choose from, or you can build your own. Well, that's about it. I hope you enjoyed it. And, uh, yeah, try building, uh, uh, to be sauce with mangan. I think now it is, uh, possible. --- # #232 AVO Directory. SEO basics. Friendly ID, Meta Tags. - URL: https://superails.com/posts/05-avo-directory-friendly-id-meta-tags - Published: 2025-02-18 - Duration: 17:25 - Access: free - Topics: avo - Playlists: AVO 0:00 Search Listings by JSON payload (metadata) 2:21 Computed resource title 4:38 Friendly ID (Pretty URLs) 11:00 Meta Tags - [Search Listings by JSON payload (metadata)](https://github.com/corsego/avo-directory/commit/1a31498f021e721fa536c19dab4eb45f17887ff1) - [Computed resource title](https://github.com/corsego/avo-directory/commit/b7f8b70813772ac25dc1dc798c2421cb722d96d4) - [Friendly ID (Pretty URLs)](https://github.com/corsego/avo-directory/commit/03cb7d8fcf51fce74fee607ea4b7974fa68a6631) - [Meta Tags](https://github.com/corsego/avo-directory/commit/9512335b51ffa32a086f6db526b00a4946e71205) ## Chapters - 0:00 Search Listings by JSON payload (metadata) - 2:21 Computed resource title - 4:38 Friendly ID (Pretty URLs) - 11:00 Meta Tags ## Transcript So now our director looks not bad here. I'm just looking at all the listings and, uh, just from having a URL, nothing else, we take screenshots of the landing pages of these websites. We extract the logos, the titles, the descriptions, let's, uh, make the app more usable. And, uh, first of all, we can start with the search. So at the moment, we can search only by the, uh, URL, so I can type HTTP, whatever. I can type RB ui, UCI find this website. And we can search by internet except the URL. We would like to also search by the title or description. So, uh, screen costs, you see, nothing is found. Now. Uh, the title and the description, they're both stored inside the payload. Uh, Jason o attribute of the listing model. And, uh, the easiest way to make it, uh, searchable would be create a ran here in the listing model. Raneer payload, uh, do or scale payload. And, uh, now I need to say that the payload is also going to be, uh, searchable here. Now, going to our listing resource in avo. Here we have search by your L Contains, and we'll also have, uh, search by, uh, payload contains. So payload contains. Let's move this al in your line. Okay. Uh, let's see if, uh, it works. So I'm going to refresh, I will search. And, uh, you see it found these two results because both of them have, uh, the word screen, uh, either in the title or the description. So now we search not only by the URL, but also the title or description looks good. Let's actually save the change, uh, search by JSO payload. Okay, this is not bad, but, uh, you see, currently we display this H-G-T-P-S, uh, uh, everywhere. And it looks, uh, it looks very not, uh, beautiful. So let's, uh, make it better. And here you see, we also have the whole HGPS, whatever we want, just the, uh, the domain name. Nothing else, don't want, uh, all the rubbish around. So at the moment in the resource, the title is the URL. We could make it the ID if make the title to be ID here. So you have the ID of the record here, have the ID of the record. And, uh, in the search, we just search, uh, the results as ideas. So instead, they're going to have a computed field Here. Let's, uh, make it, uh, a clean URL. Let's define clean URL in our model. And, uh, it is going to be the URL without, uh, HTPS. So URL Gs sub, uh, remove HTPS remove, uh, ww if there is some. Uh, let's see if, uh, it makes it look better. So, um, here you see it looks, uh, better. Let's now, uh, maybe remove the, uh, trailing, uh, slash um, how can GitHub co pallet help us, um, remove, uh, trailing at the and Okay. Dot. Okay. Looks, uh, much better. So now the titles are styled in a much better way. Let's also save this, uh, change. So, uh, resource title, computed resource title, not just attribute value. Okay, looks, uh, good. Now going further, you see this is going to be, uh, user facing application. And if you don't want, uh, everybody to know the ideas of all the records in our application, we want to have, uh, friendly ideas. Like, for example, if you go to Super ails website, you visit any record here, you see, you don't see the ID of the record, you see the, uh, friendly id. So, uh, a readable ID that explains what this record is about. Uh, they're going to use the gem friendly id. And actually AVO already has, uh, an integration with the, uh, friend id. I think. Let's see. Uh, yes, brand gem. Yeah. So, uh, there is an example of how we can, uh, make these, uh, URLs look nice using the friendly ID gem. So first of all, let's install the GEM friendly ID bundle at brandley id. Let's look at the docs of the gem. Okay? Uh, yeah, the run bundle, uh, error load. And Ruby, Ruby Jams. Interesting. Let's, uh, try doing it manually. Let's run bundle. Okay, uh, then we are going to add slug to, uh, our listings. Add slug to listings, slug, uh, unique. And we can, uh, also do it for our categories. Let's see our migrations. Okay, adding slug to listings, adding slug to categories, rails, db, migrate. Uh, next, uh, rails generate friendly id. Um, let's see what it gives us. So it gives us the friendly id, slacks migration. Okay, now let's run Rails to be migrate. Uh, adding friendly ID to our records. Let's see how it is, uh, explained in avo. So in the user model, we extend front ID and use, uh, slog. Let's go to our listing model. And here I will, uh, extend front id, friendly id, uh, URL, it is the like, main, uh, attribute on the listing. Use slog. And, uh, we're going to do something similar on our category. So friendly id, but category has the main attribute as a name use slog. Okay, going further in the friendly documentation, if we can just, uh, add the slugs to all our listings. So let's try open one listing rails Console listing. First you see slug as nil. Uh, now I will, uh, run, find each and save on all the listings. And, uh, it should update the slugs. So listing first, you see it has, uh, uh, slug looks, uh, better. Let's, uh, run the same on all the categories. So category, find each and save. Okay, let's, uh, now see our app. Uh, I'm going to try to refresh. You see, it has initialized listing friendly id, I think I need to restore the server. Okay, so, uh, I will open one listing. Uh, and you see, uh, the URL is set, uh, according to the front id. But, uh, we have this confined listing with this, uh, id. So we need to go again to, uh, the example in, uh, where was it? Yeah, here it is. And, uh, we need to satisfy record method. So, um, I think somewhere here on top of the lister source, We will say, uh, find drug method, find by slug, or find by, uh, id. Let's, uh, refresh. And wonderful, we found this listing. Let's go to all the listings. Open another listing. Okay, so it works. Now we have a friendly ID installed, and, uh, the users cannot also simply enumerate through the IDs. Let's just hide the IDs from our, uh, view. So here we have the ID field. We can just hide it completely from. Now on the listing on the category, actually for the category, we also want to add the, the same friend lady lines. And, uh, for category, we can also say that the title will be the name, title will be name. Okay, so here we have a category open. You see we have it in the UL. Looks good. Okay. So this is the basics of adding the gem friendly id. Let's, uh, save our changes. Okay, now, next step. Again, this is a internet facing website. And, uh, it doesn't require login. And uh, we want to make it searchable. We want it to all these links to be, uh, also shareable. Uh, so we need to update the metadata on, uh, our webpage. So you see in the head, we don't really have any meta keys except the title that isn't really a meta keys, the title of the tab. So we don't have any meta keys set, uh, on this page. And for example, if I go to super rails and open a video here, I have, uh, a lot of meta keys set to make, uh, it, uh, more searchable on the net. So like, uh, I have a open graph, uh, description, title, uh, images, Twitter cards, and so on. So let's add meta keys to our VO page. Now, how can we add the me keys to an other page? So, uh, the gem, uh, uh, vo uses, uh, meta tag gem. So it already is included together with ava. And if you have, have an example of, uh, extending, uh, actions inside an AVA controller. And, uh, we can actually use the same approach for, uh, certain meta tags inside the, our AWA views. So we have the gex that is already installed with VO and uh, actually found a specific, uh, pull request in 2021, where Adrian, uh, introduced, uh, Meditech into vo. And he have, uh, I think some controller actions. Let's see, a controller. Yeah, so he have controller actions where he's setting some meta text, and I think he defines, uh, default meta text somewhere. So yes, here in the layout file he says that default meta text. And, uh, here are some examples of using meta text in the controller as page title description keywords. And we can use the set meta tag. So, uh, you see, if we go to our controllers, whenever you generate, uh, a resource, you also generate, uh, inside the controllers other folder and empty controller for this, uh, resource. And, uh, let's try having deaf show in this controller. So, uh, I'm going to a listing. I will refresh the page and you see, uh, it doesn't work 'cause I'm overriding the other default to show. But if I type super, this is going to input everything from vo. So the page works. And, uh, now I can try to define some meta tag. So I can say, set meta tags, uh, description. Hello. Now let's refresh the page. Okay, so, uh, do we have the meta description? Yes, we have. Hello Seth. And I think we should have, uh, access to the record or to the resource description. There'll be, uh, record dot, uh, id, id, let's see if it works. Yeah, we got an arrow I think we need to make to stream. Okay, let's open the description. So we have the ID of this, uh, listing. Okay. Now we can, uh, set the description as the same, uh, description as it is on the official website. So we can do it, uh, record, uh, payload, dig, meta description. Uh, we can set the open graph image, uh, yeah, title image. Okay, let's, uh, see if it works. So, um, inspect element head, uh, og title og. Do we have image? Um, no. So we don't have the OG image. Maybe we need to get it from the listing. RB discover image for display. Let's save it folks. Yeah, we got an error. 'cause it has to be a URL. Oh yeah, maybe record at track. Let's save it folks. Now. Okay, so we have, uh, an attachment. Uh, it is not a URL. Uh, let's try making a URL. Uh, let's open the list in model. And, uh, instead of just having cover image, we'll want to have that, uh, URL for this cover image. Yeah, so we'll have just cover image URL. Let's save it folks. I refresh. And, uh, OG image. Yes, we have the URL. Let's try opening the URL. It does display the image. Okay, so that's, uh, basically it. We set the meta tags for our show action of, uh, uh, listing. Now it's going to be most searchable and more shareable around the web. So, uh, let's say, uh, set meta tags for listings. Okay? --- # #231 AVO Directory. Attachments. Screenshot automation - URL: https://superails.com/posts/04-avo-directory-attachments-screenshot-automation - Published: 2025-02-18 - Duration: 11:05 - Access: free - Topics: avo - Playlists: AVO 0:00 [Attach cover_image to Listing](https://github.com/corsego/avo-directory/commit/f6dde232b8ada6b2e9c5504bf13e1b1193d980f6) 5:40 [Action to take website screenshot](https://github.com/corsego/avo-directory/commit/7a04a232c4a106333dc7c5ed80e05f047a61a9a4) ## Chapters - 0:00 [Attach cover_image to Listing](https://github.com/corsego/avo-directory/commit/f6dde232b8ada6b2e9c5504bf13e1b1193d980f6) - 5:40 [Action to take website screenshot](https://github.com/corsego/avo-directory/commit/7a04a232c4a106333dc7c5ed80e05f047a61a9a4) ## Transcript Hello. So let's continue building a directory with Ava. Now, previously we created the listings and to a listing. The only thing you can manually add is A URL, but we have this, uh, action. So I will select all the listings and action to update metadata from A URL. So I'm going to click this, uh, action. I'm going to run it, and they're going to actually visit all these URLs and, uh, extract the metadata from the HTML. So now if I refresh it, wow, we have the titles, the descriptions, the logos, and if we open some of them, we even have, uh, uh, images, uh, like the open graph images. So the social preview images attached looks really nice. But let's have a look at some other listings here. For example, we tried posting this website. We found the title and description, but we did not find the favicon for some reason, and we didn't find the, an attached open graph image. So let's say we are going to allow, allow the admin of our directory to manually upload the, the cover image. For example, here we have another directory that I was taken inspiration from. And you see when you go to any listing inside this direct directory, it has, uh, a screenshot or an image, uh, uh, like a visual of, uh, this, uh, listing. So, uh, let's add the attachments. We're going to add active storage attachments. So let's add the image pro in gem. I will enable it and I will run this commands active storage install and, uh, rails to be migrate. Okay. And now we are going to say that our listing will have one attached to, uh, cover image. So listing rb, uh, has one attached cover image. Let's put it next to the other associations. Okay. And I think net bundle of the Theron bundle after adding, uh, the gem, uh, image processing. Okay, so a listen has one attached to cover image. And, uh, inside VO we need the possibility to attach an image. Now, AWA has this, uh, file field. So let's, uh, add this kind of field to our listing resource. Going to, uh, app, uh, vo resources listing here will have field, uh, cover image as file is image through. And, uh, let's refresh. 'cause we, we have this cover image field. I will go to edit and we have this, uh, input for the cover image. Okay, let's try uploading an image. Uh, we can now actually select any file. Maybe there is an option. Yeah, accept image. So I will say accept image. Let's refresh and try again. You see now only the images are selectable. Let's select one of these, uh, images. Save. Okay, you see we have the image attached. Now we have the, uh, title of the image also displayed maybe for our users if we don't need to display the titles. So I will also add the display file name as false. Okay. Now we can manually attach an image, but you see at the moment, uh, for this listing, uh, we don't have, uh, this image displayed as the cover image. We have, uh, a red circle. Now we have it in the logic for self cover photo. This is the cover photo, and we have either the open draft image or this placeholder icon. So we will say, uh, if record dot cover image attached, we'll have, uh, record, cover image. Uh, else we'll have that other logic plane. Let's refresh. Okay, now we have the cover image. Let's, uh, have the same kind of logic here on the grid view cover, URL. 'cause now if you go to the listings, you see here we have, uh, the newly uploaded file, but here we don't have it. And again, they'll have the same logic here and here. So let's move it to our model. I will, uh, go with this all and go to our listing model. And I will say, uh, dev cover image for display. I'll put in this logic. Uh, let's beautify it. I will have, uh, return cover image if it is attached. Otherwise, play the other logic. Now we don't have record defined here. It is kind of self cover image and the self payload. And now going back to avo, I can just, uh, replace all this it cover image for display here and in the grid view. So yeah, it is record dot. Okay, refreshing and wonderful. So see we have three different conditions, either the attached file, uh, this logic or the, uh, open graph file, this logic. Or if we don't have the open graph file or the image attached, we have the red circle. Looks good. Now let's save our changes. So, um, attach cover image, uh, to list them. Okay. Now, uh, we have two different conditions, but what if, uh, if we don't want to force our admins to manually upload the images, let's make it more programmatic. Here. You see this tool, they take a screenshot. I don't think that the admin manually took a screenshot and, uh, uploaded, uploaded it. Uh, I think it was done programmatically. Now we can do it with, uh, uh, rum. So I think in, uh, episode 162, I talked about the rum headless browser where we can visit a UR L and take a screenshot and attach it to, uh, record. Now going to the blog post to see how I actually did it. Let's add the G therum to our, uh, application. Now I will create a job rails generated job, uh, uh, URL screenshot for a source or, uh, yeah, resource screenshot. Resource your l screenshot or record your L screenshot. Okay, let's open our new created job. It is going to be performed based on a record. And, uh, let's see how I did it here. So it'll have u lals record, u rl, uh, then have, have browser with a few d different options to have some timeout. Uh, we're going to visit the URL sleep a bit to make sure that everything renders. Uh, and now we're not going to browse pdf DF, we're going to, to browse screenshot. Uh, we need to have TMP filed defined. So we will have, uh, uh, TMP equals them. File new. Uh, okay. And finally, we're going to attach, uh, this, uh, screenshot to our, uh, record. Record. Do, uh, cover image attach io TMP path file name. Uh, let's make it Modena record. Uh, ID screenshot and let's save it in JPG. So it phase less and quote will be 40. And, uh, we are going to make it, uh, we're going to resize the browser. I think there is an option here. No, I don't see it. But here we can do browse ultra size And we will resize it to the standard open graph size with uh, thousand 200 high 630. We will visit the UL. Take a screenshot. Uh, let's see if, uh, this works. This might actually work. Yeah, also, I need to add the insurer browser fit. Okay, now let's take a record code or a listing. Okay, here we have a listing. Let's, uh, run the job on this listing. So record url, screenshot, job, perform now on the listing. Okay, undefined method. R re, yeah, ize. Let's reload. Try again. Okay, now it's say listing. Do cover image attached. Yeah, we have attached to cover image. Let's have a look at this listing in our application. I think it was need draw kit. Okay, so here we have attached the screenshot of the whole website. Now we don't need the screenshot of the full website. So going back to our job, I will not say full through. Okay, so now we can create screenshots programmatically. We need to add an action, I think, as we did for passing the website. So let's create a new other action. Uh, it'll have the same name as here, record url, screenshot, um, rails generate vo action record URL screenshot. Uh, let's open vo vo actions. And here we will, uh, run this job for all the selected records. And finally, we need to enable this action on our listing resource. So going back to our AVO resources, here we have our listings action, avo actions record your L screenshot. Okay, let's refresh. So now we have two action. Let's now try running this action. Okay, look, there is an attachment happening, so I will refresh. And Wonderful. We have, uh, a screenshot of the website. Looks great. --- # #230 AVO Directory. Cover & Profile images. Grid VS Table. Nokogiri for metadata - URL: https://superails.com/posts/03-avo-directory-images - Published: 2025-02-18 - Duration: 24:37 - Access: free - Topics: avo - Playlists: AVO 0:00 [Extract metadata from URL. Avo Actions](https://github.com/corsego/avo-directory/commit/77c7c753bcf0907fdebcc8b654bd7315344250d6) 12:13 [Cover & Profile images](https://github.com/corsego/avo-directory/commit/ac628d777ed5fa1e7132452fe3983be52b3f16d8) 18:56 [Grid View](https://github.com/corsego/avo-directory/commit/6bc7834b3c6d6418a4de86cdcbf109b0b74f91cf) 22:16 [Not attribute-backed Avo fields](https://github.com/corsego/avo-directory/commit/eb76b32e07d0bd0fdb3a37c07eb7c12750507dc1) - [Parse URL metadata with Nokogiri](https://blog.corsego.com/nokogiri-website-metadata) Avo resources: - [Actions](https://docs.avohq.io/3.0/actions.html) - [Profile & Cover photo](https://docs.avohq.io/3.0/cover-and-profile-photos.html) - [External Image](https://docs.avohq.io/3.0/fields/external_image.html) - [Grid](https://docs.avohq.io/3.0/grid-view.html#enable-grid-view) Example directories (not an endorsement): - https://publicapis.io - https://openalternative.co - https://devsuite.co ## Chapters - 0:00 [Extract metadata from URL. Avo Actions](https://github.com/corsego/avo-directory/commit/77c7c753bcf0907fdebcc8b654bd7315344250d6) - 12:13 [Cover & Profile images](https://github.com/corsego/avo-directory/commit/ac628d777ed5fa1e7132452fe3983be52b3f16d8) - 18:56 [Grid View](https://github.com/corsego/avo-directory/commit/6bc7834b3c6d6418a4de86cdcbf109b0b74f91cf) - 22:16 [Not attribute-backed Avo fields](https://github.com/corsego/avo-directory/commit/eb76b32e07d0bd0fdb3a37c07eb7c12750507dc1) ## Transcript Hello, friends. Let's continue building a directory with avo. Now, uh, in the previous episode, we created categories. So categories can have, uh, many listings through category listings. And if we, uh, open a category, we have, uh, a view of all the associated listings, and we can go and attach listings that are not yet attached to this category. And if it is a listing, we can see the categories that the listing is attached to, to we have, uh, all these resources. So category, listing, category, and listing inside vo. And now we are going to continue building our directory. Let's first have some, uh, inspiration from, uh, different directories around the internet. So here I have a few directories, one, two, and three. And curiously, all of them look more or less, uh, the same. Uh, let's try opening a few links in them. So here you see I have, uh, a homepage. I have some, uh, t uh, listings. If you open a listing, you see the description of the listing, and you also see, uh, a screenshot and a link to go to the website and the avatar. Here we have, uh, a list of categories. We open a category, we see the listings inside the category. We can open one of the listings. Again, we see a screenshot and we see, uh, a description and link to visit. Going to the third directory, again, listings on the homepage. And, uh, I don't think they are kind of categorized. Yeah, here, they're not really, oh, yeah, there are like categories like health and so on. So here I have the health category. I have the listings. And, uh, again, if I open a listing here, we don't have screenshots of the listings, but we do have the name, the description, and the logo. So let's, uh, make our directory actually look a bit better. Uh, to start, we can actually make, uh, it so that we have the URL. And just based on the URL, we are going to try to find the title, the description, and maybe an image, uh, from, um, each, uh, website that have listed in our listings. Now, um, here, uh, to start, I also, uh, created some seed data. So here in seeds, uh, I've got, uh, some, uh, URLs that are RAs UI component libraries. And, uh, it's actually based on, uh, this, uh, blog post of mine. This blog post also, uh, kind of inspired me to create a directory because, uh, here I kind of list, uh, Ruby RAs component libraries, uh, in a blog post. But, uh, it make much more sense if it was, uh, a category inside, uh, uh, re's tools director, I think. So, uh, let's start with adding a feature to be able to get the additional data, uh, about the website just by listing. Its URL. And the easiest way to get, uh, uh, more data about the website is, uh, to go to the homepage, I think, and, uh, pause the homepage for information that we need. Uh, so let's imagine I, uh, type in the a new listing cprs.com, and we'll have a tool that will visit the homepage, and our tool will, uh, get the title of the page, the description of the page, uh, the favicon, it'll be the logo and maybe the open graph image. So we would get this from the meta text here, you see in the head, we have, uh, the title of the website, the meta description. We can even get the keywords, we can get the open graph image if there is one. So if I cope with this image here, you see it's, uh, kind of an image of the website that I can, uh, just extract from the HTML that I'm passing. I don't even have to use a screenshot generating tool. Let's try doing this. Let's try extracting the metadata of a website, and we can do it using the Gem NOCO Giri. So, uh, I've actually written a quick blog post about it recently. Uh, we can use NOCO Giri to visit a RL, get the, the core data, like the title, the, uh, favicon, the description, the open graph image, and let's do it for each of the links. So, uh, I will start by creating a new job. I will say, uh, rails generate, uh, job, uh, crawl, uh, crawler, uh, your L crawler. Let's then open our job, and I will, uh, basically copy all of this, uh, inside. So I'm going to use NOCO to open a URL, uh, pause it and, uh, get all this, uh, core data. And, uh, let's try doing it. I can do it in the console. So, rails console, uh, your crawler job dot perform. Now, let's say result equals your crawler job, perform now. And the URL will be, uh, listing last URL. And here we have the page title. We didn't find the, uh, fon, URL, uh, we did find the meta description, and we did not find the, the OpenGraph image, URL, uh, for rails cuts. So they don't, uh, have a favicon set in the same way. And, uh, they don't have an OpenGraph, uh, uh, image. That's fine. Let's, uh, uh, try for another listing, listing do first URL Here, you see, we already have the favicon, the open draft image. So here we have, uh, more data. Well, it doesn't, uh, mean that all the websites will have, uh, all the fields filled in, but this, there's a good start. So now we have added this job to extract, uh, this core data for the websites, and let's store them, uh, inside our listing. Now, in theory, we would want to maybe store more different, uh, attributes from crawling a website. And I don't wanna create a separate attribute for each, uh, of these. Maybe I want to just, uh, store them as Jason, and, uh, this way I can add more attributes, uh, to crawl whenever I want. So, let's, uh, create immigration rails, generate migration, add, uh, payload to a listing listings, uh, payload js O uh, now I'm not doing a JSON b uh, column, though. JSON B is, uh, better and more performant. I'm just doing JSON because, uh, I wanted to be more database agnostic. And, uh, as far as I know, JSOB is not, uh, uh, supported by all the databases, uh, uh, at the moment. Okay, let's, uh, now try running this, uh, ultra job on, uh, listing. And, uh, we will store this, uh, hash inside the listing payload. So, uh, let's, uh, make it based on the resource, uh, and we will have, uh, URL equals resource, URL. This will be the payload. And, uh, I will say resource update payload. Um, so this in theory, should, uh, update the payload attribute inside the, the listing with this, uh, hash, let's try running it on the one of our listings listing, because listing first, okay, let's now, uh, uh, run the job. We'll crawl a job, perform now, uh, based on this listing. Now we invoke the listing and you see inside the payload, V saved all this data. And if you say, listing payload, we have this, we can say, get, uh, uh, OG image URL, and he have, uh, the image URL, so it looks good. Now, how do we display this, uh, data in our application? And how can we make it so that we can, uh, run the job to, and, uh, crawl the website from our, uh, app? Uh, we're going to use AVO actions. So, uh, if we go to the AVO documentation, uh, actions creating an action, we're going to run a generated to create an action, uh, a will run rails generate AVO action, uh, we can name it, uh, uh, URL crawler, same as the job. And now if we go to the, our folder, we have this actions, URL crawler, um, uh, update. The name will be like, uh, uh, metadata from URL. Uh, and on the record, we are going to run URL Crawler job perform later on the record. Uh, now we need to add this action to our, uh, listings. So going to our, our resources listing, let's see how it's done in the docs, uh, we have to define actions, so dev actions, and here we will say, action avo actions your L crawler. Okay. And now I think if the refresh, uh, our page, uh, of our listings here, you see we have this action updated metadata from URL. Now let's, uh, comment it out. Okay. See, there are no actions. And then I enable this action, and we have it visible. Now, let's, uh, also display the payload of, uh, each of these URLs to see if, uh, they have, uh, uh, the metadata extracted already or not. Let's, uh, have a field, uh, metadata. Oh, payload as text. Okay? So you see we have payload for just fun of them. And let's run, uh, the job on a couple of other listings. So you see, I select a couple of listings. I will, uh, run the job. Uh, the job must be running here in the background. So if I refresh, now you see I, I have updated this data. Wonderful. We have added the feature of, uh, uh, just having a URL, and, uh, and now we have the action to visit the website and extract the core data. Marvelous. Okay, so now for some of our listings, we have the URL to the favicon, the URL to the open graph image. And let's try displaying them. If we can display them inside the, uh, listing page, or we can display them in the list of the listings, but actually other has this beautiful feature cover and profile photos. So imagine in the background, we can display the screenshot of the website or the open draft image. And here that will be the logo. Let's try using this cover and profile photos, uh, feature from avo. So I will cope with this for profile photo and, uh, go into our listing view. Oh, actually, let's, uh, save our changes. It's good to make, uh, saves during the progress. So, uh, we will say job to crawl URL and, uh, update listing metadata. Listing payload. Okay, let's continue. So now we are going to add the profile photo and, uh, for the index view, uh, well, it's gonna be nothing. We're not going to display it on the index view, I think. But, uh, we'll have, uh, by default, uh, I think slash uh, we have, uh, uh, dot png. We have some kind of png yes, if we have, uh, icon png, let's say slash icon png. And here in, uh, the actual, uh, uh, show page, we are going to display the profile photo. And the profile photo will need, uh, link. Uh, it'll be, let's see how it is in, uh, our app at the moment. So it'll be record payload, digg, uh, it'll be favicon, URL. Okay. Now, let's refresh this one. And wonderful. You see we have the favicon displayed. Let's also do it for the cover image. So we have the profile photo, we have the cover image somewhere. Okay, cover photo. Let's go with this. So, uh, cover photo, uh, the size. Let's make it, uh, yeah, let's start with medium visible on show and forms. Uh, so, uh, the default image is going to be the one that we have, uh, slash image icon png. It's the one that comes, comes with the new rails app, but you can change it to anything like not found or whatever. And here we will have record payload, dig, open draft image, URL. Now let's go and refresh the listing. Wow. You see, we have, uh, the open draft, uh, uh, image as the background and the logo as, uh, the avatar of the listing. Let's, uh, try, uh, updating UL metadata for more of them. Let's go to super ails. Okay, here you see and define metta. Dig for nil because, uh, uh, payload is not defined on this, uh, record. It's empty. Let's say record payload at Dick here and here if, uh, there is no payload. Okay? So now there's nothing, uh, if we can try display in the icon if, uh, nothing is found. So you see we have the, like placeholders. Let's go and, uh, visit the UL and update the metadata. I will, again, refresh the page and you see the, uh, and the background image are here. Wonderful. Let's try experimenting the different sizes of cover photos. Let's make it large. Yeah, it's, uh, large now. Okay, so this is basically using the cover and, uh, profile photos from our, and I think it's a really cool feature to have, uh, out of, uh, the box. Okay, let's go back to the list of our listings. And here you see, we have the URL, we have the payload. If you don't want to display the payload, uh, at least on the, uh, index page, let's hide it from index. So, uh, I will have hide on index. Okay, let's refresh. Okay. And we do want to display at least the, uh, favicon, like the avatar of, uh, the records. So let's, uh, display it. I will, uh, do it using the vo external image, uh, field type. So, uh, we would have a field, uh, uh, yes logo as external image, and we are going to use a block to define it. Uh, yeah. So we can also address, uh, Neil. And, uh, we are going to add a logo. And it's going to be, uh, let's see. We have the resource available here, so it'll be record, yeah, the record, record payload, fcon, URL. Okay, go into the list of our listings. And here you see, we see the avatars. Let's make them, uh, visible before the URL itself. Okay. And we see some of the avatars looks, uh, great. I think now, uh, a next step, uh, can be having a grid view and uh, uh, uh, table view. So a has this feature grid view where you have beautiful images, uh, uh, if u have images, uh, uh, a title and a description. Instead of, uh, having, uh, this kind of table view. And, uh, let's say I'll use, are going to be able to switch between table view and, uh, grid view. Uh, going to all these, uh, director of websites, uh, all of them have, uh, a grid view enabled. Okay, so let's add the grid view. Uh, before we do it, I'm going to save our changes. So, uh, uh, avo, uh, profile and cover photo. Okay, let's add the grid view. So again, I will, uh, copy the default example code going to our listing. And here I will enable grid. And we need to define, uh, cover URL. Now here, it gives the default example if we have, uh, uh, attachments, but at the moment we don't have attachments. We just have, uh, a URL, uh, title and, uh, body Now title can be the, um, let's see, where is it? Title can be the URL name, but uh, it can also be the payload page title. I think it's better. So I will say record payload. Do, uh, at dig page, title body will be record payload at dig, meta description. And the cover URL will also be coming from the payload. Uh, it can be either open graph image, or it can be, uh, the favicon. Let's try both. Let's go to our listing. Okay. Mismatch post, maybe, yeah, I shouldn't have copied all of it. Okay. And here, you see, now we have the table view by default, and we can switch to the grid view. And, uh, in the grid view, you see, we see the, uh, screenshots of the websites, uh, or the open draft images. Let's try using not the, uh, open draft image UL, but favicon. Now I think the open graph image URLs, uh, in our case look, uh, more interesting. So let's leave them. Okay. And that's how we have a grid view and, uh, uh, table view by default, if we go to a category, let's open one of the categories. You see the, we have the listing view. Let's make the grid view that default, uh, uh, view. So if we can make it as default view like this, let's go back to our listing resource. And yeah, I think next to the grid view settings, I will say default view type will be grid. So now going back to our app, I will refresh. And you see we have the grid view looks quite cool. Let's go back to our, uh, listings. And you see they are as a grid by default, but we can switch to table if we want to. So I think it's some good progress. Uh, we basically added the, an other action to visit the website and extract the metadata. So I'm going to now do it for all these listings. I have this background jobs happening. I will now refresh and you see it, uh, go to the favicons or avatars of all the websites. If I visit one of them, I see the cover image, I see the favicon, and I see some, uh, details about each of these records looks, uh, much better. Now, uh, as a final touch, you see we are displaying the payload, but maybe we want to display like the meta description and, uh, uh, title in, uh, separate lines. Let's try doing it. So, uh, I will have, uh, a field for, uh, each of them field, uh, uh, meta title, for example, it'll be as, uh, text, I think, or as HTML, I don't remember. That's, uh, I think yes as text, uh, label title do. And let's try having something. So you have the meta title. I think this label title doesn't fe maybe the, uh, GitHub al was hallucinating. And here we can have, uh, uh, that title from the payload. So I will have record payload page title. Let's refresh here. You see it is in separate, uh, line. And we can do the same with, uh, meta description field, uh, meta description. And here you see we have them in two different lines. So, uh, we don't need to display this, uh, payload that is not, uh, nicely user facing anymore. We see the, uh, fabricant from the payload. We see the OpenGraph image from the payload. We see the title and the description. Let's, uh, completely hide the payload. We don't need to display it anymore. And, uh, let's try another listing to our app. Let's, uh, go and create a new listing. So you say, here we have the placeholders. I will add URL box, for example, so your l box.com, add in a listing. I will go and update the metadata. Now, I'll refresh. And, uh, it didn't find the favicon, but it did find the, the screenshot, the title, and the description. So it looks good. --- # #229 AVO Directory. Associations, Search - URL: https://superails.com/posts/02-avo-directory-associations-search - Published: 2025-02-18 - Duration: 16:13 - Access: free - Topics: avo - Playlists: AVO 0:00 About Directories 0:58 [Create app](https://github.com/corsego/avo-directory/commit/5f82341b5fe41241a52291ee7bf4b41c30f5ccc0) 1:32 [Install AVO](https://github.com/corsego/avo-directory/commit/97af2a53685ce336587527a6f81ac384bee19ee6) 2:42 [Listing model](https://github.com/corsego/avo-directory/commit/de32df79a064ecfb83bd854e215ac5239dfc39d2) 4:20 [Category model](https://github.com/corsego/avo-directory/commit/843e274f41c0462483db25fb35c7c523b55c6216) 5:30 [CategoryListing model](https://github.com/corsego/avo-directory/commit/24cfd386af0e1e356fbe3be8651bfc9f4559a2d5) 6:13 [Category - Listing associations](https://github.com/corsego/avo-directory/commit/e24dc738ae8f54c3ce173f5925d746358cd80656) 9:56 [Select only unassociated records](https://github.com/corsego/avo-directory/commit/df3f04642c73f51bd98b1eee17d8c1d46f44272c) 12:21 [Search categories and listings](https://github.com/corsego/avo-directory/commit/1268f0e77d06a9d09d2be2d557b8196a93301452) Avo resources: - [Search](https://docs.avohq.io/3.0/search.html#search) ## Chapters - 0:00 About Directories - 0:58 [Create app](https://github.com/corsego/avo-directory/commit/5f82341b5fe41241a52291ee7bf4b41c30f5ccc0) - 1:32 [Install AVO](https://github.com/corsego/avo-directory/commit/97af2a53685ce336587527a6f81ac384bee19ee6) - 2:42 [Listing model](https://github.com/corsego/avo-directory/commit/de32df79a064ecfb83bd854e215ac5239dfc39d2) - 4:20 [Category model](https://github.com/corsego/avo-directory/commit/843e274f41c0462483db25fb35c7c523b55c6216) - 5:30 [CategoryListing model](https://github.com/corsego/avo-directory/commit/24cfd386af0e1e356fbe3be8651bfc9f4559a2d5) - 6:13 [Category - Listing associations](https://github.com/corsego/avo-directory/commit/e24dc738ae8f54c3ce173f5925d746358cd80656) - 9:56 [Select only unassociated records](https://github.com/corsego/avo-directory/commit/df3f04642c73f51bd98b1eee17d8c1d46f44272c) - 12:21 [Search categories and listings](https://github.com/corsego/avo-directory/commit/1268f0e77d06a9d09d2be2d557b8196a93301452) ## Transcript Hello friends. So recently I was talking to Zach Gilberg that, uh, created Rails Market that is a director of Ribbon Rails, uh, uh, tools. And a directory is basically where you have, uh, different listings that you can search for. And they are, uh, all stored in different categories. And I was thinking maybe we can build a public directory using avo. So, uh, if, uh, usually you would use AVO for like, something internal, like, uh, an internal CRM or an admin backend. Maybe we can use AVO as an external tool that, uh, uses that are not logged in, could use. And actually, here's an example of a tool that uses avo, uh, and it doesn't require log in. So, uh, here I'm like, on the homepage, I can view different, uh, resources, sold, filter, search, uh, visit any of these resources and see details. So maybe we can use AVO for building a public directory product. Now let's start with creating a new rails application, rails new, uh, Avvo directory app, uh, cd, uh, Avvo directory app, uh, rails, db create, db, migrate, uh, gi add all. Let's create the, let's commit our creation of the app. Okay, so we've created the app. Let's open in the code, uh, editor, and let's start the rail server. So yeah, we have a basic, uh, new app that we have created. Let's install, uh, apple. So I'm going to run this, uh, script, um, install in AVO again. Uh, we will, uh, run the server to have a look if we have it, uh, actually installed. I will, uh, have a look at my roots. So in route I have the Mount Avo engine that goes to a configuration route path. If we go to VO initialize it, they have just generated the root part is slash vo. So to access vo, which go to root path slash vo. Here it is, we have VO installed. Uh, okay, let's, uh, actually make the route of our application vo. So you just go here and you have vo, we'll just make it by heaven slash here. And you are going to local host 3000, and you see vo. So it is the homepage of, uh, all our application. Let's save our changes. Get add all, get, commit, message, install vo. Okay. Now, uh, if I will a directory, we will want to have, uh, listings and the listings can be inside Greece. And a listing, uh, most importantly has, uh, A URL. It also can have a title, a description, some kind of tags. Uh, but let's start with a listing that just has a URL. So I will create a new model. I don't need to create a scaffold because everything is going to be inside avo, just a model rail generate model listing. And a listing will have A-A-U-R-L or website name. Let's make it URL. That'll be text because it can be longer than just a regular stream. Oh, yeah, that'll be stream, whatever, okay. Rails to be migrate. And, uh, you see, uh, when we created this model, uh, as we already have Ava installed it, created the a resources for these listings. So if we go to avo, let's open it. We already have the resource for listing here. We can add different listings. Let's add, for example, super ails. So we have super ails. If we can add the drifting ruby. And, uh, this would be possibly in the same category. The category would be, or rail screen costs. Uh, let's first of all, save our changes. So gi at all, gi com message, uh, listing model. Okay? So we have created the listings and listings should belong to, uh, categories. And the same listing can potentially be in multiple categories. So let's create a category, model, rail, generate model, category. Category can have a name and maybe a description. Description will be text. And, uh, okay, uh, rails did be my, so now we have, uh, listings and, uh, categories, but they are not associated, uh, between each other. Let's create category as, uh, ruby on rails, screen costs. Okay? Uh, so to associate, we need, uh, maybe a joint table, uh, because, uh, listing can be in multiple categories. So, uh, the, it'll name it, uh, uh, category listing. Now, let's say our changes from, uh, creating the category model, get at all, get, comment, message, uh, category model. Okay? Now let's create the gen table for, uh, category listing. Uh, real generate model category, listing, category references and listing references. Okay, uh, rails. DP migrate, uh, bin dev. Here we have this, uh, category listing. And, uh, this is how we would associate categories and listings. Let's save our changes again, get the status, get add all it. Commit message, uh, category listing model. I like making such smaller atomic prs to, uh, have like steps, steps, steps, uh, of my work more visible. Okay, so now let's do the associations. So, uh, let's say listing, uh, has many, many category listings, uh, dependent, destroy, and has many categories through category listings. And the category listing already has the associations and category will have something similar. It'll just have, has many listings. Okay? These are the basic associations. Let's also add some validations like validates, uh, uh, URL presence. True. Your listen has a URL, right? Yes. And the category of the validate presence of a name validates name presence, true. Uh, let's, uh, yeah, we can validate uniqueness. We don't have to. Uh, but yeah, for listing, let's also actually validate uniqueness so that they don't have duplicates. Uh, okay, we have listings and categories and, uh, they are associated. Uh, now let's go back to our application and see if we can associate them in the other interface. So let's open the, uh, category and, uh, try associating listings. So we would want to add the association between the categories and listings inside a, I would go to app av, uh, category. And here it'll be field, uh, listings, uh, as has many. Okay. And, uh, let's try a attach a listing so we can select a listing. You see, we have just the ideas at the moment. So, uh, we don't want the ideas. We want to have, uh, the list in the, uh, URL visible. So inside the listing, we'll say something like, sell of the title will be URLI will refresh. And here we already have the URL. So it is, uh, now a bit more readable. I will attach this one, attach, uh, we can attach another, but you see, uh, I have already attached this listing and I can still try attaching it again. Let's try again and see, have the same listing attached to device. So, uh, this is not good. I will add some validation for not being able to attach the same listing twice to category in category listing model. So here I will have, uh, uh, validates category scope listing and validates, uh, uh, listing ID scope category, just to be double. Sure. Let me now, uh, remove this attachment. I will try attach the super rails once again. And you see I get the error. So I cannot attach the same listing to category device. So it works. I can kind of attach, uh, uh, listings to category. And if I open a category, I see the listings that are attached. I open a listing. I don't see the categories that the listing appears in. So inside the, uh, inside VO listing, I will say field, uh, categories, as has many. Okay, we have the categories. I can try attach category. And, uh, yeah, I see this category I try to attach. And you see we get an error because it is already attached, so looks good. Uh, but now let's make it so that we don't even have, uh, the possibility to select, uh, uh, something that is already attached for it not to be present in the down. So, uh, to see only the ones that are not attached, we'll have to do something a bit deeper. So inside the category has, uh, listings, as has many we'll say through category listings. And we'll also say, attach, uh, scope, uh, lambda. And we will say, query there. Uhnot. Uh, ID parent, uh, category listings, select, uh, listing id. Okay, so now in theory, I refresh attached listing. Yes, I see all the listing that are not attached yet. Okay, uh, now I will try again. Refreshing. You see, I have no options for Slack because, uh, uh, all the listing attached to this category. Let's do the same on the category side. Oh, on the listing side. So only see categories that are not added yet. I will, um, go to this, go to the listing, and here has many categories, as has many. Um, yes, uh, select category id. Okay, refreshing. Okay. Yes, looks good. So now we can, uh, attach only, uh, categories that are not attached yet. Okay, let's save our changes. So, uh, get at all. Get, commit, message, uh, attach, uh, only not yet associated records or better, uh, select and associated records only. Okay. Uh, next, let's try adding the search. So at the moment we just have, uh, listings categories, and we won't be able to, uh, find a listing or find a category. So, uh, go into the other docs. Let's go to the docs. Let's open search. Um, enable search for results. And here is an example of adding search. So let's add search to our, uh, listing model. Yeah, we actually even have it here commented out by default. So we are going to search, not by the id, but the, uh, URL. So URL, uh, contains, uh, let's, uh, yeah, add gem ransack. And you see it is also a free feature. Uh, so I'm adding the gem ransack to our gem file bundle. Okay, let's, uh, see if search by U lves going to listings. Um, you see we have this run needs, uh, runable attributes. So inside the model of listing, they are going to say, uh, the self sackable attributes, URL. And, uh, well, we don't need forensic associations at the moment, but we might want to add categories later. Let's leave this blank. Okay, let's try searching. So super rails. And you see if we find super rails, I click on it and it opens this specific crystal source opens. I have categories set here, device. So going here to the listing, I'm going to remove one of these associated categories. Yeah, okay, now categories are here. Just funds. Looks good. Uh, now, uh, yes, search for listings works. Let's add search for categories. Uh, it's going to be similar. So category rb, uh, cell sackable attributes. Uh, yeah, I'll do it in the same synex as here. Uh, will be name and, uh, I think it's description. Yes, it is description. And, uh, in category resource, we are going to enable search. So self search, we'll say, name contains, uh, or description contains. Okay, let's, uh, refresh. Now you see I've, uh, commented this, uh, out. So now we have search. I will try searching. Uh, ruby works. Let's try searching by, uh, uh, a description. So let's say, uh, screenshot tools will be the name of the category. Take a picture, uh, of a URL. Now going into categories, I'm going to search, uh, picture. And you see it, uh, finds the screenshot tools. So it, uh, works. Search books for both categories and listings. Let's save our changes. Uh, get status, get at all, get comment, message, um, search categories and listings. Okay, looks good. --- # Upgrading Tailwind CSS from v3 to v4 in Rails #228 - URL: https://superails.com/posts/tailwind-4-upgrade-228 - Published: 2025-02-02 - Duration: 13:50 - Access: free - Topics: tailwindcss - Playlists: Tailwind on Rails Keep your dependencies updated! Tailwind 4 can be configured in the CSS file (no need of tailwind.config.js) However you need to first update tailwind.config.js in a very specific way to run the upgrade command smoothly. I highly recommend you to read the official release notes & upgrade guide to be familiar with the latest syntax. Official upgrade guide: https://tailwindcss.com/docs/upgrade-guide Gem tailwindcss-rails: https://github.com/rails/tailwindcss-rails Episode source code: https://github.com/yshmarov/superails/pull/329 0:00 About Tailwind 4 on Rails 1:21 Old Tailwind setup 3:13 Run upgrade tool 5:50 Borrow best practices from new app 7:24 Prepare Yarn/NPM upgrade package 9:20 Run upgrade command 10:24 Tailwind Watch not working 11:35 Fix aspect ratio 12:15 Fix dialogs 13:00 Fix opacity ## Chapters - 0:00 About Tailwind 4 on Rails - 1:21 Old Tailwind setup - 3:13 Run upgrade tool - 5:50 Borrow best practices from new app - 7:24 Prepare Yarn/NPM upgrade package - 9:20 Run upgrade command - 10:24 Tailwind Watch not working - 11:35 Fix aspect ratio - 12:15 Fix dialogs - 13:00 Fix opacity ## Transcript Hello friends, great news. Vin Cs version four has been released, and in this episode they're going to upgrade Tel CSS from Vision three to Vision four in Ribbon Rails application. Specifically, they're going to do it for super rails.com. Now, there is this official upgrade guide on the Tel CSS Docs website. They can run this x upgrade next command on nt. And actually this upgrade guide features some really great, uh, overview of what has changed from Vision three to Vision four, what has been de deprecated, what is the new best approach to ride these specific classes. And, uh, there are examples of like, uh, how, uh, the spacing has changed for some elements. For example, like previously the default ring was ring three pixels, I think, and now to have ring three pixels and to have ring three. So a bunch of classes have been updated. Now, if you are using Rails, starting version seven, whenever you type, uh, rails new with the tail vin, you'll have the gem tail Vin CSS, uh, rails installed. And this is basically the easiest way to have tail Vin CSS working Ruben Rails application. So here has the gem, and the gem has just been upgraded so that the, you can use it with a Vin SS version four. And there's also a wonderful upgrade guide on how to upgrade from version three to version four. Now, before we upgrade, let's have a look at how VIN S is installed in this application. So I've got Rails eight running. I've got, uh, prop shaft, and uh, I've got input maps and Vin S rails, the Gem. Now, uh, if I type, uh, Delvin, uh, in the search I have this Devin config file. Uh, most of the stuff is the default. So there are the plugins that, uh, what is not default is that I added, um, the app slash components folder that needs to be also watched by Delvin, so that, uh, if I, uh, add any TNT classes in the view components, these are also updated. Then I, um, rebuild, uh, NT and I also added some custom colors. So, uh, I cope with some of the colors from GitHub in the super. You see, I've got the Nev bar, the body that, uh, are the same as, uh, in GitHub. So I added these colors and now I can define background, uh, GitHub, uh, nev Bar for example. Here I'm using it. So this is, uh, custom. And I've also got this tele application, tele CSS file where we import the tailwind, uh, uh, base components and utilities. And I defined also a few of my custom styles. And, uh, if I look for tailwind, so I got Tailwind also defined in the head of my application layout. So import tailwind and uh, I have it in the profile. So profile dev, uh, whenever I make any changes in tnt, uh, TNT is rebuilt and specifically it is rebuilt in app assets builds NT css. So this is where the NT is actually present. Okay, so, uh, let's follow the upgrade guide. Uh, now at the moment I have NT fixed on version uh, three. So whenever I type, uh, bundle or bundle update, it's not going to make a major version bump. So to allow Vision four, I'm going to set vision four here, run bundle. Okay, you see I have upgraded to Vision four, and now I will need to run the upgrade guide. So, um, it is been Rail telling s upgrade. Let's run this command. Okay, I get this error because cannot find med module relevant CSS forms. And, uh, let's see what has actually been done. So we started the upgrade, but it got interrupted. So we removed this CSS file, we added a new, uh, CS file and, uh, some changes were attempted in the tele config file. I'm going to undo all of this and I'm going to completely remove this plugins. I will run the Command V scan. Okay, this time it looks like it was successful. So what has changed? NT config has been removed. Application VIN CSS has been removed and everyone has been merged into the application to CSS file. So, uh, uh, the NT config and relevant clause can all be present in just one CSS file. It's much better than having the tele link config in a GS file. And you see the colors have moved into the theme. Now, instead of having three imports, we have just, uh, one input CSS. We've got this other element that has been added by default. So when you have like an asset picker, like select the file there is going to be a brown, uh, uh, a gray border on the bottom. So it is not transparent. And all my other custom classes have been pushed here. Okay, looks good. Let's try running the application bin dev. Okay, I've got this error. So inter font is not found. Let's go to her head and uh, I can just try removing into font. Okay, the app seems to be running. So here is production and here is local. You see the spacing is slightly different. The, uh, app mostly looks the same, but the spacing is slightly different. And, uh, if locally I open some pages, you see, uh, the aspect ratio is not, uh, working, then uh, dialogs are not spaced, uh, in the right way. Like here the dial is spaced in the center, so somethings need to be updated. Now, uh, before we do this, uh, let's have a look how VIN is installed in a new rails, uh, eight application, because that's always a great way to ensure that you're doing everything the right way is create a new application for the default config and see how it's installed there. So here I've just created a new, uh, res application with the tail end, and if I go to the G file lock, I have VIN version four installed. So, um, let's search for vin. You see, we just have application vin, CS s, and it has only one input, nothing else, very clean. And, uh, if you go to application layout, you see, uh, here has just one style sheet tag to app. In my version we have, uh, a few style sheets like VIN application and uh, another import. So I can maybe replace, uh, everything I've got here with the style sheet link tag app. I think it works for input maps and uh oh for prop shaft. So let's refresh. Yeah, works as before. So I don't need to define all these imports, however, just to double check, let's, let's just, uh, comment out VIN and see if loading application works. Oh yeah, I'm in the wrong window. Okay, so here you see I'm not loading VIN file and uh, it looks bad. I'm loading Tailwind, It Works. I will, uh, uh, where was I? I will comment. Uh, NT application and whole out and allow app. Yeah, so it works. Okay. Now, uh, next, uh, you will want to follow the upgrade guide and update all the, uh, elements. You can do it manually, like all, all the changes, uh, like previously had this, uh, naming. Now we need to have this naming. So, uh, to update this, there's also something in the upgrade guide. There's updated CS s plus names for vision form. So it's for the guide. I will open gi, ignore, I will add the note modules. I don't want to comment them obviously. Then, uh, package, do Jason creating this file copy. In this I will remove all the commented out text. Okay, now I need to run NPO Yarn Run Yarn. Okay, uh, this has been installed. Let's have a look at our diff. So there's out of stuff, I need to also put Jan into gi. So Jan and uh, yeah, package. Jason, I don't have to commit all this, uh, stuff. Uh, Jan Lock, pnp, CJs. I want to ensure that these files are not, uh, pushed to production. I need them just to, uh, upgrade that, tell them the classes And uh, this PNP below the GS oh MGS. Okay. Now, uh, next I need to uh, add additional.in the JS file, but we have removed it, so I'm going to undo all the changes I did here, here, and here from the national, uh, update command. So you see now I've, uh, kind of cleaned everything up once scan and removing all the plugins. Then the read says that I need to add an additional.in on the beginning of each of these content lines. Okay? Uh, now I, uh, added the package. Jason, I'll run young once scan, run the, the upgrade command and wonderful. So you see a lot of, uh, the files have been scanned and have been updated. Okay, uh, let's, uh, well now you can like remove all these packages and yarns from your application because the upgrade has been successful. Let's try running the application bin slash dev. Okay, seems to be broken. Uh, let's try changing some colors and see if, uh, the s has been watched correctly from pro profile. So I'll go to like body and change it to background Orange 500 for example. You see, it hasn't been updated. So, uh, the watch command, uh, isn't working from pro profile and uh, it might not be working because if I open Bin Dev, I have uh, just this. But, uh, if I have bin Dev in ndu application bin slash dev, you see it this slightly different. So maybe I would need to go with this bin Dev into my application and let's see if it works. Always good to have a reference to a new Rails app. Okay, uh, this orange color works. Let's try, uh, another color background, uh, slate, uh, or green 500 something that is definitely not used in the application. Okay, it works. Um, so wonderful. I needed to also update the bin dev, uh, for Tel which to work correctly. And now let's have another look at the application. So here, for example, the aspect ratio doesn't work, uh, going to the upgrade guide. Um, somewhere I've seen this layout, things aspect ratio. So, um, at the moment, let's see how aspect ratio is set in the application aspect. You say have aspect width and height. It's the old approach. And now you can just set aspect video that would be also 16 by nine. So everywhere I'm going to, uh, replace aspect 16, aspect nine to aspect video, find and replace refresh. Okay, now it's Firkin. And also for these, uh, models. So, uh, looking at the, uh, tell them the UI examples for models have have fixed inside zero on the models for them to be in the center. Let's, uh, try adding these clause to our models. Uh, I have it search slash form here I have a dialogue. And for the dialogue here, I will set a fixed inset, uh, zero refreshing. Uh, it didn't help. Uh, I might also have to say M Auto. Okay, yes. Now it is in the center, but you see the background is, uh, not see-through. It is because, uh, in the upgrade guides, they also have something about, uh, depreciations in the background, uh, in opacity. So you see previously we had opacity, and now we put the background slash the level of opacity. So here I have back backdrop, background, GitHub, body and Opacity 80. So I will just make it slash 80. Let's, uh, refresh and see it works okay, and it's working so wonderful. Now, uh, you see I followed the upgrade guide, upgraded, everything is working, but there are still a few places that you need to manually click through and, uh, ensure that, uh, they work well with the latest version of tail end. So that's about it. Thanks for watching and see you in the next one. --- # oEmbed - the secret public API for content metadata #227 - URL: https://superails.com/posts/oembed-226 - Published: 2025-01-31 - Duration: 06:54 - Access: free - Topics: youtube api Want to allow users to embed third-party content? (youtube or vimeo videos, podcast players) You should use oEmbed. This way you won't have to manually handle (sanitize, secure, ensure rendering) the embed code or parse content! Users should provide an URL to content, not an HTML block! My oembed app: https://oembed.superails.com/ Oembed app source code: https://github.com/yshmarov/oembed-parser-app Text version: https://blog.corsego.com/oembed ## Transcript Hello. Recently at work, I was given the task, allow users to embed YouTube and Vimeo videos into the application. Now, uh, when thinking of embeds, I think of this feature, go to share, embed, copy the embed html into your applications, H TM L and agenda, this kind of embedded, uh, video. Uh, VMware has the same, you go to share and embed and has the embed code that you can put into your H TM L, but I don't want to allow users to paste any random iframes into my application. I want them to just be able to paste, uh, uh, UL to Vimeo and, uh, YouTube. And, uh, based on this, I'm going to resolve the iframe for, uh, their URL. I don't want to work on sanitizing iframes, I just want to get the As input, the URL. And I want to, to display the embedded, uh, iframe. But, uh, what is the right way to resolve the iframes, uh, for different providers? Uh, well, I found this format called umba. It was created by, uh, Cal Henderson, the co-founder and CT o of, uh, slack. And, uh, the idea is that, uh, uh, a media provider that allows embeds would have, uh, an API endpoint, like Flickr services. O embed, for example, uh, would allow you to input the full UL to uh, uh, media asset, like a video. And it'll give, uh, JSN response with, uh, different data about this video. It can be just, uh, an image. It can be, uh, in embed html for a video. Uh, so, um, both YouTube and Vimeo, uh, use o embed. Uh, if you go to the inspector and search for om embed in, uh, YouTube, you get this, uh, uh, link with type application, JSON plus OM embed, and here's the URL. And here you see the OM embed API endpoint for YouTube. And in then we have the URL, that is the UL of the video that you were just looking at and see, we have this full response. There's, uh, the author name, there's the, uh, title of, uh, the video. There's, uh, even the thermal URL. Let's go with the thermal URL here, here it is. And then there's this embed h, TM L for the YouTube video. And, um, here, for example, in Slack, I just put in the YouTube video, URL. And, uh, we get this, uh, title. We get the, uh, thumbnail. Let's refresh. Here's a thumbnail. I click, and you see it plays, uh, the embedded I frame. So I think, uh, uh, slack is using O embeds for this. Uh, also going to Vimeo. If I go to the inspector and search for o embed, again, I have this link for the Vimeo API endpoint. You said the data, data is, uh, slightly different, but o embed, uh, documentation specifies what, uh, uh, data keys, uh, it, uh, wants to accept. And again, here I have this H TM L that gives me the iframe to embed this, uh, uh, video from YouTube or from vmo. So, uh, I have different API endpoints for VMO and for YouTube. And I want to have one input field to resolve all of them and display the embedded iframe. Uh, here's more or less what I built. So it's kind of already abstracted from the Rails application into a one page application. I have this input field for A URL. I'm going to go with this URL. And here you see, I, uh, uh, determine that it is a YouTube video. I make the request to the YouTube embed the API endpoint. I get the payload. I pass the payload. I see the, uh, uh, thumbnail, URLI display the thumbnail here. I, uh, have the embeddable H TM LI frame. I embed it here. And you see I can play this video if I, um, change anything here, like change the title. You see, there's no valid video at this. Uh, uh, URL, so nothing is displayed. It also works for Vimeo. So I'm going to paste the, the UL to M video here. And you see I'm got this response. There are more data points here from the Vimeo video. I have also the thumbnail. And again, I have the embeddable H, TM L player. And here's the embed code. So using just JavaScript, no Ruby, no rails, I make this a p requests. Let's have a look at the source code. So, uh, again, I just have an index, HTML page and the, uh, JavaScript file that could be easily converted into a stimulus controller. And whenever the user inputs A URL, first of all, I check whether it is a VMware UL or YouTube or anything else. I have this, uh, code here. So I check whether it's YouTube or VMO or something else. And based on this, I'm going to make an API call to the, uh, endpoint. So you see YouTube, vmo, and in this case also have Vista, have different, uh, API endpoints. I make the request, get the data. So this kind of data in JSON format, I pass it and I pass this video, uh, data to display it in the, uh, rich format. So like this, when a user, uh, inputs A URL, he can in real time see whether he has a valid URL or if he has the right video, URL, and you can see the player. And, uh, I think it's a good approach for, uh, displaying data that, uh, you would like, Uh, users to kind of embed into the application. So if, uh, you would like users to allow to embed an iframe from, uh, YouTube or Vimeo or another application allows, uh, kind of rich media like, uh, I know podcast, uh, providers, you could see if they have, uh, support for o embed. You could make, uh, request to their o embed endpoint and get their, uh, HTML for the embeds dynamically. The source code of, uh, this video, uh, is available here I extracted into, uh, single page application. There's this index H TM L, there's the JavaScript. I, uh, published it@oembedsuperl.com, and I published it with, uh, GitHub pages. So going to pages you see, uh, embed superl.com, and it works as a single page application. --- # Best VS Code Extensions for Ruby on Rails Developers for 2025 #226 - URL: https://superails.com/posts/best-vs-code-extensions-for-ruby-on-rails-developers-for-2025-226 - Published: 2025-01-27 - Duration: 06:03 - Access: free Copy my VS Code settings here: https://blog.corsego.com/vscode-cursor-extensions-rails The extensions I use in every app: "esbenp.prettier-vscode", - lint CSS and JS "bradlc.vscode-tailwindcss", - Tailwind CSS lookup "heybourn.headwind", - order Tailwind styles "eamodio.gitlens", - Git change lookup "Shopify.ruby-lsp", - Ruby parser "elia.erb-formatter", - erb identation "marcoroth.stimulus-lsp", stimulus autocomplete and "rubocop.vscode-rubocop", - ruby linter In settings.json I define the rules for each of the extensions. ## Transcript Over the last year, ribbon Rails has done a lot to improve the developer experience outta the box. Now, whenever you create a new Ribbon Rails application, you've got a GitHub CI that works, uh, straight away. You have, uh, uh, a doca file. You've got, uh, UpToDate, gi No, you even have, uh, a robocop that is installed, uh, with some developed configuration in any Euro rail application. But I think something that is still missing from new rail applications is, uh, a list of recommended extensions for this code for ser for similar, uh, IDs. So, uh, you can add this, uh, separate folder vs code into your application, and it'll give a list of recommended plugins. And here I have the recommended plugins for my application. So whenever somebody new opens my application, it's going to prompt them to add these plugins to work in their, uh, development environment. And there's also a separate file settings J where you can define different settings for each of these plugins. Let's go through all the plugins. First of all, I have pretty V code. It helps me style the CSS and GS files. For example, I'm going to go to mention controller. I'm going to save this file. I just clicked on the, uh, command S and you see it formatted the file. And this way, uh, I format all the files and, uh, they are going to all form to the same, uh, style. And again, if you don't like, uh, for example, you want to have, uh, uh, two, uh, instead of one, uh, brace, you can, uh, set this in the, uh, settings, Jason for, uh, pre V code. Then there is this code, VIN CSS. For example, I'm going to open a static file, static index. Um, and whenever I type something in vin, for example, I want to add pattern of 12 pixels, I type P, and uh, I see that pattern of 12 pixels is P three. Okay, so it kind of prompts me what the CSS is behind the, the relevant abstractions, like background roles 400. I see all the details of this class. So this is the, the SCO VIN CSS. Then there is the plugin, uh, named headwind, and it gives an opinionated ordering of the VIN clause. So whenever I'm going to save this file, it's going to reorder the VIN classes in, uh, an opinionated way. But I think it's good to have, uh, one defined order of VIN classes all round than just having them scattered the, uh, the rev in your, uh, class definition. Uh, next there is GI lens. This one is really nice. Uh, uh, so you see whenever I hover on the line of code, I see then it was edited last time, and I can jump to this commit. I go to this other comet, I see when it was edited. So, uh, it makes, uh, finding what was changed and then by whom, uh, much easier. Then there's rub p the, uh, tool to work with Ruby. There's, uh, yeah, formatter. This one actually requires an, uh, gem, the gem EB formatter to be installed and, uh, how it works. So this is an EIB file. I'm going to change the formatting. I'm going to save the file and you see everything is, uh, formatted nicely. So know where, where space end. Uh, then, uh, if you're using the V code, then you can also recommend GitHub compiled and GitHub compile chat. But for other code, it is, I would not recommend them. Uh, like for Al it doesn't make sense. Uh, then there is stimulus SB one mark growth that, uh, helps you with the right and stimulus controllers. For example, I'm going to try to define, uh, data controller, uh, A, B, C. And, uh, in theory, it should tell me that this is not a valid, uh, controller. Now, uh, stimulus, oh yeah, I don't have this one installed, uh, uh, yet. So let me install stimulus. SP. Okay. And you see now it gives me an error. Uh, a B, C is not valid. Stimulus controller. Uh, then there's, uh, Robocop, uh, via code robocop. And, uh, let me go to like user B. I'm going to, uh, define something like full equals ball. And I, uh, see the errors that style string, literal rolls, prefer double quoted. I'm going to save, and you see it was formatted out of the box. Then I'm going to add like some, uh, trail and white spaces here. I see that there is a problem, but I click save and you see it is, uh, saved and formatted, so I don't have to, uh, run separate. Everything is, uh, formatted on save. Now to enable format on save, I, uh, go to VS code settings. You click format on save here, and, uh, again, in settings, do js. I, uh, save format on save through for each of these, uh, formats. Also, sometimes you would want to save something without Formin. For example, like, I'm going to format this, but I don't want the Formin to be in my diff I'm going to click Ctrl Z to not have, uh, all this formin. And I'm going to, uh, open the, uh, kind of terminal and I will save without Formin. So the file is saved, but, uh, no Formin was, uh, applied. This is also sometimes handy when you want to, uh, commit just one change without, uh, having the diff of all the, uh, sty. --- # Yaroslav Shmarov, "Rails 8 Frontend: 10 commandments and 7 deadly sins in 2025" - URL: https://superails.com/posts/yaroslav-shmarov-rails-8-frontend-10-commandments-and-7-deadly-sins-in-2025 - Published: 2025-01-22 - Duration: 25:24 - Access: free - Topics: conference talk - Playlists: Live talks Rewire your React/RailsAPI brain and learn to build a maintainable Rails Frontend!We will analyse some design patterns (10 commandments) and antipatterns (7 deadly sins).You will discover practical solutions to classic frontend problems (modals, sliders, multistep forms, dynamic elements). Learn how to balance between components, decorators, helpers and partials and other frontend patterns. Discover the ecosystem of Rails Frontend tools, like component and UI libraries (stimulus-component, stimulus-use, RailsUI, ZestUI, RapidRailsUI, PhlexUI), encapsulation frameworks (phlex, view components), frontend focused gems (Hotwire combobox, cocoon, lookbook) Discover some practical solutions to classic problems like multistep forms, styled select dropdowns, dynamic forms, responsive tables, nested forms, modals, sliders. --- # Typesense search with Ruby on Rails #225 - URL: https://superails.com/posts/typesense-search-with-ruby-on-rails-225 - Published: 2025-01-22 - Duration: 14:08 - Access: free - Topics: Typesense, search Previously I did the obvious thing and searched my Postgres database for Posts by "title". But when you need to scale your search due to a a large dataset, or want to use advanced search features (image/voice/ignore typos, etc), you might want to spin up a separate search server. The usual suspects are Typesense, Elasticsearch, Meilisearch & Algolia. In this episode we will create an index of our Posts on our own Typesense server. Next, we will perform search and render results by sending requests to our Typesense API endpoint TEXT walkthrough: https://blog.corsego.com/typesense-rails Typesense source code: https://fnf.dev/3ZIAjId Gem Typesense-ruby: https://fnf.dev/3PHqHcb Gem Typesense-rails: https://fnf.dev/4jBiZhw Integrating Typesense into SupeRails (members only): https://fnf.dev/40t6NX4 0:00 Different search engine APIs 02:02 About Typesense 3:30 Install Typesense 4:25 Typesense API - collections, documents, index all posts 8:25 Update Typesense index on Post Create/Update/Destroy 9:29 Typesense API to search collection 10:25 Rails Controller, views 12:00 Typesense-rails integration 13:00 Deploy to Production? Thanks so much to Typesense for sponsoring this video! I had a lot of fun integrating the search server with Rails. ## Chapters - 0:00 Different search engine APIs - 02:02 About Typesense - 3:30 Install Typesense - 4:25 Typesense API - collections, documents, index all posts - 8:25 Update Typesense index on Post Create/Update/Destroy - 9:29 Typesense API to search collection - 10:25 Rails Controller, views - 12:00 Typesense-rails integration - 13:00 Deploy to Production? ## Transcript So in my application, I have 270 records, and at the moment I can search them all by title. So here I have the search field, I'll search for hot fire. And as I search, uh, uh, you see there are the results that match hot via in the title. It works like this. In the post model, I have defined, uh, scope. So here it is, I search by title with I like, so, uh, case insensitive. And, uh, it works okay, but if I make a typo, like two letters are, then you see there is no search match and they're all human. We can all make mistakes. And at the moment the search works quite well on one attribute. Now, I could also add search by description, but possibly the search would be a tiny bit slower but still have 270 records. And that is nothing for Postgres. But as your application scales, you might have 270,000 or even millions of records and you would likely want to, um, scale your search and maybe move it to an external service. So the first things that come to mind are Elasticsearch. There's also Algolia, mail search and type senses. Now I've seen Elasticsearch installed in a few ribbon drills applications over the years, and I always felt that it's like a very, very heavy integration that is, uh, uh, hard to install and maintain and, um, in the type sense, uh, uh, website, there's, there's thing no PhD required. So that kind of, uh, resonates with me. I don't have PhD and I don't want to spend all my life maintain Elasticsearch. Um, anyway, so we've got Elasticsearch. There's also a, Golia is actually a French, uh, company and they also work with rub rails, and that's mail search. And then there is, uh, type sense. Um, I found a nice comparison of, uh, all of these options on the type sense website. So for Elastic search, it says that, uh, um, if you've got like petabytes of data, like logs, then you might want elastic search, otherwise you can, uh, go with another option. Now, algo is fully closed source, so it's just like an API that you integrate with. Uh, mail search and type senses are, uh, open source. And uh, in this episode we're going to focus on type senses. So here is that type sense source code. It's all free. You can, uh, install it locally. You can, uh, push it to, to your own server. So, um, on the type sense, uh, website, actually it was cool to see all the features. I didn't know that there are so many different, uh, features around the search. So obviously typo tolerance, something I have mentioned here and trying to type hot wire with, uh, typo. Uh, then there's uh, uh, I mean search for by images voice, uh, and uh, like conversational search, is this even a thing? So there are so many, like I know businesses that you can even build around the, uh, search itself. And there are a few examples of, uh, searching large data sets. So for example, a session songs I'll try to search for yesterday from the Beatles. So you see I made some typos and it's still, uh, Considering the typos found the best me result out of 32 million, uh, songs. So that's, uh, really cool. Um, yeah, let's try installing type sense in a Ruben Journals application. So here in my application, I'm going to search not only by title but also by description. And I'm gonna to use type, uh, type sense instead of, uh, the skill, uh, search that I'm doing here. So, uh, uh, to install type sense. First of all, I'm going to go to their docs. So, uh, going to their website, uh, docs guide, install type sense. So I'm not going to do it via Docker. I am not a big fan of Docker, uh, local especially. Then I'm going to run Bruin install type sense. And then I'm gonna start the type sense server. This was fast, okay, it says successfully study type sense server. And we can make a CRL request to local host 81 0 8 slash health. So by default it started the type sense at the local host 81 0 8, and you say, I made this request and it says, okay, true. Let's actually drive this in this CRL, uh, you see. So it actually has a web server running and it has this health page. Uh, looks quite cool. Uh, now how can we interact the type sense? So to do this, we have this, uh, gem type sense, uh, Ruby. So, uh, I'm going to add the gem type sense Ruby to my application. I've actually already done it. Um, and here's the pull request, and they're going to go through all the changes that I have done. So, um, here I added the G types senses, and next I added an initializer. So types senses are B, let's have a look at this, uh, file in the VS code. So I create a new types senses client, uh, with, uh, host, local host, the port 81 0 8, the one that was, uh, started the, uh, from the terminal. And the default, API key is, uh, X, Y, Z that we're using. Uh, okay, so I started the server and now I created a small, uh, service to interact with the API. So here I have this type sense service, and here I can create a schema. So types can have like collections of records of objects that you can search through. So I have a collection of named posts and the post collection has attributes, subtitled body created at and default. We're going sort by, uh, created at. So we are going to, uh, create this kind of schema inside, uh, types, and they're going to load, uh, our posts into it. And posts are going to be documents. So collections are like, uh, tables and documents are records inside the tables. So I'm going to call the types and servers from our rails console, um, types and service, uh, dot, uh, create schema. We have created the schema. Let's try retrieve the schema for post. Uh, so types and service, uh, gets schema. It's going to get the schema for post. Now you see here in my service it, everything is around post. Uh, then, uh, let's try exporting the documents. So, uh, kind of the payload of everything they have for these documents. You see at the moment it's empty. Uh, so let's load all our posts into the, uh, type sense, uh, collection of posts. Uh, to do this, I have also created, uh, rate task. So type rake, if, if you don't yet have a type senses, uh, collection created, it's going to create a collection and it's going to load all our posts into, uh, that collection. And you see we have this command index post. So for each post we're going to, uh, get the collection of posts inside, do absence, and uh, create a new post with the ID titled body and, uh, created at timestamp. So, um, I'm going to run this command. I can run it, uh, uh, either for Rails console or using Rails types and setup, run this r task. Okay, let's start the console and scan. So let's say post do count, we have 270 posts. And let's, uh, see the data that we have tried to push to types and, and see if it was successful. So I will say types and service dot, um, export documents. And you see we have a big payload of, uh, all the documents that, all the posts that we have pushed to our, uh, collection. Let's try getting a document, uh, count. So types and service dot documents count. You see it's the hundred 70 at the same amount as uh, posts in our application. So we have pushed all our posts and let's try getting a post by id. So, um, they post do first, uh, dot id and dot title. Okay, and let's, uh, uh, run types and service retrieve document and the ID will be six. And here we have, uh, the title from, uh, our Posts database and the title from Type Senses. So see it is the same record. Now we can interact with the type sense API, but we want to be able to update the, uh, collection of, uh, indexed posts in types and never something changes in the active record. So whenever a post is created, we want to add it to the index. When the post is updated, we want to update the index, and then the post is deleted. We want to remove from the index. So for this, I created the three callbacks. So, uh, after the creation, uh, Intex post, after update, update, post, after destroy, delete post, and in the steps in service, I have these defined. So find the collection of posts, find that document by the post ID and delete it or update it or create it. Okay? So, uh, let's uh, see it in action. I'm going to try to change the title of, uh, this post. So post first update title full 12. Okay, and let's find the document. You see it in the document there. Title has also been updated. So, uh, our callbacks work. Okay, now let's uh, go to the next step. So we have the types index. Now we want to do the types and search. So we also created this, uh, command search post. And uh, we are going to make this, uh, call to kind of the type sense API to our local types and server. And we're going to search the documents by this. Search parameters. We're searching by title and body of a post. The default sold is by created the at the descendant. We add some pagination and the quantity of items per page. So let's try searching. First of all, in the rails console. Um, I will say types and server dot search posts and filter will be hot via. And you see we got, uh, a big payload and now we can do it from our rails, uh, controller. We can, uh, uh, sanitize the payload and display it in the rails view. So here I have already actually implemented it. Here I have, uh, the uh, URL post slash search. And here I have, uh, a search field. So, uh, in our roots inside posts, uh, I created a collection for search. Then in the post controller, I created this dev search. And you see if there is a very parameter in this search form. Then we're going to hit the types and API from the search and render the results in a sanitize the object of both. We're going respond in JSON and in HTML, but we don't have to really respond in js. It's just, uh, an additional response type that we can have in the application. Let's even comment it out Alpha now. And so let's search for Hot Fire. Hot Fire. You see the results have been loaded and each time, uh, when we make the search, we kind of search not in uh, uh, the Rails posts database, but we search in type sense and uh, we render the results. Let's even make some typos or JSO, um, instead of JSO, and you see the results are being loaded. I can also add some kind of like additional, uh, points from anywhere in the title or description, and it's going to show the most relevant results. So we perform the search, uh, using uh, uh, type sense, it works well. Let's, uh, try the J endpoint. Let's, uh, try doing JS O. And you see we have the results in the JSO Looks really cool. Now, uh, there are a couple of more things we can do with the type sense. So uh, we can make a serial l requests with our API key to retrieve something from our collections. So let's try getting, uh, a specific document. Okay, there's no document that I did too, but I remember there is document that I did six and you see we get the payload and we can also get the stats, Jason. So the stats of our uh, type Senses server, uh, yeah, that's, uh, about it. So, uh, I'm going to leave a link to the implementation, uh, and to the types and services that I have created in the description of this video so that you can implement type sense in your Rails application. And I see the Type Sense is currently working on an additional integration except of Type Sense Ruby. It'll be Type Sense Rails. It is going to based on a Algos Rails integration. So hopefully the API is going to be really similar and, uh, it should be possibly even easier to install type Sense in Rails application. And as the final step would be to deploy to production. So, uh, now we have this type sense server running, uh, locally at uh, 8 1 0 8 and we would want to push it to production so that we can use it in our real application. And uh, if we go to the Type Sense Docs, there should be some options on how to do it, uh, how to push types to production, uh, for it to be self-hosted, but they also offer the STS cloud. So if I go here, um, what's the pricing? So 720 hours is like a month, and it is, uh, the cheaps version is, uh, $21 per month if they are hosting it. Uh, if you're not hosting it yourself. And, uh, another interesting thing I noticed, if you go to the login and go to Inspect Element, you can see that they are using Hot Wire and most likely also rails for their sars. So yeah, that's, uh, the basic implementation of type sense in Ribbon Rails application. I invite you to try type sans. --- # Global app settings with gem rails-settings-cached #224 - URL: https://superails.com/posts/gem-rails-settings-cached-for-global-app-settings-224 - Published: 2025-01-13 - Duration: 07:50 - Access: free - Topics: Gem Allow admin users to change global app settings like app name, contact email. With this gem you store settings in the database, and have a UI to change settings. This way, developers do not have to change code and re-deploy the app when configurations change. It's amazing that you can use Setting **before** Rails is initialized. Meaning, you can maybe even let admins edit API keys! - [Gem rails-settings-cached](https://github.com/huacnlee/rails-settings-cached) - [My branch](https://github.com/huacnlee/rails-settings-cached/pull/259) - [PR implemeting the gem in an app](https://github.com/yshmarov/shopify-zero/pull/3) 0:00 different approaches to app settings 1:57 install gem rails-settings-cached 4:30 Admin UI ## Chapters - 0:00 different approaches to app settings - 1:57 install gem rails-settings-cached - 4:30 Admin UI ## Transcript Hello, friends. So here I have a basic self-checkout restaurant menu and the application as many other applications have some global settings, like their application name, the application description, their contact phone number, contact email, and how would you usually approach, uh, storing this in the application. Option one is just to hot code them, but, uh, then it's not a great pattern because if the, like application name changes or phone number changes, you would want to manually change it in multiple places in the application. So it's not a very sustainable approach. One approach that I took in the specific application is I, uh, moved this kind of global settings to locales. So here I have the application title in the, the top title. I have it, uh, in the application header, and I have started to locales. So if I go to EN NVML here, I have a separate folder that I created for locales with application, name, description, and so on. So if I change the name and, uh, go back to the application here, you see it has been reflected here and here. So just one place to manage this kind of global settings. I, again, can change, like, uh, show or hide this tab. I will set it to false. And you see the tab is hidden, but it's not a very sustainable approach. But of course it requires a developer to open the code and, uh, do some, uh, changes and, uh, redeploy the application. So there's been a lot of discussion of what the good approach to storing kind of application wide settings is. So there's an idea of storing them in application rb in the application config, there's been discussions here suggesting different gems for, uh, storing application, uh, settings, uh, create a via ML file. Uh, another approach I discussed in episode 45 was, uh, the rails feature conflict. Four, that allows you to have, uh, environment specific or global, uh, settings in via ML file. I think it's a quite good approach, but it doesn't solve the problem of, uh, uh, not requiring a developer to participate in change gender application settings. So recently I discovered this, uh, really nice GM rails settings, cached that, uh, creates a database table where you would be able to store, uh, the settings and, and also have some default settings that you can override. So an admin would be able to actually access that database table and change settings without, uh, redeploying the application. So let's try installing this gem in this, uh, application. I'll, uh, run this command rails, uh, bundle, add rails, settings, cached. Uh, I'll run the install command and it's going to create a, a setting model. I could join it with, uh, like another name and it would, uh, store not in the setting model, but in another model that we would like. So anyway, let's use the setting model. So it created a migration and a model. Let's have a look at the migration. The B um, create settings. So it creates a table with the, uh, keys and, uh, values and the setting model. Here, here, we'll given a few, uh, fields, uh, that we can use, uh, uh, as an example. Okay, let's run the migration rails. Did be migrate. Let's, uh, open the rails console and go to setting, setting. Oh, there are no settings yet. Let's, uh, try and comment in one of the settings. So let's, uh, type setting host, um, let's reload setting host. Okay, here we have this, uh, host set. Um, let's, uh, move our application name to this settings. So here I will have, uh, field, uh, app, name, type, stream, and default, uh, my restaurant. Okay, uh, let's, uh, start the application. And here I'm now getting the application name from local. Instead, I will get it from setting. So I will have set, uh, app name here And Let's, uh, refresh our application. And here we have the, uh, name set as a default in the settings. So I add add one in the end, and you see it has changed here and in the tab bar. Okay. But, uh, if we go to our real console, if we go to setting dot count, you see there are no settings actually stored in the database. We're just using the default, and we want to, to let the admins see the defaults and override them if they want. So let's, uh, add an admin interface. Now, if we go to the documentation of the gem, there is an example of, uh, uh, route controllers and views that we can, uh, copy paste into our application and add the settings. I actually recently made, um, blue request into this gem, hopefully bit merged at France. It, uh, as a generated to create these, uh, uh, roots controllers and, uh, views. So it should make, uh, the process faster. I will run it from my branch. So here I have my branch rails and cached, uh, my GitHub and my branch. I will, uh, run bundle once again. Uh, now I will run rail, generate settings, admin, it'll generate the controller, uh, view, uh, roots. So let's have a look. Uh, going to our roots here. We have namespace admin resource, uh, settings. I have another name space admin here. So it's duplicated, but uh, I can put it inside. Okay. Uh, now, uh, here in our settings ib, I was injected with a few example fields. Uh, additionally for tests, uh, go into our controllers have, have this setting controller with the create action. And, uh, uh, we also have the deaf show that is, uh, not defined, but is present, uh, in the views. So here we go to views, um, settings show. Okay, let's try actually accessing it in the rails, uh, server. So going to local host slash uh, settings, oh slash admin slash settings. And here we have the application settings. So we have like the restaurant name, for example, here, have the default restaurant name. I will change it to one to three. Save settings. You see, we have this new restaurant name, and if we go to Rails console, setting, dot count, uh, setting. Oh, uh, you see these other settings have been also saved. But, uh, we also have the application name that, uh, is, uh, taken not from the default, but from what we actually have in the database. So this is really nice. And Jim also provides a lot of, uh, cool things like validations for fields that can be edited by, uh, admins, uh, read only fields that, uh, can be displayed for admins but not edited. So it's really cool. I encourage you to try this gem and, uh, uh, store global, uh, configs in the V place. And it's really cool that the admins can, uh, edit this configs. Now, um, in this, uh, I have to note that in this application I'm using the simple CSS. So I have this, uh, nostalgia script. So if I remove the nostalgia script, then the views for the settings are not, uh, really nicely styled. So you can sell them on your own, but, uh, otherwise you can use, uh, a script like simple CSS, just in this, uh, uh, single show file to make it look, uh, uh, the form look nice. So thanks version, and see in the next one. --- # ALL the Jekyll plugins you will ever need #223 - URL: https://superails.com/posts/all-the-jekyll-plugins-you-will-ever-need-223 - Published: 2024-12-29 - Duration: 33:24 - Access: free - Topics: jekyll - Playlists: Create and host static website with Jekyll 10 Jekyll plugins (ruby gems) that will turn your blog from 💩 to 🔥 Episode source code: https://github.com/corsego/220-jekyll-blog/commits/main/ Based on this blogpost: https://blog.corsego.com/jekyll-plugins 0:00 Progress so far 1:05 Navbar links 3:40 Embed google form to subscribe 5:20 Configure Minima theme 9:14 Open external links in new tab 10:48 Opengraph images 16:34 Sitemap 17:43 Jekyll Redirect From 19:33 Link to edit page on Github 22:32 Tag discovery pages 27:00 Similar posts (based on tags) 28:56 Anchor tags 31:05 Fix CI (libvips) 31:51 Fix Anchor tags duplication ## Chapters - 0:00 Progress so far - 1:05 Navbar links - 3:40 Embed google form to subscribe - 5:20 Configure Minima theme - 9:14 Open external links in new tab - 10:48 Opengraph images - 16:34 Sitemap - 17:43 Jekyll Redirect From - 19:33 Link to edit page on Github - 22:32 Tag discovery pages - 27:00 Similar posts (based on tags) - 28:56 Anchor tags - 31:05 Fix CI (libvips) - 31:51 Fix Anchor tags duplication ## Transcript Hello, friends. Previously I talked about hosting a basic, uh, static page on GitHub pages for free on your own domain. Then I talked about creating a new JI blog and post and hosting it on GitHub pages. And then I talked about, uh, extending our jiggle blog with the external tools like analytics, uh, external command system, and, uh, monetization via buy me coffee. And, uh, we added this kind of buy me coffee button. We added the, a common section. And now let's, uh, extend, uh, our GLE block even more with the JL plugins. Now, if go to our gem file of our GLE block, we have this group jiggle plugins. If we search for JI plugins, then there's like a list of different jiggle plugins that we can add. And, uh, I have my preferred plugins and they're going to try to add some of, uh, my preferred plugins to make our blog much, much better. So, uh, for doing this, let's first, uh, figure some basics out. First of all, we have, uh, our, uh, posts, but we can also have static pages, and we can post, uh, we can place static pages in the N bar. So for example, we want to have the link to the about page here on my other blog here, I have a few different, uh, links in the nav bar. So to have the link in the nav bar, we would need to extend the some settings in our, uh, theme. Now we are using this mini theme. It's, uh, theme. We have it, uh, set in our config V ml theme mini. Let's have a look at the, uh, rhythm of this mini theme. So, uh, if we look here, so the layout by default should be base, uh, I think we talked about it, uh, in the previous video. So the layout should be base. And, uh, here somewhere we have, uh, head pages. So, uh, we should set this in our confi via ml. Let's try doing it. I go to confi VML here on the theme mini, we will say head pages. We, so we will have about do md we don't have this portfolio up page. And, um, okay, we have about markdown. So it was generated with markdown, but let's use MD with markdown. I think it'll not work. Now, let's, uh, refresh. And K, you see we have this, uh, about page visible in the npa. Let's try adding another static page. Let's say, uh, uh, page to subscribe to newsletter. Let's, uh, just go with this about page. I will name it, uh, newsletter title of newsletter. The link will be slash newsletter. And here we can have a link to Google Forms, for example. Okay, uh, I will need to add the newsletter to our config via ML refresh. Now I think I need to restart the server to be sure that it picks up the changes. Okay, now we have the link to about and newsletter. I think if I swap the places in the confi via ml, and again, I will restart server. Yeah, you see the places have been swapped. Okay, so we have added the, uh, custom static page that is not post, and we've added to, to the nal. Let's save our changes. So, uh, static pages in nal. Okay, looks good. Okay. Now we have this newsletter page, but we want to let users subscribe. Now, you can use, uh, any newsletter management tool, but, uh, if you want to be very simple, you can just have a Google form that you embed here, and you can start letting users emails, ASAP. So here I go to Google Forms, uh, uh, subscriptions. Uh, question, uh, will be short answer, uh, your email. Okay, publish. Uh, now we don't need to just have a link. We need to embed it. I think, uh, here have this embed, HTML. Let's, uh, copy it. We'll now paste it inside our newsletter. Okay, let's, uh, go back and wonderful. Uh, now users can subscribe. We can, uh, also change the width and the height. Let's make like width. Uh, for example, let's make the height the 600. Let's see if anything changes. Okay, now we don't have the scroll, so looks better. And just like this, we can let use, we can start collecting emails very fast. So, um, embed, uh, Google form to subscribe. Okay, uh, going further, let's extend the minimum theme further. So, uh, if we go to the docs, we can, uh, see different, uh, uh, color schemes. So by default, we have the skin set to, uh, classic. So it's just a bite mode. And let's try changing the skin to something else like a skin dog. So, going to the config, we will, uh, say skin. It will be dark for minimum. I will, uh, restart the server. And you see it's, uh, now a blog is, uh, all in dark mode. Looks, uh, fine. You see the text is, uh, white. So it is, uh, readable. Uh, there are other options outta the box, like, uh, uh, solarized, solarized lights, solarized Dark. Let's try one of these. Let's try solarized. Okay, here's another theme, but, uh, let's try keeping it dark for now. Okay? And, uh, then there's another thing with social links. So, uh, here, I already previously put with a username, GitHub username, but, uh, they are not displayed in the folder. And, uh, the Synex has been updated for these in mini. So, uh, yeah, let's also put the author. It suggests put in the author. Uh, let's, uh, before I save, let's see if, uh, we have the author of the blog in Metatags already present. Go into head author. You see this GitHub user, so it's definitely not what you want. Uh, they're going to set the author to your name. Okay, let's, uh, restart. And you see the author is the name that isolated. So it is not just GitHub user name. So I think it is quite important to set the author, uh, going forward, what you have this, uh, social links. So let's, uh, add the social links. Um, GitHub, uh, Twitter, uh, I don't think we need these, uh, progress links anymore. Now let's, uh, restart and, uh, just have a look here. So, at the moment, if we don't have the social links in the filter, I refresh. And you see now we have the link to, uh, Twitter and to GitHub. And, uh, you can set links to like blue sky and to all the other social networks easily. So, uh, yeah, then there's this show experts thing. Let's, uh, try adding it. So I think it's going to give a show description of each post in the, um, landing page. So let's say show experts true. I don't think it should be nested inside mini. It's, uh, there is some weirdness, like I would, uh, some things need to be nested within minimum, some don't. Okay. You see, show experts works. So I have like a shorter description, a short preview of the blog post, so the users are more incentivized to open and read more. Okay? Uh, yeah, this is more or less everything I would be adding by default. Uh, let's save our changes. So extend minimum or, or configure minimum. Okay, so we've done this. We've done this. Uh, uh, that's another really cool thing I think we need to add. So here we have a blog post, and, uh, let's say we have, uh, internal links and external links. So these are external links. And you say, I click on an external link and it just, uh, makes me leave my, uh, blog and, uh, redirects me to another page. But, uh, usually you want to keep your users inside your ecosystem. So maybe you would want to open internal links, like links to other pages in your app, always, uh, uh, on the same browser tab, but the external links as, uh, in, in a new browser tab. So for this, we're going to install the, uh, JL target blank plugin. Let's, uh, try adding it. So I'm adding in this, uh, jam do gem file inside the JE l plugins bundle. Now, uh, in plugins, in config v ml, I need to add the, the gem have, have JE field. Now I'm adding JL target blank. I am, uh, starting the server once again. Uh, and now I expect to click on this link and it'll open in a new tab. So does it work? Yes. So now all external links open in a new tab, I think. Uh, this is great. Let's save our changes. So open external links in your tab. Okay, uh, going further, uh, J-L-S-E-O tags. So, uh, another important, uh, plugin to make your post more discoverable by search engines. Let's try inspected the, uh, head of a post. And the, you see, we actually already have a description, uh, uh, a title, uh, yeah. So we already have some, uh, meta tag set, uh, because, uh, the mini mini already comes with this J-L-S-E-O tag. Let's even find it here. Kill SEO Oh, no, I'm looking at the wrong place. Yes, plugins mini comes with J-L-S-E-O tag already. So we don't really need to install it because the gem is already present. But if you're not using the mini, I would highly recommend you to install the gem SEO tag and, uh, add the meta tag to your blog post. Uh, going further. So this is a plugin and you JL plugin developed by Igor Alexandro. It's really, really great. It, uh, gives, uh, um, it creates open draft images for your JL post. So, uh, you just don't share just a link. You share a link with the preview, so it is much more clickable. Let's try installing this, uh, gem. So I'm adding JL OG image In the GEM file. Then, uh, we need to have JLE tag and JL OG image in the plugins. Um, then we need to, to set the config, or yeah, let's try not setting the config for how the open draft images are generated. Let's, uh, just run bundle. Let's, uh, restart. JE open our block. And, uh, yes, you see it generated these images. Isn't this wonderful? It generated the, uh, images for our two block posts, but you see, they're not very nice, so let's customize them a bit. So, um, going back to the GEM GL OG image, let's take, uh, this example and, uh, I will, uh, paste it here. So, uh, we can have like a title image. Let's, uh, let me put my face as a title image. I'll go to my other blog here. In the about section I have, uh, a photo of myself. I will, uh, put this photo somewhere in the assets, uh, images. I will name it, YA or Avatar. So I have assets, images, I will, uh, put the avatar. Okay, avatar, png. Yeah, png. Now I'm going to delete these previously generated images. I will, uh, restart, uh, JL because I made some changes in the config file. I will, uh, Render the page. And, uh, yeah, it looks like, uh, Nothing, uh, was generated based on this. Why. So, okay, so I'm trying again. And, uh, I, this error images do not have same number of bands. So, uh, I was trying this avatar, maybe there's some problem, but I tried with another image that is actually A JPG, not PNG. And, uh, it should work. So let's try this. Image avatar two JPG. Maybe the gym doesn't support all, uh, types of images. And, uh, you see in the open graph folder, we have two new images. And you see they are now better out. We should also maybe change that domain. So I'll put, uh, my domain here, like, uh, super ails.com. I will stop the server. I will, uh, remove the images and start the server once scan. So the nice images have been generated and, uh, looks good. So now, uh, let's, uh, check our head. And in the head we have this OG image, and it is the part of the image. Uh, welcome to JL png. Yeah, it tries to find the PNG. Uh, for some reason, uh, it doesn't find the image. Now, actually, uh, I think I couldn't find it because I need to restart the minimum once again for it to pick up the new folder. And, uh, now I restarted. And, uh, now everything works. And actually like this, you can have access to the assets. So here I go to parent directory. Here have like, uh, via in images, we can open og, uh, post here, have the images. So, okay, looks good. We have added the, uh, OpenGraph images, uh, with the GEM J OG images, let's say our changes. So, Okay, what is next? So we have, uh, added the SEO text. We had them in mima. We added the open draft images. Now let's add a site map. Site maps are usually important, uh, for search engines. So, uh, here's the GEM JL site map. Let's, uh, add to our gem file, uh, in plugins. So going into config in plugins, we added JL site map. And, uh, now before we start, uh, before we're on bundle, let's, uh, just double check. So I will go to sitemap dot xml. You see there is no site map here at the moment. And now I will run bundle, start the server once, scan, refresh the page. And here you see we have a site map generated for all the pages, uh, in our JL block. Isn't this wonderful? So like this, we get the free site map for the block, uh, JLL at site site map. Okay, what is next? We have added the site map. And here's another nice gem. So in theory, you can have like multiple URLs directing to the same, uh, page in your block. Or, for example, you had an old URL and you want to redirect it to a new URL. For example, you want to redirect the page slash hello to a specific, uh, uh, blog post or specific page in your blog. So for this, we can add the JL redirect, uh, from, uh, I don't really use this often, but there can be some cases where you would want to redirect, uh, links to other pages. So I'm adding check redirect from adding it to the plugins bundle, starting the server. And, uh, so they're going to add the, some redirects. Let's go to a blog post that have, we have our posts in the posts here, no, those assets. So we have under scope posts. And, uh, here, let's add the redirect from slash hello. So whenever somebody visits the slash hello page, it should redirect us to this blog post. Let's, uh, try, I'm going to, hello. And you see it redirects uh, slash hello to this blog post. So, uh, uh, add redirect from. Okay, uh, next we have, uh, yeah, links to edit a specific GitHub, uh, page for me. It is very comfortable. For example, I am looking at my existing jiggle block here, and I see a problem on this page. And I can, uh, from my phone or from anywhere, I can click on edit this page. I will have this page opened in the, uh, GitHub, and I will be able to quickly edit the specific page without, uh, going through all the navigation. So, um, to add the, this, uh, I will add this GitHub edit, URL in my config. It's, uh, just a variable name that I'm given, but this GitHub edit, URL, is going to be accessible anywhere in your pages. And the URL is going to be to your, uh, repository. So in our case, the repository is slash cogo slash 220 JL blog. And we have the branch that is main, not master. So repository blob slash main. And now we're in the layout of each post. Let's open the layouts post. So here, uh, anywhere we can add the link to edit this page on GitHub. Um, here I added it as an a tag. Mm. Let's, uh, see. So here we can have the link to edit this page. And, uh, here we would maybe want to have, uh, uh, the GitHub editor l or you could just have, uh, this kind of path. Now I see a mistake here, so I have like an absolute path written here. And, uh, I, I'm not going to hardcode the absolute path for each, uh, uh, blog post. Let's see how I actually did it in this other repository. Uh, going to, uh, layouts post edit. Yeah, here's the correct, uh, a tag. So here have the site, GitHub edit, URL that we defined, uh, here. And we have the page path that is going to be determined. Okay, let's, uh, restart our server. Go to our blog. And here on top of each page you have this added, this page that is going to try to open this page, uh, in GitHub. So editing will be easier. Looks good. Let's save, uh, our changes. So add link to edit page. Okay. Now another powerful discovery feature that I really like using I inject is tags. So you can, uh, add tags to any blog post and, uh, find similar blog posts based on these, uh, tags. So, going to our blog at the moment. Um, here we have this, uh, post, it doesn't have any tags and neither does this one. So let's add a couple of tags. Let's say tags, Ruby and Rails. And this one will have just, uh, uh, tags, Ruby and uh, je. Okay. And uh, now we are going to add links to, uh, tag pages. So we click on the tag Ruby, and see all the posts for this tag. And we're going to do this with the uh, gem JL tag. Let's add this gem, uh, j tag tag to our gem file. Okay, bundle. Now, uh, We need to create this plugins XT dot IB file file. So, uh, create a new folder plugins, okay, I already created it. And I will have XT rrb file inside. I'll have to put this, uh, JL tag require, 'cause it's going to generate like new and a new part of the app that, uh, hasnt existed before. It's going to generate like new pages for all the tags. And in config via ml, I will say tag page, layout, tag page, tag page, directory tag. Okay, let's go to our config. Now, uh, we are going to add a layout page for our tags. So, uh, layouts, tech page, HTML. Uh, here in the example we have layout default, but we need to use layout base for, to actually work. And, uh, let's see if, uh, it now generates tech pages. So going to our app, uh, going to the site folder, we have a tag, a new folder named tag or could, we could name it tags. And have, we have a tag for Rails and Ruby, let's, uh, go to our local host and go to slash Ruby, um, tag slash Ruby. Okay. And here you see all the posts with the tag, Ruby. So here I have the posts. I click on tag rails, two posts, uh, yeah, one post with rails, two posts with the ruby, one post with Jekyll. So now we have, uh, basic tag navigation. And this on below here I think is a tag cloud. So a, the cloud like list of all, all the tags, uh, in the application. So these are both tagged with this tag. And these are like all the tags, let's name say, uh, will have a break and all tags. So this way we have this kind of basic discovery feature. Now let's try displaying the tags on a post page. I think I had an example here. So in the post layout, uh, layouts post before the article, uh, I will have a list of all the tags for this, uh, article. I will refresh. And here you see we have the edit this page and we have all the tags. Let's, uh, add a break, okay, or a row. Yeah. So edit this page tags, uh, the title. Maybe we want to put the tags under the title, but, uh, it's, uh, enough on us here. I will also remove the tags, uh, title because it should be self, uh, explanatory. So Ruby and Jakel are the two tags on this blog post. So I go to Ruby, I go to this one, one, and I see the two tags that are present here. Looks good. So we have basic tag, feature, um, use, uh, add tag pages, And I'll also add a link to the gem. Okay, wonderful. And, uh, another thing would be, uh, having a list of similar posts. So imagine you finished reading on this and on the bottom of everything, so that it is easier for users to find other interesting content. That would be a link to similar posts. Similar posts, again, based on tags. So we're going to use this GEM JL tag and related posts. Another gem based on tags. So I really encourage you to add tags to all logical posts. It'll help the discovery a lot. Um, okay, add this in config via ml. I'm going to also add the line here. Mm. And on the bottom of the post layout, I'm going to add this, uh, line. So after the comment section, now let's restart the server run bundle. Okay, and going back to our blog post here, you see after the comment section, we have this related post. And, uh, we have this welcome to JE post, why it's related, because it has, uh, tags in common. And on my other blog, it works like this. So here on the homepage, I have different blog posts, I have tags. I click on the tag, I see all the blog posts tag with this, uh, tag. Uh, and uh, I go down on any blog post. And on the end I have this, you might also like, and links to other blog posts. So it's another discovery feature for the user to stay captivated on your blog. Let's, uh, save our changes. So, um, similar posts at the end of post. Okay. Now another thing I would also add to the blog is anchor tags. So here is, uh, considerably long blog post. And, uh, what if you wanted to link to a specific part of the blog post, for example, to this head of tag. So I have this, uh, sign and I can, uh, have the URL to this specific anchor tag. It is very comfortable for long blog posts. So for this, we're going to add the A script. Um, here it is. So here's how I added anchor text to another app, and, uh, what do I need to do? So in includes folder, I need to add the anchor headings, html. Let's go Here we have Includes and Goins, HTML. I will, uh, go with this. That's quite a lot of code, but, uh, yeah, maybe it should be extracted into a gem. I think that a nice, uh, idea. And in the layout, in the default layout, I can add the, this anchor tag initializer. Let's see, our layouts, we don't have the default layout extracted. Maybe we can do just in the, uh, posts. So, uh, let's add this, uh, tag. And now I will go back to our, uh, app. I will again just start it just in case, 'cause I added this new includes. And, uh, let's, uh, add some head ins to our posts. So inside the post, uh, I will add the, another heading, save the post. Go to that, uh, post. And you see we have the ANCA tag. So, uh, wonderful. Let's save inches. Hey guys, I'm recording this as a last minute addition to the episode. So I noticed that when trying to deploy to, uh, production, we get, uh, an error in the build. And the error says that we don't have, uh, whips, uh, uh, installed. So we need to add the lib whips to our repository. And we need it because we have the GM GLE open graph image, I think. So, uh, you can do it by adding these lines into the jiggle vml, uh, command. So, uh, uh, app can install, uh, lifts. I added it, uh, here to the ci. And now deploy into production worked another problem that I, uh, encountered while trying to deploy to production. So, uh, if I open the blog post, you see it is rendered, uh, device, and, uh, it is because of, uh, us adding these, uh, uh, anchor tags. So, uh, to make the anchor tags work properly. Here, you see, we put this anchor headings, uh, in the post layout, but instead, we should put them in the base, uh, layout instead of the content. So going to the, uh, G mini or or j we are going to import the base, uh, layout. So layouts, mm, base, I'm going to add this file, uh, locally in our layouts. And, uh, I'm going to remove these anchor headings from our post. So I'm not saving yet. Let's look at our post. See, see, it is rendered twice as remove it. Now it's run advance. And in the base layout instead of the content, I'm going to replace the content with this, uh, anchor headings, h tml that they generated for our content that has the anchor head. So I'm removing the content here. Refreshing and, uh, use of the content is been rendered, uh, just once, but, uh, the anchor head work. So yeah, fixing some, uh, small box. --- # Jekyll site - add comments, analytics, monetisation #222 - URL: https://superails.com/posts/jekyll-site-add-comments-analytics-monetisation - Published: 2024-12-24 - Duration: 17:38 - Access: free - Topics: jekyll - Playlists: Create and host static website with Jekyll Let's add some external tools to our Jekyll site: - Plausible analytics for tracking visits - Giscus (Disqus alternative) for comments via Github Discussions - Buy Me A Coffee for basic monetisation Most importantly: We will deploy Jekyll via our own CI. We will learn to override layout defaults. Episode source code: https://github.com/corsego/220-jekyll-blog 0:00 Add comments with Giscus 9:30 Add Plausible Analytics, Github Actions, override theme defaults 15:38 Add BuyMeACoffee ## Chapters - 0:00 Add comments with Giscus - 9:30 Add Plausible Analytics, Github Actions, override theme defaults - 15:38 Add BuyMeACoffee ## Transcript Hey guys. So previously we have created a basic, uh, JL blog, and we have hosted it on GitHub pages for free on our custom domain. So here we have it running on our domain, and here we have the same blog running on local host. And now we are going to add some, uh, external plugins to make, uh, our blog even better. So, first of all, we're going to add comments. Uh, so for example, here in my, uh, blog, I have, uh, GitHub comments, uh, under each blog post. So users can leave blog posts, and I can even moderate them. Uh, then they're going to add the analytics. So I'm using plausible analytics, uh, for my blog. And, uh, you'll be able to select the stats of your blog, uh, in the same way. And we can also even consider adding, uh, a buy me a coffee button that is quite common on blogs these days. So, uh, first of all with a comment section. Uh, historically the most, uh, common way to add comments has been this discuss that, uh, has worked well for many years. But now this discuss is going to add ads into your comment section. And I really don't want to add in my comment section. And this is kind of the last job for me. Also, discuss, always felt like a, a very big tool for a very small, uh, uh, problem. And I had the feeling that there should be a simple way. And, uh, you can do it for GitHub. So there is a tool called Utterances that, uh, at comments, uh, based on GitHub issues, but there is an even, uh, better tool. Um, it's called gco that is based not on GitHub issues, but on GitHub discussions. So, GitHub discussions is, uh, like an quite new, uh, new added GitHub tool, uh, that, uh, didn't exist when utterances was created. And when it falls, uh, when the GitHub discussions have been created that GI discuss, um, tool has been created. So GI CS is better than utterances, and utterances is, uh, better than discuss. So let's try adding, um, discuss to our, uh, post in our blog. Uh, here I'm on the homepage of discuss, and let's just follow, uh, the readin. So repository, let's, uh, see what's the repository? URL. Okay. Uh, so the criteria is not met. It should be public. Uh, GCA app should be installed, uh, in our GitHub, uh, profile and discussions feature should be enabled in the repository. So here in GitHub X apps, I, uh, should install GI have already actually, uh, done it for some app. I think I need to do it for another app. So in my profile, uh, let me log in. Uh, the repository will, will be today. I learned blog. Okay, save. And then, uh, I need to enable discussions feature on the repository. So going to this, uh, uh, block of mine going to settings, we have the feature discussions. So I need to enable discussions. Okay, let's, uh, go back the installer. Uh, let's try again. I will, I'll refresh fresh. You see success. The repository meets the criteria. Uh, the discussion title contains a path name. So like, how should each discussion be identified? Uh, if I go to discussions on my other blog, here are the kind of, uh, slugs, uh, based on which the discussions are identified. Um, it can be based on the, uh, birth name. That's, that's fine. Uh, then, uh, discussion category. Uh, I think it doesn't matter much. Uh, features, uh, enable reactions, yes. Um, emit metadata, uh, doesn't really matter. Place the comment box above the comments. So, uh, if I go to my other blog, I have the possibility to create a new comment on top, but if I change it, it can be on bottom. I want to have it on top. So, um, comment box above the comments, uh, load lazily. So only when, uh, the user scrolls to the bottom, then the comments are going to be loaded. I think that's, uh, also good for the speed of loading the page. Uh, then the color. Let's use, uh, just default. Uh, and here we have a script that has been updated based on, uh, uh, everything we selected here. And we can add the script, uh, under our post in our post layout. So let's go to our basic Google blog. And you see we have our web pages, and inside the post we have the layout post. But, uh, we don't have actually the layout post file anywhere in our blog. So we would need to import the layout file from, uh, uh, our remote, uh, theme that is mil. See here we are using the theme mil. We have the gem mil installed here. So here we have the G mil. I'm going to, uh, open layouts, and I'm going to copy the post layout. So here I'm going to create a new folder and the Cisco layouts inside, I will have, uh, post do html, and I will just copy all of this. And let's put something, uh, on top just to make sure that, uh, uh, we are rendering, uh, imported layout. Now, let's, uh, restart just in case. Okay, so we have, we have our text for imported layout, but, uh, it is not rendering really nicely. Okay? So this one is kind of a weird one. I had to change layout, default to layout, uh, uh, base to default. So in the post layout that I imported, uh, I'm changing it to default. And you see now it renders and it also renders with this, uh, additional, uh, text that I, uh, put in. So, imported layout. Okay, so we have imported the post, uh, layout file, and now we can add the g discuss script. I will put it, uh, like somewhere at the end, I, uh, let's see if it works. So refreshing. Okay. And the comment section has loaded, uh, what will change if I post it, uh, on top of the article, it's going to read on top. So I think I will leave it as it was under the article tag. Okay. And wonderful. I can now leave comments. Now, I can't leave, uh, comments, uh, until I sign in with the GitHub. Let me sign in. Let's say hello. Um, it's trying to make the first request taking a vial. In the meantime, we can try it on our, uh, repository. Have a look at discussions. Okay, it hasn't been posted yet. I think it font work until I set a category Id. You see here, I left them blank. So going back to discuss, I'm going to select a category. Oh, yeah, I refreshed, so I'll have to do it all over again. Okay. Past name, uh, category, general box about the comments, load lazily, open a script. Okay, let's refresh comment. Okay, you see the comment has been posted. I can even leave a direction. And if you open the same discussion in the, our discussions section inside our repository, here it is. Wonderful. So you can moderate the discussions out of here. Looks good. So we have added the discuss comments for our comment section of our Jacob block. Now let's try adding the analytics like plausible analytics. I will go and create a new analytics, uh, website. So the domain name is alet.com. Uh, install plausible. Here's the script. I will want to put it, uh, in the general layout of my, uh, app. So I think I will need to import, uh, something else from mini, let's have, uh, a look. I'll go to Includes, and that should be the custom head theme. So I will create another folder name. The underscore includes inside custom head, and, uh, inside. I'll just put the script. Uh, let's see if, uh, the script renders. I'm going to go to inspect, find the plausible. I don't see it. Let's try refreshing. Okay, so I actually found why, uh, the script doesn't, uh, render. So, and I also understood why the layout has to be default or, and not base as we have set in the mini, in the layouts here, you see, we have base, but I had to set it to default. So the point is that, uh, by default, gem Mini is pointing somewhere else, not to the GitHub, uh, uh, source of mini. So here I'm going to have to explicitly say that I'm going to use mini from GitHub, because on RubyGems, I suppose it's not, uh, like the same version as in GitHub. So, um, I'm going to use, uh, this version of mini and let's run JL Serve. Okay, refreshing. You see now I have, uh, layer broken again, I'm going to set the IT to base, not to default. Okay? And now it should also pick up the custom head. So I will go to inspect head. Uh, I have a plausible edit, so it works. And now let's just finish up. I will, uh, start collecting data. It's checking if the installation has, uh, been added. Well, we have it locally, but we don't have it in production, so it'll not be successful. Now, we'll try to first push to production and then wait, let's actually do the, do the push. So we did just a couple of small changes. Um, we added comments with the GIZ cost, and we added plausible analytics. Let's, uh, push see if the app, uh, compiles and, uh, let's see in our actions. So it's built in. Okay, so it looks like the build was, uh, successful. You see, we had this, uh, error because, uh, uh, we did not deploy to production yet. Let's see, in production, do we have the comments? No. Uh oh. Yeah, we do have the comments, but the layout is, uh, uh, broken. And let's see if plausible works. Let's, uh, also have a look in the inspector. So, yeah, uh, plausible will most likely not work now. Okay, so what went wrong with our production deploy? Um, I think what we need to change is, uh, in our settings, uh, in pages, we have this, uh, default, uh, just build from branch, but we would want to say that we want to use our custom GitHub, uh, actions. So let's add the GitHub actions for JL. Let's, uh, add this workflow file and see if, uh, the app builds, uh, with it. So I think it should trigger a new build. And I will also push pull from remote to get the JL ci v ml file locally. Okay, so the build failed. Let's have a look. So the current run was detected as self-hosted. Yeah, so, uh, actually solved this problem recently. Let's have a look at how my digital contact works in, uh, another app. Um, so I set Ubuntu 2 22 0 4. Uh, at the moment, something doesn't work with the Ubuntu 24, and I set Ruby, uh, at V one. Okay, let's, uh, push once again and see if it deploys. Okay, I get another error that we need Ruby version 3.2 plus. And in the other block I use 3.3 0.3. So, uh, here I have, uh, used Ruby with Ruby version 3.1. Let's use 3.3 0.3. I'm going to push again and see if it deploys. Okay, so now it deployed successfully. Let's refresh the page and see if, uh, it works here in production. Okay, it works. Let's have a look. If, uh, the plausible script is available in the head, yes, it is available. Let's now. Okay, yeah, it already picked up. It says that there is one unique visitor on this specific page. So analytics works. You see, uh, we had to do quite a lot of, uh, extensions customizations, uh, on top of the default theme to make, uh, our extensions work. But, uh, sooner or later, you'll definitely want to, uh, run JL Workflow, uh, via your own CI file. You will want to import files to make the app really yours and really unique. Okay, so we've installed two extensions. We can also try and buy me a coffee. So, uh, let's try, uh, adding the website fidget. Um, okay, let's go with the code. I will put it, uh, I think in the custom head. Let's see if it trend does locally. Yes, fund will we have the buy me a coffee widget, I can click and it'll, uh, give the, uh, popup add, buy me a coffee. Okay. Uh, wonderful. Now finally, one more customization. Uh, here I see in the UL we have, uh, tag one tag, uh, uh, tool, the date stamp, and then the title of the post. So our URLs are quite strange, and I would like to just have the title of the post in our URL and to do so, I'm going to just add a line in our contact file. Perma link is going to be the title of, uh, post, uh, I found this in the JL documentation. So let's me restart the server. Okay, now it's, uh, not working. This old DRL is not working anymore. And now if I go to a post, you see the link is just the title of the post, no, HDML, no slashes, no date, just the title or the post. So looks good. And, uh, yes, that's it. So here's how you can add, uh, external plugins to your gel and change some patterns. --- # You don't have a blog? Are you even a Web Developer? Create a blog with Jekyll! #221 - URL: https://superails.com/posts/you-don-t-have-a-blog-are-you-even-a-web-developer-create-a-blog-with-jekyll-221 - Published: 2024-12-24 - Duration: 13:06 - Access: free - Topics: jekyll - Playlists: Create and host static website with Jekyll Use Jekyll: https://jekyllrb.com Episode source code: https://github.com/corsego/220-jekyll-blog My blog that uses Jekyll: https://github.com/yshmarov/yshmarov.github.io 0:00 Install & customise Jekyll 05:58 Deploy to Production 07:15 Create a new blogpost ## Chapters - 0:00 Install & customise Jekyll - 05:58 Deploy to Production - 07:15 Create a new blogpost ## Transcript Hey guys. So in the previous episode, we created a new GitHub repository and deployed it, uh, on a, uh, separate website, uh, on its own domain using GitHub pages for free. So here are the send of the GitHub pages. And now, so we just have a one page H TM L website. We are going to use GitHub pages to turn this one page website into a real blog. So here's again, a wonderful example of using GitHub pages. Here's my blog with lots of blog posts with a different static pages. You can navigate by, uh, tags. You can open a single blog post, see images, uh, potentially embed the videos. I think I have some videos embedded the, um, somewhere like here. So you can just own the whole website. Let's try adding the JL to our existing repository where we just currently have this index, HTML and, uh, CNAME file. So I'm gonna do the JE L homepage. I'm going to run GEM install bundle JE L. But, uh, first I checked out this repository locally. So I have run, uh, this repository locally here. If I go to the code, I see I have the CNAME and index H TM L file. So I will say Gem install, bundler jl, uh, jl, uh, my new awesome site, whatever. Uh, now I run this kind, uh, inside my repository, so I don't want to like nest it even deeper. I'll just move everything out of, uh, uh, the file, the folder that I have just generated for it to be in the root folder. And I'm going to delete this, my awesome site, uh, folder. And, uh, I can also delete this index html file that I have previously created. So I replace everything in this, uh, uh, repository except the cname. It stays with the, what I have generated with the je. Let's have a look in the GI changes. So here we have the GEM file, where we have Jekyll Gem and we also have the GEM Mini. It is, uh, front and template for, uh, rubbing the styling of your, uh, blog. Uh, the is a plugin will come, uh, to the plugins later, but fun plugin that is installed by default and some other gems to just make the full thin work. When you run like rail generate, uh, create new rails application, you have a gem file with like maybe 20 gems installed. Here. There are, uh, fewer gems, uh, at least in the gem file. You have, uh, this index markdown. So it's kind of the, uh, home, uh, page, I assume, uh, an about page that you can populate with what you want. And, uh, we have a posts, uh, folder, uh, the most important folder where you're going to keep all your posts. So all my posts, each, uh, file for this is kept in the post folder. And you see they're all in markdown. And the, uh, config file where you can set the defaults, uh, around your application, like the, uh, title of your app, for example. Anyway, we have generated this, uh, new app. Let's, uh, try run it on local host. So going back to the JE l docs, I'm going to say, uh, bundle execute je l serve. I'll just run bundle on scan, and, uh, by default it is going to serve on local host, uh, 4,000. So let's visit local host, uh, uh, 4,000. And here we have our basic block. Now, uh, we have the title that is, uh, again defined here in config. You see, you can over write the title. So, uh, today I learned blog by YA o. We can update the title. It'll not get updated straight away because whenever you change something in config, you need to restart the server to see the changes. So you see now I restart the server and I see the, uh, changes. I have, uh, an about page where you would usually put, uh, like, uh, information about your services, about yourself, maybe your email address. Also, by the default, there is an example link to a GitHub page at Vita page, uh, your email. So let's make it, uh, more personal. Now, in that video username, I would put my, uh, username, GitHub username, uh, email, uh, description. You can, uh, edit later. But, uh, I think it would be, yeah, here's a description, this, see it, uh, on the bottom again, you can say like, again, a couple of words about yourself or why you are actually writing this blog. Um, let's try adding something in this index markdown. Yes, you see, so this is, uh, kind of the layout file. And whatever you put, uh, here is going to be rendered above, uh, the list of your post. And you see we have this subscribed via RS button. Uh, so I think it is provided by this JL feed, the, I think it's the JLRS feed, the, uh, plugin. You see, we have this JL feed plugin, and we have the theme set to be minimal from the GEM mini. Okay, so we have the basic installation of jl and we have a blog running. Let's, uh, save our changes. Um, I'll, uh, just not be committing this, uh, small node that I just did, uh, create JL blog, and I will push and, uh, now I think whenever I push it is going to trigger a new deployment. So here you see, I just, uh, pushed to GitHub and, uh, it triggered. And action. So there's the action, uh, page build and deployment. And, uh, we don't have a folder of like, uh, GitHub, uh, actions, uh, set in the application, but it still has this workflow because we have it in our settings pages. Uh, uh, here we say build and deployment, deploy from a branch. Uh, we could manually have a GitHub actions, uh, uh, deployment I ml file if you have our own setup. But if you don't have like anything non-standard, you can just have this deployed from a branch. Okay, so it should have deployed. Let's try, uh, refreshing our website. Boom. You see you have the jiggle blog running. Wonderful. Let's try at an, uh, blog post. So, um, in our posts, uh, again, we have, uh, first of all, the timestamp, uh, name of the blog, uh, post as it is going to appear in the URL. So if you visit this blog post, here, you see, uh, the timestamp, the URL, let's create a new blog post. So here, inside post, I'm just going to manually create a new file. 2024, 12, can do it in the past. So 20, uh, I want to blog or j versus WordPress, md. Then, uh, I'm going to go to another blog post, and I'm going to copy the header. So, uh, we need layout, post layout post for all the posts. Uh, should I use JL or blog post for bill? The title, uh, date, um, I don't think you actually have to set it if you have the date, uh, in the, uh, markup. But, uh, let's leave it. And categories, ZEKEL and WordPress. Uh, let's add some kind of text. Let's try adding and the image. So, uh, for adding images, I think we would need to, uh, put them somewhere. Now, uh, we can create a folder for them. Let's see how I do it in, uh, another application. So here in this circle block, I have a folder for, uh, assets. Let's create a folder for assets. Inside. I will have, uh, uh, images, or you can just have it directly in assets. Let's see how, see how I do it here? Yeah, I have some folders, but I also have, uh, some images just in the route. Let's add the assets for posts. So just a folder for all the posts. And here I'm going to input some kind of image. It can be just a screenshot or, uh, whatever. I will, uh, put it here. Let's, uh, open this in finder. I'll put it into the posts. Okay, so we have a screenshot inside our posts. Let's give it a better title. So a screenshot, okay. And we want to display this screenshot, uh, in our, uh, app. So I'm going to copy the relative path. I will put it here. And let's just pause, uh, on this. So, uh, let's go to our blog. You see, I refresh. And as the blog has been running, you see I have, uh, another post that's has been added. Uh, it has been added with today's date, not with the, uh, the date that I put here, because we have today's date, uh, and the date timestamp. Let's refresh. Okay. Not found with this date, but if I refresh here, you see I have the date. What if don't have the date set at all. Uh, it is still the 20th. Let's try changing to the 21st. Refreshing. Okay, so you see, if you don't explicitly set the date, uh, in the, the, uh, header, then it'll inherit from, uh, the date in the title. And I think, uh, it's better to inherit from the date in the title. Um, okay. And, uh, now we'll go to the post and try to display the screenshot. So this is the path, and, uh, we would need to have exclamation mark. It's already mag on, syntax, it, whatever, like, uh, screenshot. So it's the title. And, uh, he would have the path. Let's refresh. Okay. It, uh, doesn't work. Maybe we need to have slash Okay. And you see, we render an image. Let's try. Uh, yeah, you see, we still have the Jekyll and Jekyll IB in the titles here, so that's not very nice. Um, um, yeah, we can update this, uh, later, but the, oh, no, I think we don't need to update it later. I think, uh, we just needed to restart the server after making these changes. Let's, uh, try once again. I have little start server. Okay. And, uh, all these have been updated. So we have created our first, uh, blog post of our own, not one that was provided by the example. And we customized our website a tiny bit. Let's push our changes. So, uh, create a post. Okay, let's deploy again. The push it, uh, triggers, um, action to deploy the website. And now when I, uh, refresh the page after the deployment is successful, I expect to see, uh, my new blog post, uh, and, uh, well, and the image inside it. Okay, deployment finished. Let's refresh here. We have a new blog post, and the image has been rendered. Congratulations. So this is the basic of, uh, creating a blog with je. And in the next episode, I plan to go a bit deeper into markdown itself. I think you as a developer should know how to, uh, use markdown, at least for reads, uh, that are readable to your repositories. But, uh, really markdown is going to be big in 2025, believe me. --- # Host a static website on Github Pages on your own domain for free #220 - URL: https://superails.com/posts/host-a-static-website-on-github-pages-on-your-own-domain-for-free-220 - Published: 2024-12-24 - Duration: 11:38 - Access: free - Topics: jekyll - Playlists: Create and host static website with Jekyll Am I even a Web Developer, if I don't have a website? Episode source code: https://github.com/corsego/220-jekyll-blog Text version: https://blog.corsego.com/jekyll-blog-github-pages Verify a domain with Github: https://github.com/settings/pages_verified_domains/new 0:00 about Github Pages 2:37 create new repo, deploy on Github Pages 04:05 verify a domain with Github 05:40 serve website on your own domain ## Chapters - 0:00 about Github Pages - 2:37 create new repo, deploy on Github Pages - 04:05 verify a domain with Github - 05:40 serve website on your own domain ## Transcript Am I even a web developer? If I don't have my own website? Am I even a programmer if I don't use Git and Markdown to write my blog posts? Well, here in front of you is my, uh, blog. I've been dragging my blog for around four years now. And, uh, I usually write in the format today I learned or, uh, knows to self. It is the easiest way to write as a developer. And, uh, it is also a great way of summarizing your recent learnings. I often use my blog to self reference and, uh, to find information that, uh, I learned the hard way sometime ago and, uh, forgot. And, uh, if you are a programmer, I hardly, I strongly recommend you to write your own blog. Now, for this blog I'm using, uh, je uh, before Jekyll, I tried WordPress. I even tried, uh, Google Blogger, and it was, uh, horrendous. As a developer, I really want to use markdown and, uh, git, uh, markdown for format in my blog posts and, uh, GI for saving, uh, all my changes and having all my change history of my blog in the, uh, readable, readable way. And, um, Jack is, uh, a wonderful tool that, uh, uh, lets you write a blog post in, uh, markdown, save the changes in Git and, uh, uh, compile them into a static site. And with the GitHub pages, uh, pages dot GitHub com, you can host your static sites, uh, for free. And, uh, they're very friendly to hosting, uh, sites with Jekyll. So in this episode, I'm going to show you how you can start your blog with, uh, GitHub pages and, uh, je. Now here is my blog. You can, uh, see the source code of my blog. Here it is, uh, open source. And if I go into settings and pages, so here I see the information about, uh, how the blog is deployed. It is deployed at this domain. I can, uh, change the domain here. It is deployed by GitHub actions, and, uh, the name of the repository is Vice GitHub io. So you, uh, also have, uh, like if you have a GitHub account, you have access to your username, do GitHub, do io, and you can, uh, uh, host something on this, um, domain, or you can, uh, host something on your own domain. So let's start by creating and new GitHub repository and, uh, uh, hosting it on a separate domain. I will create a new repository, uh, second, or today I learned blog. Uh, it'll be public create repository. And, uh, now, uh, to have at least something going, I need to have, uh, at least like one page. Uh, let's create a file. I will say index html, um, hello World by Ya, commit Change. Okay. And, uh, I will try to deploy this. So go into settings, pages, um, deploy from branch. Uh, the branch is going to be main save. And, uh, let's see what happens. So I go to actions, and you see there is this page building and the deploying going on. It can, uh, last, uh, from like a few seconds to a few minutes. Okay, so here it says that it has deployed. Let me go back to settings pages, and it says that this is the URL. So, uh, it uses block.com because I already have my, uh, domain block.com set up for my other blog slash today I learned blog, and here it, uh, renders this, uh, page that I have just created. So, uh, I have, uh, another repository that I'm serving to this year, l but, uh, I would like to serve this, uh, URL to its own, uh, website. So I'm going to try to, uh, host, uh, this, uh, repository on, uh, this, uh, certificate al.com domain that I own. Uh, how can I do it? So, uh, first of all, I'm going to, uh, try adding this, uh, domain to my GitHub account. I actually wrote a blog post, uh, where I have a link, uh, to verify an A domain on GitHub. So in your profile, in, uh, you have this add verify domain, I'm going to try to verify the domain certificate al.com. And, uh, I need to add a XT record. So going to the Advanced dns, uh, add new record D XT host, um, value. Okay, Uh, it's saving. Then it can like, take, uh, a few seconds to update or even a few minutes. Uh, let me try clicking on verify. So you say it hasn't verified ownership, but I'm going to try again in, uh, like a minute. So while we are verifying the domain here, we can, uh, uh, try to add this domain to our, uh, repository where we want to use it. So here I have this blog. I will add the custom domain here. I will click, uh, save. And, uh, uh, another thing I need to do is, uh, add a CNAME file. So here, oh yeah, it has actually generated the CNAME file, uh, itself. Then I added the, the domain name here. Uh, I think it's actually better to use www, uh, because if you use www both, uh, www and the, uh, root domain are going to work. If you use, uh, just the root domain, then www will not work. And, uh, some read in the GitHub pages documentation that it is, uh, uh, advised to set the www. Okay, so I updated this and, uh, maybe it updated the comment on the cname. Yes. So you see it, uh, updated the CNAME here. In the meantime, I can check if the verification process has passed. Okay, here you see the domain name is verified. Wonderful. And, uh, it, uh, is trying to deploy. So let's have a look. It is, uh, during the DNS check, this can, uh, last, uh, some time. I think it's also trying to deploy. So let's debate until it deploys. Okay, so it looks like it has deployed. Let's, uh, have a look back at settings and pages. Uh, this is still in progress, so, uh, the domain is still not available because, uh, uh, the verification is still in progress. Let's, uh, wait, uh, ebit, okay, here you see we have this DNS check, uh, unsuccessful. It is because we need to add the, the connection between, uh, our DNS rec, our DS here and the GitHub pages. So, uh, again, something from my blog post. Uh, we need to add the, the, uh, host and value for your username dot GitHub io. So as I'm hosting on the RU domain and on www, I'm going to add a CNAME for www, uh, then my GitHub, uh, username.github.io. And, uh, it looks like there need another.in the end and another CNAME with at and the same value. Okay, so, uh, now we are linking our GitHub, uh, pages, not just the GitHub account where we verified the, uh, the domain, but our exactly linking our GitHub pages with the, um, our domain. So let's try to check again. And this time I suppose it should, uh, work. Now, you are likely to get this kind of error, Dennis check unsuccessful, and the solution to this problem is most likely that you just need to wait. So if you have, uh, it set up at, at uhub username, do GitHub io here, ww uhub username GitHub io here. And you are getting this kind of error. Most likely, you just need to wait. It can last 10 minutes, it can last an hour, it can last, uh, a day. Uh, most likely it'll work a bit later. So, uh, here I set up these CNA for the domain certificate owl, but I previously tried it on another domain, e wl it.com ever.com. So, uh, I kind of set up the same CNAs. And, uh, now in another tab, I just updated the custom domain to work with www ever.com. I have previously also verified this domain name. And, uh, now when, uh, going here, whenever you refresh the page, it kind of checks to, uh, verify the DNS and, uh, this DS check is successful because I, uh, did it a bit, uh, before. So, uh, the DNS had enough time to update. So now I will also say, uh, yeah, I'll just try this in the domain and see if it works. You see the website that we just created, uh, is rendering on, uh, uh, this website, if you try going to the bare domain, you see it's also rendering is like redirecting to the www version. Okay, let's, uh, try also saying we will enforce https and it's another good, uh, thing to do it called otin. And, uh, your website will not be available via HT p, only HT ps. So that is good. And, uh, yes, that's basically how I, uh, uh, just in a few minutes deployed, um, uh, static website for free using GitHub pages. It is just an H TM L page at the moment. If you go and look at the source code, all I need is as the minimum is an index, uh, uh, H TM L file. Actually, you can name it like whatever you want. Uh, you can add js, CS, s, whatever. Uh, now it's a one page aesthetic site, and we've got this CNAME that was, that you can, like, add on your own or that can be generated in settings when you go to pages and whenever you update your custom domain. So yes, this is, uh, basically it. See in the next episode where we are going to actually install gel on top of, uh, this, uh, static side. So we're not going to just have a index, h TM l we're going to have a whole CMS see in the next one. --- # How to use SupeRails | #00 - URL: https://superails.com/posts/how-to-make-the-most-of-superails - Published: 2024-12-22 - Duration: 02:45 - Access: free Platform: https://superails.com Text blog: https://blog.corsego.com Source code for all episodes: https://github.com/corsego ## Transcript Hello, my name is SLA and I'm a Ruben Rails consultant. Every day I help my clients solve all kinds of problems while building the Ruben Rails applications. And I created the, this, uh, platform strip rails as a way to summarize all my learning and share them with you so that it is a easier for you and faster tools solve all the same problems that I did. That's why I have the tagline, learn 10 times faster than I did. Now, how can you use tutorials? First of all, there is the YouTube channel. There are videos and they are also curated into playlists. Uh, uh, there are different kinds of nice playlists like Hot Via Native and so on. There are also a few live videos. I also recommend ING them. Uh, they're not visible in the video section, so you can see them here. Uh, now if you open any video on YouTube, most likely you will see this, uh, ads before watching the video and it kind of ruins the experience. So I recommend using, uh, superhero.com to view all the content. Now, under each video, I recommend you to also see the description. I have, uh, uh, chapters to most of my videos that would help you navigate to the exact part of, uh, content that you are looking for. And also there are links to the source code. For each comment I make, I try to make granular encapsulated comments so that it's easier for you to track. For example, here is a comment commit where I just add search functionality. Here's another comment where I just add style functionality, so it is easier for you to learn step by step. Now, going back to super Rails, here I have, uh, not just all the same content that is on YouTube. Again, here you don't see any ads. It is better curated and also there is exclusive content. So here are the pro videos available only on super ails and in general, all the content that I create is first available on Super rails.com, and then maybe a few weeks later on YouTube. So if you login for the freshest and most exclusive knowledge, come to cprs. Also, there is the GitHub, uh, account GitHub com slash cogo. That the source code of all my episodes, you can just go to the source code of any episode that you can find. Uh, number four. So here you see episode two seven, and if you go into the comment history, you see the comment for just created the application. And here is the comment where I add actually all the functionality. So maybe you don't want to really watch the whole video, you can just have a look at the source code. Also, I have, uh, a blog where I have, uh, blog posts. So text versions of most of the videos that I create. Again, if you, uh, prefer learning, not by uh, uh, watching videos, but by reading, this might be the solution for you. So what are you waiting for? Go build something great and I'll be here to help you. --- # Add Avo to existing app | SupeRails #218 - URL: https://superails.com/posts/add-avo-to-existing-app - Published: 2024-12-16 - Duration: 22:46 - Access: free - Topics: avo - Playlists: AVO Let's add Avo to an existing Rails app 0:00 generate Avo resources 4:48 add search 12:42 admin-only Avo access 17:10 styling Change history: - [generate Avo resources](https://github.com/yshmarov/moneygun/commit/8e206ee40eb9778820b6f3455178919f5c81ba82) - [add search](https://github.com/yshmarov/moneygun/commit/21decdfbe257f55a615341f9efd420346839a042) - [admin-only Avo access](https://github.com/yshmarov/moneygun/commit/8fae41451c9380f6002f6435dd018b424f564d7a) - [styling](https://github.com/yshmarov/moneygun/commit/279403e174dff60a63bcdcf965da64626bbd40a8) [All the changes together](https://github.com/yshmarov/moneygun/compare/main...add-avo-3) ## Chapters - 0:00 generate Avo resources - 4:48 add search - 12:42 admin-only Avo access - 17:10 styling ## Transcript Hello friends. So in the previous episode, we added VO into a new ribbon rails application and used it with the Rails eight authentication generator. And now we are going to try adding VO to an existing, so not just to a new, but to an existing ribbon rails application. And we are going to use man gun, my ribbon rails, uh, multitenancy boiler plate as an example application. Don't forget to put a star. Okay? So here I have Mangan running and I have three main models in the application. I have the user model. The user can log in, the user can, uh, browse his organizations. He can create an organization, and he can be a member in multiple organizations. So three models, user, organization, and membership. We see that in this organization there are two users. One is an admin and one is a member. And I want to add AVO as an admin backend. So they're going to use AVO to be able to see all the users, all the organizations, all the memberships, and uh, do some moderation like, uh, uh, change, uh, a member in another organization from admin to member. Add the new organizations from the admin, uh, add, uh, users to, uh, organization. So anything as super admin would want to do. Okay, let's start with the going to the other homepage, um, VO HQ io. And here I have this, uh, app template. I'm going to run it, uh, in my repository. Uh, what is it doing? So it added VO to the GEM file. It, uh, added VO to the roots. So, uh, here I have this, uh, root AVA integration route path, and the avo initializer has a lot of, uh, defaults. And it says that the root path by default is ava. So if I navigate to slash Ava, I should be able to see the A dashboard. And it also tried creating a resource for users. So a resource is way to see a list of users with vo, it's like an VO scaffold where we have, uh, all the fields, uh, of the user defined, uh, based on the database, uh, schema. Uh, and, uh, these are based on the associations in the user model. So a user has many memberships, has many organizations through memberships. This is based on the user model. Okay, let's try, um, uh, run our application. Now, I need to restart because I added a new engine and I added a new initializer. And let's open our app and go to, uh, slash Okay, so here I have, uh, VO kind of runin. We have the, uh, default, uh, homepage, and let's try going to users. And, uh, it says polymorphic associations not support computing in the class. Okay? So, uh, if you go to our newly created, the user resource, this one, uh, he have, uh, uh, all these invitation fields, and we have this invited by type and invited by, uh, id, I will just comment them out for now. We don't need it. Okay. So here have, uh, users, we can see a list of users. We can try to create a new user. We can, uh, open a user. We can delete a user, edit a user. Okay? Now I open a user and I have this, uh, error membership field, uh, uh, not found because, uh, for our membership model, we also need a resource. So it prompts us to run rail, generate our resource membership. I can cope with this command and, uh, run it, uh, in my, uh, and so, or I can actually have a look how to generate all the resources for all our models for vo. So if I type rails, generate help here, I have all the prompts, and I also have the prompts for vo. So rails generate vo, uh, all resources. Let's try this. Command rails generate other all resources. Uh, no, I don't need to override user. Okay? And here I have, uh, the other resources generated for inbox membership, membership invitation, organization, transfer user. Okay? Now, a couple of them are empty because they're not backed by the database. I just don't need them. I can remove them. Uh, I also don't really need the resource for inbox. I will remove it for now. And, uh, here I have three resources, user, organization and membership, and three controllers. If you're not going to need these other controllers now, but in theory, you could override some behaviors here if you wanted. Okay, let's, uh, save our changes. Uh, add other generate resources. Okay? Uh, now let's, uh, refresh our page and, uh, yeah, it works. So we have, uh, our memberships, organizations and users. We can navigate to a user's page. We see all his attributes. We see all his memberships and organizations. We see that this user is a member of this organization. He is, uh, his role is a member and he doesn't own the organization. He's just a member. Let's go to the organization. We see we have two members, okay? Undefined inboxes, fields, uh, yeah, it was added by the scaffold. We don't need it. So, uh, let's go to our mul created folder. App, uh, yeah, app. Our resources organization. I'm going to remove this inboxes. Uh, I need to go back because it is looking for the inboxes in the URL. Okay? So the organization has memberships users. Let's go to the other user. We see that he is, uh, an admin organ organization, and he owns one organization. So the organization, uh, uh, table has his user ID as the owner. Okay? So we can navigate, uh, in avo, we can create, we can edit looks, uh, good. Now let's try adding some more functionality. For example, imagine you have a big list of users, big list of organizations, and you want to find a user by the email or organization by the name. So, uh, let's try adding search. Now, uh, if you have a look at your resource, it already has an example of adding search. So let's try adding search by the name of, uh, an organization. Let's uncommon this. And, uh, by default, we have the example with adding search by the id, uh, that equals this. DSL is from the gem ransack. I relies on the g ransack to make, uh, search for. So I will cope with this, and I will, uh, say coma, uh, name contains. And the, the D from RANSACK is CONT. Um, okay, so we can search organization by the idea or by the name. Let's, uh, refresh. And you see we have a search field that appeared. I will click on the search field, and we have this undefined method ran for class organization. So I think we need to manually add the gem ransack to our, uh, gem file, otherwise again on ransack, uh, for search, and then the gem bundle, restart in the server. Okay, let's try searching. Okay, we have this, uh, undefined sackable attributes. So it is, uh, uh, ransack, uh, security feature, uh, that, uh, they'll need to add in the organization model. He will have DAF solved sackable, uh, attributes, uh, decision by ID and by, uh, name. Yeah, actually we need to add this note in the resource, but in the modal. So organization, modal right here, and recyclable attributes by source, by ID, or name. Let's now refresh. Okay? We have the organization name here. Let's try searching for something that doesn't match. Nothing found, super, found it has been highlighted. Okay, looks good. Now, in the same manner we can add search to our users. Let me go with this. I'm going to the user model. I will add the search by ID or email, uh, and, uh, here in the organization, resource searched again by ID or name in user. We're going to search by ID or email. Okay, refresh. Let's see, our users, so have, have search. And, uh, I try to search. You see, it shows the id. So, uh, uh, if we don't want to just be able to see the list of user id, like I'm searching for hello. I see that this is used with ID number one, but, uh, I don't see the email, so the idea still doesn't tell me much. So we want to be able to see the ideas, uh, not only the ideas, but the email, uh, here. So the simplest way would be to set self title. Uh, of the resource is going to be email. Now I'm going to refresh. And KC have, uh, the email here, uh, looks good so we can search by our users. And, uh, also setting the title, uh, changes, uh, the way the user, uh, the resource looks in the breadcrumbs and the title. So let's, uh, comment out that title. You see now we have the ID here and ID here. So it's, uh, a good practice to always when, uh, there is something defining characteristic on a resource to have a title. Now, we didn't really need to do it on the organization model because it searches for a name attribute by default, but I think it makes sense to, uh, manually declare the attribute, uh, yourself to, uh, say that you want to the organization to be defined by the name attribute. Again, let's try defining the organization by the id. And you see it, uh, shows the ID here instead. So, okay, name is, uh, definitely better. Okay? We added search for organizations and for users, we don't really need to edit for memberships. Yeah. And token of, um, uh, titles. So here you see we have, uh, the name of the, uh, the email of the user. But imagine you have two organizations with the same name. If you're not validated, the uniqueness of the name, and, uh, uh, likely you would want to to be able to have organizations with the, the same name. Uh, uh, if it is not just in the URL, uh, it's just like, uh, like in Trello, you can have two boards with the same name. So, uh, you would want to have, uh, not only the name of the organization, but also maybe something defining like the ID in the, the search results. So we can have a programmatically defined, uh, title. And I think we have it in the other docs. Actually. I usually look a lot into the other docs. Uh, yeah, we have this computer title, and, uh, here we have an example, title Lambda and, uh, something. So let's have a computer title. Let's have not just name, but, uh, equals, uh, uh, and say, uh, record. We have a record defined here. So we have record id, coma, record, name, join, uh, slash So now we go back to vo. I go to search. And you see we have the ID and the name of the organization. So this way, uh, what version four is definitely going to be unique. And, uh, let's try open the organization again. We see the computer title here and computer title here. Okay, looks, uh, good, let's save our changes. So, um, uh, a search, uh, and titles. Okay? Now, uh, another very important thing is to make it so that only logged in users, only logged in, admin users have access to other. So first of all, we're going to add the admin attribute to the user model rails, generate the migration, um, add admin to users, admin bullying. Let's, uh, open our migration. And we are going to also say no false and default false. So by default, a QC is not admin relatively migrate. Okay? Now, uh, let's, uh, uh, make it so that only a logged end user has access to abo. So if go to the our docs, uh, let's open device. And here we have this current user method, current user. Let's go to docs. Here we have, uh, current user method, current user, and I can just un comment it or paste one that I took from the device docs. And, uh, we are also going to add a log out, uh, path. Uh, so config send out path, name, destroy user session path is the one that works for, uh, device. Uh, okay, now let's, uh, see if it works. I will, uh, start the server going to ava. Now we had our user, and now I refresh. And you see we have hello@cprs.com. So, uh, it, uh, sees the current user that is sent in Now from Ava. I can also try signing out. So let me sign out. I sent out here, uh, now I will sign out. Uh, I will just refresh this page. You see Ava send me out. So I managed to send out with, uh, there's an out link from ava. Uh, but if I go to slash Ava, now, uh, you see I'm not sent in and I still have access to vo. So, uh, what I need to do is, uh, going back to the other docs, uh, here I have this authenticated user access, ava. So now, uh, if I add this, uh, around our AVA route, only a logged in user will have access to, uh, a, uh, I refresh. You see, I'm, uh, redirect from vo, uh, I need to send into access, uh, vo, uh, let me send in. Okay, I have access to vo, but I'm not an admin. So let's make it so that only admins have access to vo. I will again, go to the VO docs. And, uh, here, uh, I'm going to say that user has to be an admin. So like this, now I'm going to refresh my, our page. And you see, again, I have no access to ava. So let's manually make our user an admin for the console rails console user first email, okay? This user dot update, uh, admin, true refreshing. Okay, wonderful. Now only an logged in admin user has access to ava. And finally, let's change our route to be no slash val slash admin. So I will go to Ava rb, and here the route part is going to be slash admin. I refresh slash ava, nothing. Refresh slash admin and have, have our AVA dashboard. Okay? Uh, now let's have a look at our users. Here we have two users. Let's also display which user is an admin and make it so that the, the super admin can, uh, change the admin status of other users. So going to resources user, I will add another field, field, uh, admin as a bullying refresh. Okay? We have, uh, uh, one user that is, uh, an admin and one user that is not, let's make this other user an admin. So, uh, just checking the check mark. And you see now both users are admins. Okay, wonderful. Let's save our changes. So, um, only admin has VO access. Okay? And now let's do some more styling. Uh, so here, for example, we have our users and we see all these invitation fields, and they don't, uh, serve a lot of value on the index. So let's make the invitation fields visible only, uh, if they open the user. Um, so how can we do it? Let's go back to users and, uh, uh, we can add hide on the index to a field. Let's refresh now. And you see we don't have this, uh, uh, uh, invitation token visible on index anymore. Uh, another thing that, uh, we can do is, uh, uh, style our show page. So here we are going to have like, uh, main, uh, content error, and we're going to have a sidebar, and let's move all this invitation fields to the sidebar. Let's, uh, go again to the other docs and search for sidebar. And here we have an option to have a main panel and a sidebar. So let's add the main panel to our fields. In the main panel, we will have ID, email and admin, and within it, we will have the sidebar with all the invitation fields. Okay? And, uh, again, I'm not adding all this has many fields, uh, neither to the main panel, not to the sidebar. Let's, uh, now go back here and, uh, have a look. Okay? So here we have the main fields, and here we have the invitation fields. Let's go to the user's index. And you see all the fields that have been moved to the sidebar are not visible on the index. So we don't need this hide on index, uh, anymore. Even, uh, better. Now we can, uh, open again a user. And here we have our memberships, organizations owned organizations. Let's add all these into, uh, tabs. So instead of like having them as an endless list, we are going to have three tabs for seeing all the memberships, all the organizations, all the owned organizations. So again, our has, uh, tabs on show view. Uh, we're going to have tabs. We don't really need to set an id, uh, tabs, uh, do. And our most importantly, we have organizations and memberships, then owned organizations. Let's, uh, have a look. Now I will refresh and have, have organizations, memberships and owned organizations. So now the hierarchy of things in our, uh, show page of, uh, user looks better. Now, at the moment, we don't see when the user was like created or updated. So the default timestamps, let's also try adding them. I will, um, add them as another sidebar. So sidebar dual here will have a field, uh, created add field, updated app. Let's refresh and let's not show the seconds. Let's, uh, show the date in another format. So do something like SDRF time. Now, um, AVO has its, uh, way of format in date, uh, time format. And, uh, here's the example. So field, uh, as time or as daytime. And they're going to add the format attribute. And, uh, the format is not f time, it is this luxon format in tokens. And, uh, here are examples. So, uh, uh, let's just show the date as August 6th and, uh, uh, yeah, and let's all show the day of the week. So let's just make it, uh, format DDDD, refreshing. And you see now the credit at is more readable. Let's also show the, uh, time, uh, as just like t. So after D, DD, D, we'll have t refresh and have the time. Okay, let's try putting a comb. I wonder if this will work. Yes, it worked. Okay, we can add this, uh, kind of, uh, uh, date format to all our, uh, datetime fields. Let's refresh. And all of them are a bit more readable, so wonderful. Uh, we improved the style of the show page of our users and removed all the, uh, informational overload from user's index. So let's save our changes, uh, as, uh, style users, uh, AVO users show. Okay? And, uh, yeah, this is the like basics, uh, what I would do when add VO to an existing application. So you ensure that only admins have access to vo. You make sure that all the resources, uh, can link, uh, among each other, and you add some, uh, your search and you add some basic, uh, styling. So that's it. Thanks for watching and see you in the next one. --- # AVO Internal tool. Authentication, Invitations, Roles, Tests | SupeRails #217 - URL: https://superails.com/posts/avo-internal-tool-rails-8-authentication-invitations-tests - Published: 2024-12-03 - Duration: 28:55 - Access: free - Topics: avo - Playlists: AVO If you want to build an Internal tool, or Admin dashboard in Ruby on Rails, there is no better tool than [Avo](http://avohq.io). If you are building an internal tool, you don't need User registrations. You will want new users to join only via Invitation. 0:00 Create new Rails 8 app 1:31 Install Avo 2:45 Rails 8 authentication generator 3:55 Only logged in users can access Avo 7:42 Generate Avo CRUD resources (User, Session) 12:22 User Invitations 15:55 Only Admin Users can access Avo 20:43 Handle signed in non-admin users 25:00 Add tests 27:57 Summary ✅ Install Avo ✅ Rails 8 authentication ✅ Only logged in users can access Avo ✅ Generate Avo CRUD resources (User, Session) ✅ User Invitations ✅ Only Admin Users can access Avo ✅ Test coverage Usually I try to make my videos shorter, but I really wanted to go through each problem you might have with Avo or Rails 8 authentication. [Episode source code](https://github.com/yshmarov/avo-internal-app/commits/main/) - https://blog.corsego.com/rails-8-authentication Avo resources: - [Rails 8 Authentication](https://docs.avohq.io/3.0/guides/rails-authentication-scaffold.html) ## Chapters - 0:00 Create new Rails 8 app - 1:31 Install Avo - 2:45 Rails 8 authentication generator - 3:55 Only logged in users can access Avo - 7:42 Generate Avo CRUD resources (User, Session) - 12:22 User Invitations - 15:55 Only Admin Users can access Avo - 20:43 Handle signed in non-admin users - 25:00 Add tests - 27:57 Summary ## Transcript Hello friends, they all love rubber burn rails 'cause it makes building web applications much faster. And AVO is a layer on top of rubber burn rails to make building applications even faster. So AVO is especially popular for building, uh, uh, admin dashboards and for building internal tools. And in this episode, they're going to start getting acquainted with vo. They're going to, uh, install VO on a user application. They're going to make it so that only internal users can access VO and users can be created only via invitation. So an admin will be able to invite users to join their application and access their vo uh, uh, dashboard. Let's start by creating a new Ribbon Rails application. Let's check our Ruby version, Ruby version 3.2 rails, version eight. Wonderful. We're going to use the latest version of Ruby Rails. So, um, let's start with Rails. New avo, CRM. We don't need to select the, uh, CSS framework like Tailwind or Bootstrap. They're just going to use what is included in avo. Okay, cd, avo, CRM. Let's open the application. I will say rails. The B grade, the B my to create a database. And let's save our changes. Get add all, get com, uh, message create app. Okay, wonderful. Now let's install VO in our application. So I'm just going to run this command and it's going to add GEM to the GEM file. It is going to add the other roots and it is going to add an other in initializer. Let's start the application and, uh, have a look. So, uh, going to our roots, we now have, uh, mount other engine at VO configuration root path. And if you go to avo, rb, the initializer that was just created, we have this, uh, VO configuration root path that is slash vo. So if you go to our new created application and go to slash vo, here we have access to the basic VO dashboard. But at the moment the dashboard is well empty. Okay? We have done the basic installation of, uh, vo, we can exit at slash vo. So let's, uh, save our changes, get add all, get com message, uh, install vo. Okay, now let's make it so that only authenticated users can access Apple. So they're going to start with adding the authentication and they're going to use the Rails eight authentication generator. I will say Rails generate authentication. It'll give us a bunch of files. Uh, by default I will say rails, the B migrate. And, uh, let me just restart the server just in case. So now we have our route. If we go to slash uh, avo, we still have access to avo. And if we go to slash session new, we have the, uh, registration or authentication. We don't have registrations by default in the rails authentication generator. So we can send into an existing account or we can reset our password just to authentication features by default. Okay, let's, uh, save our changes, get status, get at all, get commit message rails, generate authentication. So going like by small steps. Okay, now let's make it so that only log in users can access vo. So if you go to a guide, an official guide from VO have this, uh, way of making VO work with the rails authentication generator. So we can go to our vo uh, IB file. And here we have this authentication part here. I'm going to say config current user method is current user. And I'm going to set the sign out path that is going to be session path. Okay, let me restart the app and uh, navigate to av. You see, I still have access to ava, though I'm not, uh, logged in. I have also the sign out button, but it'll really do nothing. It'll just redirect me to the sign in page. So I still have access to ava though I'm not, uh, sent in. Going further inside this, uh, uh, documentation, we have, uh, the option of creating this AVO application controller. So I'm going to generate the file. We have created the controllers slash avo slash application controller, and inside this file we need to add, include authentication. Authentication is this authentication file that was generated with the, the rails authentication generator and require authentication to access Ava, all the AVA controllers. Okay, let's, uh, refresh. And you see we have this undefined local variable new session path in the request authentication. Let's find it. Um, dev request authentication. So redirect to new session path. I will need to say main app, new session path. That means demonstrate. So here, new session path doesn't fe but if you make it, uh, main app, new session path, it works. So inside VO controllers, everything is named spaced within VO by default. And we need to say main app dot, uh, root name. Let's, uh, refresh one scan. Okay, so now when we go to slash vo, we get related to the sign in page. Now let's actually sign in. Uh, at the moment we don't have any users and we'll need to create a user programmatically. So I will say user dot create email address. It's not email, it is email address. If you go to the schema visit that we have users with email address. So it is the default name of the user email field. It's going to be like ya@superrails.com and password is going to be now 1, 2, 3, 1, 2, 3. Okay, we created the user. Let's try signing in, sign in and wonderful. I am, uh, logged into vo. So as a send end user, I can access uh, VO as a not send end user, I cannot, uh, access vo. It seems to be broken. Let's, uh, save our changes. Let's just see get status. Uh, Okay, uh, let's, uh, say get add all, get com message, uh, require authentication for Apple. Okay, let's go, uh, further. So, uh, at the moment when we are logged in, let's look in again, we just have this, uh, default dashboard. We cannot do anything. They're going to generate the, the views and uh, uh, the possibility to like edit or delete or create users and sessions. These are the two models that we have in our application. Let's go to, uh, our models. So in Apple models, we have, uh, user and session two database back to models. And we are going to generate the other resources for them. Now, if we run rails, generate help, we have all these, uh, commands and, uh, if we have VO installed, we'll also have the VO prompt commands here. So we are going to generate all the resources for vo, we'll just say vo all resources rails generate other all resources. It'll create a resource for session and for user. Let's have a look at our, uh, our folder. Now we have the current file. It is empty. It is not database backed. We don't need it. We have session. So session has, uh, basically all the fields that we have here. Uh, and user has id email address, uh, and sessions. You see, a user has many sessions. It's set as has many. And, uh, session belongs to a user. Let's, uh, open our application. Let's actually restart the application and see how it looks. So you see we have two, uh, tabs now in our sessions and users, if we go to users, we have the list of users at the moment, just one we can view, edit, or destroy the user. Let's view the user. You see, the user has, uh, one session at the moment. Let's open the session. You see it has user ID and it belongs to user. This is not clickable, but this one is, let's go to our session and, uh, remove this field. Okay, yeah. So session belongs to user. We can click and file, navigate within our application, create sessions, create users. Let's actually try, uh, deleting, uh, session. So, uh, I will open the session Destroy. And now, uh, I have destroyed the only session, the current session of this user. So if I refresh the page, it sends me out. So deleted a session, can sign out anybody remotely, and it's a wonderful, uh, feature of, uh, session based authentication that ships with Rails eight. Okay, let's sign back in. And yes, we have generated our sessions, uh, okay. And find local variable method root, URL. Uh, we don't have a root URL in our application, so let's go to our roots and, uh, set it to be, uh, sessions new. Okay, so let's, uh, try again. I will try signing in. I have signed in, but uh, I, uh, don't see that I have signed in and I wasn't directed to slash Ava. So let's, uh, say that, uh, if a user is sent in, he is going to be directed to vo. I will go to our sessions controller, and here in new action I will say redirect two. Uh, we're going to redirect to the VO root path. I think we have it here, VO configuration, route path, uh, if authenticated. So now if I go to Session U as a logged end user, it drives me back to avo. Uh, okay, let's, uh, uh, try signing out. I, uh, just go to session u, sign in again. Okay. And the assigned end user is directed to Ava. So it works well. We have generated the resources and the assigned end user is automatically redirected to Ava. And we have set the default, uh, uh, route to the new session or to the kind of login page. Okay? Uh, as a next step, let's make it so that we can invite users to the application. So, uh, we have users and we want to invite the user by typing in the email. And the user should receive, uh, an email so that he can set his, uh, password and access that application. So let's say like, uh, fu@bar.com, let's, uh, click save, invite the user. And you see password cannot be blank. And, uh, well, the user is not created, was not invited. So let's go to our user model. And um, here we are going to, uh, set a random password for the user while we are creating his, uh, entity. So let's say, uh, before validation, uh, set password on grade. So when we are kind of creating the user account and user accounts can create only via invitation in our application, we are going to set a random password. Let's say private, uh, set password, uh, sell of password equals secure random hex. Okay, let's try creating this user again. Okay, so you see the user has been, uh, created, but, uh, well, he has been set a random password and he doesn't know that he has been invited and he doesn't, uh, have the possibility to log in. So we are going to actually send the user, uh, reset password. The email, remember, uh, we have the reset, the forgot password feature automatically included the when the create the rails a authentication. So we are just going to invite, uh, him to set a password. Let's, uh, try it. So here, uh, how does this reset password work? If we go to our mailers, we have this password mailer. It sends a reset password email. Let's see how it is invoked password mailer, reset, user deliver later. Let's go with this. And they're going to use it in our user model. So, uh, I will say, uh, after create commit, do password mailer reset, the user is self deliver later. So whenever a user is invited to the application, he will get a password reset email, and he'll be able to click on the link and, uh, lo set a password and log into the system. Let's, uh, check if it works. I will, uh, go and create a third user. Click on create. And in the logs we should see an email. Yes, we have the email. So you can reset your password within the next 15 minutes. Let's, uh, go with this reset password link. I will, uh, click on it. I will set a password. Save. Okay, I have set a password. Let's try logging in as this, uh, user. Okay, so I have logged in as this third user. So I have, uh, basically added an invitation, uh, system to our application within AVO in, uh, just a few lines of code. It is, uh, I think quite amazing. Now, let's go, uh, step deeper. Let's say there are users that can be logged in but cannot access avo. So only a user that is, uh, an admin can access, uh, all the AVO features like, uh, using invitations and so on. Uh, how can we do it? First of all, let's, uh, uh, create a new field on the user model, uh, admin that is either true or false. So a balloon and, uh, make it so that only users that have the admin set through can access. Uh, a, let's start with the saying rails. Generate the migration, add admin to users, admin, bullying. Okay, let's open the migration. Add admin to users. I will, uh, say, uh, default, false and null. False rails migrate. Okay? Now, uh, let's, uh, see how can we make it so that only uses the admins that the admins can access. A let's, uh, also display like for the uses an admin or not in the other dashboard. So, uh, I will go to AVO resources user, and here I will have another field, uh, admin as bull. I will refresh. And you see I have just, uh, added this small line here. And obviously whether the user is an admin in the index view in the show view, we can edit the user admin to be true or false. Let's set ourselves, so this third user to admin. Okay? So this user should be able to access, uh, AVO and this other user that is, uh, not an admin should not be able to access. So there are a couple of ways we can do it. The first way would be in our roots, we can add a constraint, I would say constraints, uh, Lambda, um, request, uh, and current user, uh, admin. So if, uh, there is a current user and if he is an admin, then he should be able to access, uh, other rules. I will refresh here. You see, the route is not found because this user is not an admin. And here, uh, okay, this user should be able to access. Um, uh, let's try not. Okay? So this works. Okay, maybe constraints isn't the best way, but, uh, a good way definitely will be in this, uh, VO controller that we have previously generated. So let's go to avo, uh, application controller. And here in this a application controller, we are going to say that only admin users can access avo. So I will say before action, ensure admin or authorized admin drive it, dev authorized admin, and uh, yes, direct to uh, uh, yes, let's say route path or main app path, let's say root path, unless, uh, uh, current user is an admin. Okay, I will refresh. Uh, okay, both users cannot, uh, access at the moment. Let's see why. Yeah, so both users cannot access at the moment because I didn't, uh, remove this, uh, constraint from the rules. So, uh, let's try again. Okay, so here I have too many redirects and here the admin user can access, uh, avo. And this other user, uh, is getting too many redirects because he can access avo. He gets redirected to the, uh, what path to the root path. The root path is uh, uh, our sessions, new path. And from sessions controller, uh, we direct to our, so it's, uh, an endless redirect. So we don't want this endless direct. We would want, uh, some kind of, uh, fallback, uh, location for users that are sent in, but they're not admin, so they cannot access. Uh, so let's create a new controller for this. I will say rails generate controller, a static, and it'll have an action index. Okay? And, uh, let's say that, uh, they're going to set the route URL of our application to be not, uh, uh, sessions new but static index. Okay? And, uh, next step, uh, we will, uh, do what we will redirect in our AVO application controller, uh, to main app, the root path. Okay, let's see if this works. Okay, so now, uh, here I have access to avo, and here I redirect it to the root path. Now, let's also add some buttons to see if the user is, uh, logged in. I will, uh, go to something I've done before authentication, and I'm going to go with some, uh, uh, lines from here. So going to application do, uh, html, EER B here, I will just add all these, uh, uh, links. Let's see if, uh, it works. So undefined method, dashboard path, it'll be not dashboard path, but it'll be our, uh, AVO configuration route path. Okay? So let's see. Now I see that I'm logged in. I have, uh, a link to the homepage. I can sign out, I can click on dashboard, and, uh, well, nothing happens because I have no access to dashboard. Let's also run the flash messages. I will say files notice and equals alert. Okay, so I click on dashboard, uh, maybe I need to restart, uh, the server. Okay, so homepage, dashboard. Yeah, actually I need to add a flash message. Uh, alert, not authorized. Okay? So go into dashboard. And you see I get this not authorized alert. And here if, uh, I go to, uh, the root path, you see I have this, uh, authenticated page and I can click on dashboard and I'm directed to. Okay? So I think everything kind of works. Uh, uh, now let's try signing out just in case. And, uh, I get this, uh, undefined new registration path. Uh, let's see. Yeah, because we don't have registrations, I don't need that. Okay, so now I'm not logged in. I can click home dashboard, sign in. I am always on the sign in, uh, page. Can I go to the static, uh, index page? No, I cannot. Let's make it accessible for any user. So I will go to static controller, and here I will, uh, allow unauthenticated access. In the our authentication. We have the thin for allow unauthenticated access. I will just edit for the whole controller. Now, I, uh, refresh. Okay? And now like all users can see this, uh, screen, uh, but only, uh, sending users can go to the dashboard. Let's try sending in. Okay, I'm sent in, but I'm not authorized to exit the dashboard and let's make that user actually an admin. Okay, now I go to dashboard and voila, finally, I have access to the dashboard. Now we have identified some, uh, authentication logic into our application. Let's write some tests. Static controller test. And it's going to be actually quite, uh, easy. Let's, uh, first of all, test that, uh, just getting the root URL or the application works get root URL as such response success rails test. Okay? And now an user that is not authenticated should not be able to access, uh, vo and the VO route is AVA configuration Route path. So get the VO configuration, route path and assert redirected tool, uh, slash sessions slash new. It's actually slash session. Let's test again. Okay? And now let's, uh, uh, authenticate a user. I will say user equals users one, it'll access the user fixture and this one. And, uh, I will log in with this user. So I will say post slash session, Ram's email address. And the password is, uh, just password. We see it here. And now we'll try to, uh, access other and, uh, uh, let's see what happens. Yeah. So, uh, we didn't do an insertion. Lets assert that the user is logged in, but he's not an admin. He cannot access ava. So, so success, it'll not work. It should be a redirect. So, uh, the expert success, but it was redirected the root path. So actually it should be a certain redirected to, uh, root path. Let's follow the direct and a such response success. So a final destination of the older redirects. Okay? And, uh, let's make the same user an admin and ensure that he can access VO user, uh, update admin. True. Let's again, get, uh, VO and set the response to be success. And you said it is not, uh, success, but because it tries to direct to the first, uh, of the roots inside vo. So if we try to get a route inside VO like, uh, slash vo slash resources slash users, if it try to get, for example, this resource, it should be successful rails test, okay? And it's successful. So just with a couple of, uh, lines of tests, we have, uh, tested the whole, uh, other integration in our application. And basically now we have a small application where, um, users that are not signed in can see the homepage and they see the sign in page, they can, uh, log in and the users that are logged in and that have their admin rights are automatically logged into their admin view. Here, a user is also an admin. Let's make, uh, one of the users not an admin. So I will, uh, revoke adding privileges. I will refer to this user's page. And you see he was, uh, redirected to the homepage and he gets that not authorized. So he cannot access the ever dashboard. And that's basically it. This is, uh, how I would be building the, uh, authentication into avo, uh, that you can use for any internal tools that you would like to build. So thank you for watching and see you in the next one. --- # AppStore Review Component | Hotwire Native Bridge | SupeRails #216 - URL: https://superails.com/posts/hotwire-native-bridge-dropdown-app-store-review-prompt-component-superails-216 - Published: 2024-11-25 - Duration: 03:31 - Access: free - Topics: bridge (Strada), hotwire native - Playlists: Hotwire Native Blogpost: https://blog.corsego.com/hotwire-native-leave-a-review-bridge-component Source code: https://github.com/yshmarov/moneygun/pull/141/commits/bfe8ced96cb03408a2e99b9f85d1ebfb7b8dae77 ## Transcript Hello France. So far we have added a bunch of, uh, bridge components to our native application. We have added the NF component that opens a dropdown. We have added the, uh, menu component that opens a model like this. We have added the form component that, uh, gives us a native submit button. And now let's try adding another component for prompting a user to leave a review for our application in the app store. Now, this was, uh, the idea for this episode was, uh, completely taken from Geo Melodi, uh, uh, post on Twitter about, uh, adding the reviews to your hot native application. So, uh, I have, uh, extracted this into a small blog post and let's try introducing this, uh, review prompt component to our application. Again, I'm working based on this, uh, hot native iOS demo application. So first of all, I'm going to go and create a review prompt component in my, uh, uh, demo bridge. In the bridge folder. Going to file a new empty file review component of Swift. I'll paste all of this. We really need, uh, a new library, uh, of, uh, all possible, uh, uh, bridge components that we would, uh, want to use in our applications. I think it would be great to have a library, like a component library of, uh, bridge components. Okay. And then we're going to add the stimulus controller to our web application. So inside JavaScript controller's bridge, I'm adding review prompt controller. Okay. And finally, we can apply it anywhere in our markup. For example, let's do it when visiting, uh, one specific page when we go to, uh, the organization show page. So going to organization slash show. And here, anywhere in the markup, we will add this data controller bridge or review prompt. Now, let's navigate around applica our application. I'll go to new organization. I will go to homepage. Uh, I will go back to organizations. I will open the organization page, and, uh, I would expect to see this, uh, bridge review prompt. Uh, maybe I need to actually restart this application. Oh, yes. Not only this, I should also add the review prompt component to our scene controller. So here I will add you your component self. Okay, now I will restart the application once again, and now it should work. So let's navigate to an organization show page. And, uh, yes. Here you see we get this, uh, uh, model to open the app store and rate the application. Now, you would want to really, uh, render this, uh, component conditionally, uh, on some pages after, uh, uh, user does some kind of positive interaction. For example, Burges says, uh, something on your uh, app. That way he's more likely to give a positive review. Anyway, thanks for watching and see you in the next one. --- # Dropdown Component (iOS UIMenu) | Hotwire Native Bridge | SupeRails #215 - URL: https://superails.com/posts/hotwire-native-bridge-dropdown-bridge-component-uimenu-superails-215 - Published: 2024-11-22 - Duration: 08:54 - Access: free - Topics: hotwire native, bridge (Strada) - Playlists: Hotwire Native Blogpost: https://blog.corsego.com/hotwire-native-ui-menu-dropdown Episode source code: https://github.com/yshmarov/moneygun/pull/141/commits/520147f5f725fd816b9e10739ed3ffa3833b0688 ## Transcript Hello friends. In the previous episode, we added action sheets to our Hot Via native application. Basically, uh, action sheets are, uh, native, uh, uh, models. Hello friends. In the previous episode, we added action sheets to our Hot Via native application. Basically, uh, models with different, uh, action links and, uh, buttons that you can click and, uh, that would, uh, be able to perform the default behaviors that you would want. For example, you'll be able to hide the edit and destroy action buttons from your show pages in the application and render them in a native model. So one way to open this native model we had with the the H TM L link to open the menu, and another one was with the having a native, uh, button inside the UI navigation bar to open the menu. Now again, uh, this native element in the top is called a UI navigation bar. Now, another way to add the, uh, native actions, uh, dropdown would be to use the UI menu element. So the Google UI menu, it'll give us this example. You see, it would be able to have not, uh, screen blocking, uh, uh, action sheet on the bottom, but, uh, drop down with the actions on the top. So for some use cases, you would want to use, uh, uh, action sheets. For other use cases you would want to use. Uh, UI menu. Now let's try to introduce your menu into our existing application. And going back here, I have just a basic rails application running where I have already installed native, uh, with input maps. And, uh, the native application I'm running here in the Xcode is, uh, from native iOS GitHub repository. I'm just running the demo application, uh, with the, uh, demo Xcode project, uh, run file. So, uh, here I opened the app, I just clicked the run, and here I have the native application running and, uh, it is serving my local host, uh, inside the native wrapper. So anyway, in the default, uh, example application, we have this, uh, folder with bridge components. We have a form component, many component and overflow, many component, and we're going to add another component. Let's name it enough component. Let's, uh, go to our bridge folder file on you, uh, nav component. And now I'm not going to kind of create this component from Xero. The cool thing about, uh, native is that you don't really need to know a lot of swift to be able to, uh, run your web application in the, uh, native wrapper. So we are just going to copy an existing the nav component, and I have it here. So here's my article. I will leave a link in the description to this video where you can copy it from. So I'm just going to copy this nav component. Okay. And I need to also say in the scene controller that I'm using this n component. So here where I have defined register bridge components, I will also say nav component itself. Okay? Now I will, uh, restart the application and, uh, in the web app, we can, uh, check if, uh, uh, the web app sees that the n new bridge component was registered in the iOS side. So to see it, you could, uh, go to application h TM L and, uh, say equals request dot referrer. Let's load the page. Um, something isn't loading correctly. Let's see. I will just type something else. Okay, yeah, I don't want to request referral. I want to request that user agent. Okay, let's, uh, try again. Yes. So here you see we have bridge components and uh, we have also our new registered nav component. So our web app sees the nav component. Now on the web app side, I need to add the stimulus controller that is going to communicate with the native nav component. So inside the geos scripts controller bridge, I will create a nav controller Gs. And again, I'm going to copy the controller and paste it here. And finally, we need to, uh, use this, uh, n controller Sunday in our H TM L. So again, let's try using it for our, uh, visit profile page and uh, uh, lookout button. So, uh, here again, I have an example with, uh, applying this stimulus controller. Let's, uh, just see if it works. And then it will go through the H TM L code. So I will, uh, refresh the page and you see, voila, we have this native button. And, uh, when I click the button, we have, uh, a dropdown top on either profile or sign out page. Now, let's have a look here. So, uh, on, uh, a wrapper on a DV initialize, the stimulus controller, you see the synex is a bridge slash slash nav, not just bridge nav, uh, because, uh, nav controller is nested inside the bridge folder. Then we have the bridge sides, right? So, uh, it can be either left or right. Uh, we have the bridge image. It is personal circle, but, uh, if you go to SF Symbols app, you have a collection of all available, uh, icons in the iOS that you can use outta the box. So, uh, here we're using Person Circle, and then we have two links to profile and to sign out again, on the links, we have, uh, the breach nav target. We have, uh, targets item inside our stimulus controller. So we apply them on the actual links and to the links. We also give, um, unique images. Uh, let's, uh, try another syntax. So you can type it like bridge, uh, dash nav target. So here are two different syn to solving the same, uh, problem. Both are valid. And you see we have link to sign out through method delete. Now if it make it button two, let's try making button two. Uh, just method delete, and we don't need Toru. Let's just make an A button. I will refresh. I will even try to restart the app and see if, uh, the button works. So see now the component isn't trended and the component isn't trended because it, uh, accepts on the links not, uh, buttons. Let me refresh without the button. And you see it works. So this is a scenario where you would want to have link to turbo method. Uh, that is not get, uh, and button will not work. Uh, let's comment out the button. And, uh, finally, we would want to have this links available only if, uh, the user is signed in, only assigned in user should have a link to sign out, right? So let's say, uh, if a user signed in, then display these links. Okay, now let's try signing out. And, uh, yeah, we still have this icon visible. Uh, maybe it was sketched somewhere. Let's try starting the application once scan. Okay, so now I'm not signed in. I will, uh, sign in. Okay, here we have that native, uh, uh, button. Folks, let's click around. Let's now, uh, uh, sign out. And you see we don't have the native button because it isn't trended in the H TM L markup. So yes, that's basically it. That's how you can add, uh, uh, native UI menu. Thanks for watching and see you in the next one. --- # Menu Component (iOS Action Sheet) | Hotwire Native Bridge | SupeRails #214 - URL: https://superails.com/posts/214-hotwire-native-bridge-menu-component - Published: 2024-11-15 - Duration: 07:46 - Access: free - Topics: iOS, bridge (Strada) - Playlists: Hotwire Native Bridge Menu let's you open a native menu with links and buttons. For example you can hide the HMTL "Edit" and "Delete" buttons on mobile, and display them in a native menu instead. Blogpost: https://blog.corsego.com/hotwire-native-bridge-menu-component Episode source code: https://github.com/yshmarov/moneygun/pull/141/commits/6f702c621f7a9ee9174e8498a4eb66083f2723f8 0:00 How Bridge Menu works 0:57 Add Bridge Menu to a Rails app 6:35 Use Bridge OverflowMenu ## Chapters - 0:00 How Bridge Menu works - 0:57 Add Bridge Menu to a Rails app - 6:35 Use Bridge OverflowMenu ## Transcript Hot fire native comes with, uh, another default, uh, Strat or bridge component. It's the menu component. So basically, if you go to the example application and click on this menu example, you can click on this open menu button and you see it opens, um, uh, a native menu. And it also has an example of this overflow plus menu where you have, uh, this, uh, button on the top right corner of the screen that, uh, opens the same kind of menu. So you don't have to click on an HTML button to open the native menu. You can click on a native button to open the native menu. And, uh, let's try implementing this, uh, in, uh, rails application. So first of all, I will use the local host to run my native application and on local host on the server. I already have an application running. It's, uh, this application. So, uh, in the web application, if you go to an organization page, you have the edit button and the destroy button. And I want to open this two buttons in the native, uh, uh, actions model. How can I do this? So here I already have this, uh, uh, local host served. I go to organizations. I have the edit and destroy, and I want to serve them in this, uh, native popup. So, uh, going to this native demo repository, we can find the, uh, menu, uh, controller. I'm going to go to this, uh, menu controller into my rails application, into my stimulus, uh, controllers in the bridge folder. So here I will create a new, uh, file menu controller. I will go with the content and, uh, also for the, uh, no, actually, yeah, I use the wrong menu. Uh, here I have the bridge menu. So I need this menu content, okay, the bridge menu. And, uh, I will also cope with the overflow menu to have the icon in the top right corner. So I will go with this overflow menu here. Okay. Now, I, uh, have already previously installed, uh, uh, hot wire bridge in my input maps. So here, already have hot native bridge, and I have, uh, the stimulus control is present in my rails application and in my, uh, iOS application, again, by default in the demo application, have this menu component and overflow menu component. Now, I just need to apply it in, uh, h tm L in, uh, my page. So if I go to organizations slash show here, I have a link to edit and a link to destroy. And, uh, as a previous, I want to open them in a model. So let's have a look at how, uh, these menus are used, uh, in, uh, the example application. Let's, uh, look for menu. You have this bridge menu, HTML file. Let's, uh, try copy in the menu and display it, uh, in our page. I'll just copy it. Let's see if, uh, this menu works. I will, uh, refresh. Okay, we have, uh, a lot of, uh, H TM L but if you click on this open menu, you see it actually opens the menu. Let's click on something. Yeah, so the menu gets, uh, open and this buttons are clickable, but, uh, now they don't have any actions. Let's clean this, uh, up. And, uh, uh, just use it on the link to edit and link to destroy. So, uh, this is the button to open the menu. We will, uh, leave it. We can just clean everything up, and we'll have a link to edit and link to, uh, destroy. Um, so for the link to edit, we will need to add data, uh, bridge menu, target item. So, uh, I will say data bridge menu, target item, um, like this. And, uh, we don't need the data action, we just need this bridge menu target item. I think, um, we can remove everything else, and, uh, we will need this, uh, uh, data bridge menu target title with the title of the dropdown. So you see here, you see it's like an option. Let's name it actions. We can remove everything else. Uh, we can remove this. Okay, much, uh, cleaner. Let's see if, uh, this works. I will, uh, refresh the page. Here we have the added button. I click on open menu. And you see we have the added button. I click on the added button, and it actually pops up the edit model. So our, uh, clean up, uh, version works. Let's, uh, also add that destroy button. And the destroy button. Again, I will just add the, the bridge menu target item. Let's refresh. Okay, now clicking on open menu. You see we have edit and Destroy. I click on destroy, and, uh, we have the default behavior so we can destroy the organization or cancel. Uh, looks good. Now, uh, again, we have the duplication of Edit and Destroy here and here in theory, we could, uh, uh, wrap these elements in data controller, uh, menu, bridge menu, or we could just, uh, uh, apply a hidden, hidden class to this, uh, element. Uh, we can do it later. For now, we have applied the menu controller, but we haven't over applied the overflow menu controller. So we still want to, uh, remove this H TM L button, and we want to have a native button here. Now, to do this, we can just, uh, have a look at the example of the bridge overflow. And all we do here is we add this additional data controller, bridge overflow menu to our action button. So here we have our action button, and, uh, we will just add data controller, bridge overflow menu. And, uh, the native button is going to click on this button. Let's, uh, try, if it works, I will refresh. You see we have this native button pop up, and, uh, what this native button does, it, uh, just clicks here. So you see we have this native button that opens, uh, this actions, uh, pop up, looks well, works, uh, well. So that's how you can use, uh, alterna menu that, uh, opens with H TM L, or overflow menu that opens with a native uh, button. Thanks for watching and see you in the next one. --- # Form Component | Hotwire Native Bridge | SupeRails #213 - URL: https://superails.com/posts/hotwire-native-bridge-form-component-superails-213 - Published: 2024-11-11 - Duration: 11:00 - Access: free - Topics: iOS, bridge (Strada) - Playlists: Hotwire Native Hotwire Native Bridge **FORM** Component let's you hide the HTML "Submit" button on a form, and display a native "Submit" button on the top-right corner of the screen. It also "disables" the submit button after it was clicked, so the user doesn't click it twice. We will also add `data-turbo-action="replace"` (instead of default `"advance`) to the form, so that the modal dismissal works smoothly, and flash is displayed. Finally, `bridge_form_with` helper will make the form reusable! Blogpost: https://blog.corsego.com/hotwire-native-form-component Episode source code: https://github.com/yshmarov/moneygun/pull/141/commits/80b12616fe6257070bd1321b46a607e624fb6382 0:00 How Bridge Form works 4:02 Add Bridge Form to a Rails app 6:47 Redirect and render flash 7:40 bridge_form_with helper ## Chapters - 0:00 How Bridge Form works - 4:02 Add Bridge Form to a Rails app - 6:47 Redirect and render flash - 7:40 bridge_form_with helper ## Transcript Now, if you go to the default, uh, hot file native iOS uh, uh, repository, there is this demo application, and you can run this demo application in the Xcode. So by clicking on the, uh, by opening it in Xcode click and Play, you will have this iPhone pop up with the hot file native demo application that you can click around and see the default, uh, native behaviors outta the box. And on the bottom we have these different bridge components. So, for example, there is this form example. If you click on this form example, there is a form with a couple of fields and a native submit button in the top right corner. So by clicking on the submit button, you'll submit the form. Let's see how the same page works in the web. Now, if we open this, uh, demo, uh, here you will see the default URL that is being served with this, uh, application. Here it is. I will open this URL, um, and here in the bottom you see it's kind of the same, uh, uh, page, just the way it looks on the web. And here we have this form example. And you see in this form, I have the HTML submit button for the same form. So, uh, in the the native view, we are hiding the HTM L submit button and display in the native submit button. And in the background, this, uh, native submit button actually submits, uh, the hidden, uh, H TM L submit button. Now, how can you use this kind of behavior in your own, uh, application? So, uh, first of all, uh, going to native, uh, iOS here in this demo O application, we have this form component, uh, already installed. So you can reduce this form component and to use, uh, this, uh, iOS form component in your, uh, application. You would, uh, need to look how this, uh, hot file native, uh, web application is structured. So here we have this hot native demo repository. It is the web app itself that has been served here. And inside it we have the, uh, form, uh, controller. So it is the stimulus controller that, uh, interacts with, uh, this, uh, native, uh, form component. And this stimulus controller is being applied on an H TM L page. Let's try finding the H TM L page form, uh, bridge form. So, uh, here is an example of applying this, uh, uh, stimulus controller to an HTML form. Now, let's try applying it in, uh, a form in the rails application. So here I have a basic growths application that it, it, I'll be using the, uh, mangon repository for the example. So, uh, here it's, uh, just a basic HTML form. I have the H TM L submit button, and, uh, I have error rendering. So just, uh, basic Rails app. Let's try serving this Rails app in our IOS. So, uh, going to this, uh, demo file, instead of serving the remote URLI will serve the local URL. So the current URL is going to be the local URL. Let's see that I have the server running. Yes, I do. Okay. I will, uh, run the application with the local URL. And, uh, here I'm actually already logged in. I can go to the list of organizations, the organization, and here I have, uh, a form. And you see it's already opened in a model, uh, by default by because, uh, in this, uh, demo application, we already have this part configuration. And it says that all the URLs that start with, uh, that end with slash new or slash edit by default are opened in a model. But we want to show this, uh, native, uh, submit button that will submit this form. Now, to do this, I would need to first of all install, uh, uh, bridge or previous Strada in my Rails app. So going to the hotel native, uh, uh, docks, there is this, uh, command line, uh, thing that I can run if I'm using input maps to install Hotel native bridge. So I will run it in my, uh, rails, uh, app. Okay, let's have a look at our input map file. Yeah, so I have, uh, added hot file native bridge. Now I need to add the stimulus controller. Let's have a look at the stimulus controller, uh, form controller. I will just go with the stimulus controller and I will put it in the, uh, stimulus controllers in the bridge folder. So I just created the bridge folder, and inside I will create this form controller. I'll go with the contents of this form controller, and, uh, I'm going to apply it to my form. So let's open organization slash form, and I'm going to apply the stimulus controller. So first of all, the name of the controller is form, and it is inside the bridge folder. So I will say, uh, data controller bridge, dash dash form. Now it's bridge dash form, not just form because uh, I have this nesting inside the bridge folder. Uh, next I have the submit target that needs to be applied on the submit button. So I will say data, um, bridge form, uh, target is submit. Now have a look at the syntax here. The synex works well. Uh, and I can also apply the submit stat and submit. And let's see how these are applied in the, uh, HTML example. So here we have this, uh, action, uh, to disable and enable the submit button. Uh, so while the submission form submission is in progress, the uh, button will be disabled. So, uh, also here I will have data action to submit start and to submit end. Okay, let's, uh, see if, uh, it works in our form. Now I will, uh, close the model. I will open it again. We have error loading page. Let's see why. Yeah, I don't, didn't start the server. Okay, let's retry. And you see we have the native submit button. So it's working. Uh, beautiful. Uh, we can close the model. We can, uh, try actually submitting the form. So organization number five, uh, click on this bottom. The organization has been created and have a look at this via the behavior. So, uh, when we created the organization, we close the model, see the previous page, and then there's the new page, and we don't actually see a flash message. So to see this kind of flash message after, uh, submitting the form and, uh, to not have all this fear the behaviors, we're going to also add another data durable action that is going to be not, uh, advance the navigation by default, but replace, let's have a look at how this form submission works with turbo action Replace. So organization number seven, create organization. And you see, we then have all this loan animation of, uh, a closing the model and then direct, we just, uh, uh, are on the new page and the flash messages work. Okay. Now imagine you have a lot of forms in your application, and you would want to add this, uh, uh, controller action and this, uh, data target to all of them. This could take you quite a while and it'll make the code a bit, uh, more clumsy and less maintainable. Now, uh, Joe Maslo in his daily log application has a solution for this. So he has this form helper that wraps around the default, uh, form width element, and adds, uh, uh, this, uh, data breach form target submit by default to the submit button and, uh, adds, uh, the breach form controller and the action, uh, by default to the form itself. So it would, uh, clean up the syntax a lot. Let's try, copy this form, help into our application, uh, application helper. Let's see where our helpers are. I will create another form helper and I will cope with this contents. Now see, uh, this file still has this older, uh, name in the strata. Uh, you could rename it to Breach Form Builder. Uh, but now we can use the strata phone with, let's try using it. So, uh, I will, uh, use, I will actually remove all the code changes added here. I will just use, uh, Strada form with, let's try opening the, uh, organization, edit or create again. And, uh, yeah, it looks like wasn't applied. Let's, uh, see why. Uh hmm. Okay, so we have strata form width, um, bridge form Target. Yes, bridge form. Let's, uh, try refreshing. Let's go and try opening new organization. Okay. Yeah. So, uh, it works. It looks like I just needed to, uh, no, no. It works for Create, but not for update. Now, it, uh, will maybe work if I add a label here, like save, let's save if it works now. So, going to edit. Yes. So, uh, it looks like in some cases we need to explicitly have the label defined on the Form Summit button, but, uh, but not just, uh, the label should be guessed. So now the label, it's present and, uh, it works. Let's again, go to new organization. Yes. So we have this, uh, save button. Uh, okay, so this, uh, Synex is, uh, much, uh, cleaner. And, uh, yes, this is basically how you can use, uh, the bridge form component in, uh, rails application. And don't forget, uh, about, uh, adding data, durable action, uh, replace on the, the forms that you want to open in models, and after which you want to show a plus message. So, thanks for watching and see you in the next one. --- # Cmd+Enter to Submit Forms | SupeRails #212 - URL: https://superails.com/posts/command-enter-to-submit-forms-superails-212 - Published: 2024-11-11 - Duration: 06:14 - Access: free - Topics: stimulusjs We will explore 3 different ways of solving the same problem with StimulusJS. I think solving the same problem with different approaches is a great way to learn. 0:00 (BAD) Targets and Event listeners 3:40 (BETTER) data-action in view, keyboard logic in stimulus 5:05 (BEST) Stimulus keyboard events Stimulus keyboard event docs: https://stimulus.hotwired.dev/reference/actions#keyboardevent-filter Blogpost: https://blog.corsego.com/stimulus-cmd-enter-to-submit Source code: https://github.com/corsego/212-stimulus-keyboard-submit/commit/98126bb58effc79439d235d02cc8f9f2028dda48 ## Chapters - 0:00 (BAD) Targets and Event listeners - 3:40 (BETTER) data-action in view, keyboard logic in stimulus - 5:05 (BEST) Stimulus keyboard events ## Transcript Once upon the time I was browsing a forum thread and, uh, I noticed a suggestion. So customer suggested Jeremy to add the feature of submitting a form by clicking command enter. And actually this is a popular feature in many applications on the modern web. So you would be filling in a form, type in something, and by clicking, uh, command enter, you'll submit this form. So I have implemented this behavior on S rails and let's learn to implement this behavior together in New Rails application. And we're going to do it in three different phase from the first phase to the best way. And I'm going to talk about which, uh, why each phase is better. So here I have basic, uh, new rails application. I've got a scaffold of posts and I've got this body field. And currently to submit this field, I can only, uh, manually click on this great post button. Now, I'm going to add a stimulus controller that is going to listen to click and, uh, command control. If you are on, um, windows Commander Control, enter, and if it was clicked, we're going to submit this form. So here is our form. Uh, we have the form defined. We've got the text field and the submit button. Let's create a stimulus controller to submit this form. So I would say rather generate stimulus form. Here it is. Now they're going to have, uh, two targets, the, uh, input target and the submit target. And then within then the input target, uh, command intervals, uh, clicked. They're going to click on the submit target. Okay, so let's have, uh, a look. I'm gonna create two targets. So static, uh, targets input and uh, submit. And I'm going to add an event listener to the input target. So this is going to be the input target. So this dot input target at event listener input. Uh, and the, it's going to trigger the submit the action. This submit bind. And they're going to listen not to input, but to key down. So when a key is being pressed, key down, and let's create a submit action. So, uh, it commands if event is enter, uh, click on the submit target, but they're going to do it not just when the key is enter, they're going to do it. Then the key is command or control plus enter. So, um, they're going to say, uh, uh, let press, uh, control equals, uh, control key or meta key to handle, uh, both types of keyboards. And we will say if, uh, press control and event key equals enter, prevent default, uh, and submit the form. Now let's apply this, uh, in our HML form. So here I will say, uh, data controller form. And here I will say data, uh, data, uh, form target input. And on the other one, data form target, uh, submit. So when we're listening to vendor in this input, we type control enter, we click on the submit, uh, target. Let's see if it works. I will, uh, okay, it looks made typo. I should remove the scoma. Okay, let's start typing. I click on command the enter and the phone was submitted. So it works. Uh, it's okay, but uh, we can do it in a better way. We don't need to actually have all these, uh, event listeners and these targets. Uh, let's remove, uh, these, and instead we are going to just have a data action inside this text error. So data action and action is going to be key down. And on, key down, we are going to, uh, trigger the, submit the action of the form controller. So form submit, so then a key is being pressed. We trigger this form, submit. We can also remove this other form target. Let's see. So go to edit the post, click control, enter and have this and define the, this submit target. Click yes, because now we don't have the submit target. We just want to submit the form. So this dot element do request submit. Now this element is this element on which the form controller is defined. So the form controller is defined on the, the form. So it is going to submit this form on which the form element is defined. So this element request submit, Let's try again. And you see it worked. It's, uh, much better, but we can, uh, clean it up even more. We can, uh, leverage, uh, steam loss keyboard events and, uh, improve this data action to work as key down control plus enter. And for the other keyboard layout, key down plus meta plus enter. And we can clean, uh, everything out of this submit, uh, uh, action. So you see inside the stimulus, the only thing we have is the submit action and the all the logic is inside this, uh, data action. So let's, uh, try once again clicking on command enter and the form has been submitted. And you see our JavaScript is very clean and, uh, everything is defined. Uh, all the logic on our HTML that is much easier to edit in this specific place. So here are three different approaches. Thanks for watching and see you in the next one. --- # Clickable Video Chapters. Vimeo Player API | SupeRails #211 - URL: https://superails.com/posts/clickable-video-chapters-vimeo-player-api-superails-211 - Published: 2024-11-10 - Duration: 10:49 - Access: free In my app I embed videos with Vimeo. I’ve just implemented Video timestamp/chapter navigation on videos embeded on SupeRails. I was inspired by Chapters on Youtube. 0:00 How Chapters work on Youtube 1:19 Dynamic Vimeo video embeds 3:58 Vimeo Player js - custom play button 7:10 button to go to timestamp 8:44 parse text to clickable chapters Episode source code: https://github.com/corsego/211-vimeo-player-js-text-2-chapters/commit/86dfc94c15486040985641aaa1ac807c27054b63 Blogpost: https://blog.corsego.com/vimeo-player-js-timeline-navigation Vimeo Player JS: https://github.com/vimeo/player.js ## Chapters - 0:00 How Chapters work on Youtube - 1:19 Dynamic Vimeo video embeds - 3:58 Vimeo Player js - custom play button - 7:10 button to go to timestamp - 8:44 parse text to clickable chapters ## Transcript Hello friends. So, uh, YouTube has this feature of, uh, chapters in a video. Basically, often when you go to a video, you will be able to hover on the video timeline and see different titles of different parts of the video. And if you click, uh, on this action ribbon, you will see this sidebar with chapters. And this video will be able to more easily navigate to the part of the video that, uh, you are interested in. Particularly. Now, most often, these, uh, chapters are not automatic. The video creators actually type them in manually in the video description. So if you go to the video description, you see here I have the, uh, timestamps that I put manually and their titles. And in the video editor, it looks like this. Here I have the description. I reversed the video myself, and, uh, I put in the timestamps and their titles, and I actually implemented the same feature in super ails. So here I have, uh, my, uh, video that I embedded from vimo. And in the description I have these, uh, timestamps and I put them in the, in the sidebar so I can click on different, uh, timestamps and navigate to different, uh, moments in the video so that it is easier for you to find what you are looking for. Now, how can we add this kind of, uh, timestamps in the VM o player in a new Rails application? So first of all, I have, uh, created ER account and, uh, imported a video. And, uh, here in my description, I already put in the timestamps manually. Now, uh, I want to embed this video in my Rails application. So let me go and create a new post. A post can have a female ID and a description. Let's, um, copy with this description. I'll put it here. And, uh, the video ID will be actually the video embed Id, not just the id. So here is the video id and this is the embed, uh, uh, code, I guess. So if I click on the share embed, I will copy them, embed code, and put it in the posts, uh, show. So, uh, here in the video Id have the id then fashion mark H and the kind of, uh, other part of the code. So this is the like, full, uh, ID of the video. I will also put it in the video description or in the video vmo ID here create post. I've created the post. And, uh, as I've got this code here, I've got the video embedded. Now, let's, uh, make the part of the embed code dynamic so that, uh, each post, uh, can have a different video ID and different video will be embedded. I will have equals at post, uh, V id. Okay, I refresh this post and I have the same video embedded, but if I go and create another post with, uh, an invalid or an empty al id, you see the video isn't being Embedded. So let's go back to this post here. I have a video embedded. I've got the description and, uh, I want to interact with this player. So I want to add the custom buttons as I did, uh, here to go to different timestamps. Now, first of all, to be able to interact with this VO GS player, we need to use the player Gs, uh, library. We can actually see a link to it Here. We've got the script player Vimeo. Player js. Yeah, let's clean up this, uh, code around the I frame a bit. I think we don't need anything. Let's try live in just the iframe itself. Uh, yeah, okay. It looks, uh, bad because it takes up all the space in the page. Uh, yeah, but I think we don't need the script. Yeah. So without the script, everything works as before. Okay. Anyway, to interact with this player, there is this, uh, library that has all the documentation and we've got this, uh, package, uh, that we can install in our application. Now we are using input maps, not, uh, NPM. So, uh, the syntax to install, uh, the Vimeo Player Library in input map would be, uh, input map pin, and, uh, the same ID at Vimeo slash player. So let's say, uh, bin import map bin at Vimeo slash Player. You see if we go to input maps, we have pin display. Now let's create a stimulus controller, rails generate stimulus vmo. Let's go to the stimulus controller. VMO controller. And here I will say import, uh, player from VMO at player. Uh, now I'm going to, uh, wrap everything into this, uh, VM o uh, uh, controller. So div and, uh, data controller VM O. Okay. And, uh, within the VMware controller, we have the player that is the actual iframe. So on the iframe, we are going to have a data ephemeral target player, let's say data VM o target equals player. Let's define this target in our stimulus controller, static, uh, targets, uh, player and on connect, we are going to initialize this, uh, VM R player. We will say, uh, this player equals new player, this player target. Okay, let's, uh, see if, uh, we get any errors. Now I will open the console refresh, and if we don't Get any, any arrows, now let's try adding, uh, play button. So I will say play, and it'll be this player play. So, uh, we have this play defined in, uh, the player js. Here we have player play. Uh, let's try adding this button in our controller here. So I will have a button, uh, data action goes to VMO controller. Play action. The button name is play, and let's close the button. Let's see if it works. So, uh, I have this play button, and you see it actually starts playing the video. Now let's try playing the video. The moment the video loads, let's try move this play, play to the connect action, and let's see if it works. So I will refresh. You see, it tries to start playing and we get, uh, an error. You see, there's no video playing. We get this unhandled promise rejection because, uh, I think the browser doesn't allow us to autoplay videos, so that's nice. Okay, let's remove this play, play from the connect. Let's now try adding a button to go to a specific moment in time in the video. So we would have a button named, uh, let's say set current time, and it would use the set current, uh, time, uh, action from the Vimeo GS docs. So player set, current time. Uh, this player set current time to event target dataset time. And we would also want to prevent default event, prevent default. And I think we can also add this prevent default here because we have, uh, buttons. So there can be some unexpected behaviors from buttons except of, uh, this geo script action that we do expect. Um, now let's try adding buttons to go to different moments in time. So we need to have, uh, that data time in the button. Let's add the button. The action will be set current time, and we'll have data time, for example, 30 seconds and go to 0, 0 30. Another one will be go to two minutes. And, uh, 10 seconds it'll be, so two minutes and 10 seconds is, uh, 130 seconds. Uh, let's see if this works. Now I, uh, go back to the layer. I have, uh, these two buttons go to 30 seconds, go to two minutes, 10 seconds. And you see the buttons are clickable and evoke. So we managed to add some external controls to our player. Now going to the cool part of turning of pause in this text and turn these, uh, timestamps into these kind of buttons. First of all, this text doesn't look very readable. So let's, uh, go to our post partial where we have this post description rendered and it'll have simple format, post description. Okay, now it looks, uh, more readable. And now we need to pass this text and, uh, turn these, uh, uh, timestamps into buttons. So, uh, I'm not going to reinvent the wheel. I already have, uh, written a script for this. So, uh, it would, uh, look through the text, uh, find this kind of, uh, timestamps and turn them into buttons. So, uh, here is the example of the button code. I will put it into application helper timestamps to buttons. And, uh, let's, uh, add this timestamps to buttons around the simple format post description. Let's see if it works. Okay, so it does add the buttons, but it is not sanitized. So let's try adding like HTML safe. And now these are actual buttons. Uh, they're not clickable because, uh, the post partial is not within the data controller VMware. So they'll have to put it, uh, inside here. And, uh, now let's try looking around and you see, go into different moments of the video works. So, uh, wonderful. We learned, uh, the basics of using the vimo just player. And, uh, uh, we added this, uh, helper to pass a text for specific projects and turn the projects into some H TM L buttons. That's basically it. Thanks for watching. --- # Pros embed Vimeo. Amateurs embed Youtube. Learn Vimeo API basics | SupeRails #210 - URL: https://superails.com/posts/vimeos-competitive-advantage-over-youtube-superails-207 - Published: 2024-11-09 - Duration: 12:27 - Access: free - Topics: api I host SupeRails PRO videos on Vimeo It is great because: - no youtube branding & ads = more premium feeling - I don’t have to think about hosting my videos & styling my player - has basic security features, as “video can be viewed only on selected domains” - fixed cost, not usage based like MUX.com I see it as "Vimeo is B2B, Youtube is B2C". But having hundreds of videos in my library I want an easy way to import videos from Vimeo into my Ruby on Rails app. I did not feel comfortable using an Vimeo API gems, but their own API is very good. You don’t need an api gem to use an API! Episode source code: https://github.com/corsego/207-vimeo-api/commit/03a798a9ceeeccf511db0106ef973c6547b9201a Based on this blogpost: https://blog.corsego.com/vimeo-api ## Transcript Hello friends. So as you know, I've got the YouTube channel where you can view my videos, and I also have my own video platform, supers.com. Now this video is actually an embedded YouTube video within Supers, and you see it has a lot of YouTube brand, 1, 2, 3, 4. And if I start playing a video, hello and pause. I also have this ribbon that has recommendations not only for my content, but for other creators content. So you embed the player, but you don't really own the player, and that is the price of the free GI hosting. Now if you look at another video that I have here, you see it doesn't have any YouTube brand. What is, and uh, it looks quite, uh, good. Now, it isn't an HTML five player. It isn't, uh, wrapped in JavaScript and I don't use active storage in this application at all. Actually, I'm using Vimeo and uh, I'm embedded the video via Vimeo. Why did I choose Vimeo? Uh, first of all, it is, uh, quite, uh, cheap. So I'm paying around $60 per year for hosting all my videos, and it is a fixed cost. So no matter how many videos I have and how much they have been watched, it is a fixed cost. So I got all my videos here. Other cool things are that, um, I get to customize my play without writing any additional code, and it's quite a nice thing to outsource. And there is some basic, uh, uh, security here. So, for example, I go to the embed settings of my videos and you see, I allow embedded in the videos on, on specific, uh, domains and, uh, it makes them a bit more secure from, uh, virus, I guess. And, uh, I've got like the video, I've got thumbnails, title, description, uh, I can add tags and I can import all this information from Vimeo into my client application. Now, there are also other tools, uh, for premium video hosting, unlike Vimeo, for example. I think these guys, uh, are using, uh, MUX. And if you go to the pricing of MUX, you say there is a fixed cost that can be like seven, 3000 per month. And uh, yeah, that's like smaller plan, but uh, bigger plans for them, for example, I think would be at least 3000 per month. And uh, you see, you pay for the storage. So the more videos you have, the more you have to pay and the more viewers you have, the more you have to pay. So it is a very variable cost. And uh, again, the cool thing here, I don't even have to have active storage or an uh, Amazon S3 account. I'm just storing everything, uh, on uh, Vimeo. Now another thing that Vimeo is quite good at is the API. So let's try using the Mu API to get my videos and uh, uh, get the general feeling how you can use it. So first of all, I'm gonna create an API application. Let's create one. Let's give it a timestamped name, 1858. Now, it's not going to be an or out application, it's just going to be me accessing my content. So I'm answering no here create application. And now I can create an access token, send an unauthenticated access token Will have access only to public videos. But I want to get my private videos. So I'm going to say authenticated. Basically I'm going to get with authenticated, I'm going to get my private information and I'm going to say that I want to access private videos, uh, generate the token here, have the token, and let's use it with the API. So here's the API reference and the basic API calls. So then this is the root of the API. We can get something about a user, we can get the metadata of a specific video, or we can get all the videos of a user, for example, let's try using it. So here have, I have a new rails application. I'm going to create a job, let's say rails generate job vmo and let's open VMO job. I will say access token equals the one that they have just created. The URL of the API is, uh, uh, this one. So URL equals a pcon. And uh, I'm going to use third day GEM to make a P request. So I will install the gem third day bundle. At third day, it'll add the gem third day to my gem file and run bundle to add the gem. And now I'm going to use fairday. So I will say connection equals fairday. UURL equals this URL uh, do fairday. And now I'm going to add the authentication. So I will say Faraday dofest authorization, it'll be with bearer and it'll be with this access, uh, token. And now let's try making an API call. So the most simple API call that they can make as an authenticated user is get slash me. Uh, let's say, uh, response equals connection Do get slash me and let's run this job. Let's, uh, open the rails console and let's say result equals uh, vijo dot perform. Now we have the result. Uh, let's get result body. Let's pass the result body. Let's actually do it here. Let's say if response success, let's say return, unless response success, we are going to continue only if it was a successful response. If we'll say, uh, body equals J pass response body. Let's, uh, reload because we've done some changes in files and, uh, run the job again, result, you see we have the information about this user. Let's get result, uh, URI and we have this, uh, endpoint slash users and our own user id. Let's try make a request to our own user id. I suppose it would give more or less the same. Let's say, uh, user ID equals body, uh, URI. And we're going to make a request to this endpoint. Let's again copy all of this. Reload. Now we are making a request not to uh, just myself, but to a user by id. And we get the kind of the same information because we made the request about the same user. And now let's try getting all the videos of a specific user. Now, one way we could do it would be well for ourselves, we can just get slash me slash videos or we could get the it with a slash I think, uh, uh, users, uh, slash the idea of the user slash videos. Let's get just our videos. So again, I will, um, make the request. I will, uh, pass the results and display them. We don't need the this. So reload. And let's get all our videos result. You see in total we have 520 videos uploaded. We have 25, uh, per page with this API endpoint. And the in data we have the videos themselves. Let's see results data, uh, dot count first. So we have the name of the video, the description, we have the layer embedded URL that I'm actually using. And you see the ID of the video, uh, here it is, is not the same as the ID of the embed URL. You see there is some additional character that we need to add that is quite unique for each of the Vimeo videos. And if it'll go further, we can also see the URLs of, uh, Vimeo images. So, uh, we have some different variant of Vimeo images that we can also import into our application. So I can kind of host both the videos and the thumbnails and descriptions and tags on Vimeo and input them all into my application. Okay? So, uh, have, have, uh, a list of videos and we get the first video. Let's just try to find one video by id. Let's have a look at, uh, this first video and let's get its URI. Okay, let's try making a request to get just this video by id. I will, uh, say, uh, response, uh, equals connection, get uh, slash videos and the id and let's get the video body. I will again reload 'cause I've done some changes in the file. I will run the job. And in the result we have just, uh, this video. So we managed to get this fun video by Id. Looks, uh, quite good. So yeah, this is the basics of how you can use the Vimeo API. Now let's try storing this information in our application. Now, uh, we could store the name, the description, and so on as separate attributes on a video or on a post model. But we can also store them all in, uh, Jason for example. So let's create a video model that will not have a separate attribute for everything. It'll just have, uh, uh, it'll just adjacent with this payload. So I will say re generate modal, uh, video. And the video will have a Vimeo payload. JSON B. Now JBI think exists only in, uh, PostGresQ. It doesn't exist on escalate, I think, but it is a like more performant, uh, way to pass. Uh, Jason, I think. So let's go to our migration, create videos and we'll just have, uh, female payload. Let's make it JS B. Oh, actually here I have escalate. So it's gonna to be js o relatively migrate. And uh, let's try storing, uh, let's try creating a video in our application and storing this, uh, uh, payload. So I will say, um, video equals video dot great. And Vimeo payload will be body two hash. Now it is important to make it to hash, otherwise it'll not be stored properly. So, uh, this would create a Vimeo, uh, object in our application. That's first Try Rails console video. Do count none. Now I will run the job, um, Vimeo job perform now, and let's say video first. And you see we have, uh, this v Vimeo payload. We have stored all the information and we can pause it. Let's, uh, uh, get the URI, uh, yeah, we need to get within the payload, so video first dot v, payload, and inside we can get the, uh, name or your I and So on. So I think it is a good, uh, um, idea not to store like just 10 attributes that you might need, but store the whole payload of, uh, the object that you, that you got from the API and be able to do anything with it that you want. Okay? That's about it. So we manage to play with the vm, OAPI. We got the information about our user. We got all the videos of a user, we got, uh, information of one specific video and created a video object in our database. Thanks for watching and see you in the next one. --- # Moneygun - Add ActsAsTenant to an existing application | SupeRails #209 - URL: https://superails.com/posts/moneygun-add-actsastenant-to-an-existing-application - Published: 2024-10-30 - Duration: 11:39 - Access: free - Topics: moneygun, saas, saas boilerplate, Mulitenancy Enhance Multi-Tenancy with ActsAsTenant in Rails! In this episode, we’re diving into ActsAsTenant—a gem that strengthens your multi-tenancy setup by ensuring that each tenant can only access their own data. While ActsAsTenant doesn't provide the database architecture for multi-tenancy, it adds an essential layer of security and data isolation to your Rails app. In the previous episode, I introduced Moneygun, a multi-tenancy boilerplate for Rails. Now we’ll take it a step further by integrating ActsAsTenant into our existing setup, enhancing security and data management. 0:00 Multitenancy basics 1:50 Install ActsAsTenant 5:30 Enforce Require tenant Pull Request adding ActsAsTenant https://github.com/yshmarov/moneygun/pull/120 Gem ActsAsTenant: https://github.com/ErwinM/acts_as_tenant ## Chapters - 0:00 Multitenancy basics - 1:50 Install ActsAsTenant - 5:30 Enforce Require tenant ## Transcript Hello friends. So in the previous episode, I have introduced Mon Gun, uh, multi-tenancy boilerplate for Ruby Rails, where basically you can have, uh, organizations and users can be members of an organization and they can, uh, have role-based access to different resources inside the organization. It is built more or less on this kind of architecture. Um, here's an example of, uh, a user that can have multiple memberships within an, uh, account organization or this is basically the, uh, architecture where everything belongs to a tenant organization or account, whatever you name it. And, uh, all the downstream resources should have, uh, a tenant or organization ID set. So everything is scoped to an organization. Now, whenever you want to implement multitenancy, you would maybe think of adding some kind of multitenancy gem that, uh, would, uh, have to solve all your multitenancy problems. But, uh, usually this kind of multitenancy gems, they don't, uh, provide the whole solution to, uh, like the views and the controllers. They only provide some kind of, uh, mechanism to scope, uh, the data from the database. So for example, GEM acts as sta it's main feature is being an additional layer of security. So, uh, if you add the gem access talent, you can, uh, add the, a default scope to your projects, for example, that you cannot, uh, query all the projects in the database. You have the first set and, uh, tenant or, and organization. You can only get projects within one organization. So this kind of an additional security measure. Now let's try adding the gem external to an existing application. In this case, I'm going to edit to the default code provided by Mon Gun and let's see how we can make external work. So first of all, I'm going to go and add the gem access tenant to my gem file. Uh, yeah, I'll add it next to authentication authorization bundle. So going further, I need to set the current tenant for the access tenant gem. So I'm not doing it by based on sub domain or based on domain. We have, uh, uh, URL or root based, uh, uh, way of setting the current, uh, organization. So I'm going to set the current tenant through a filter. I'm going to copy this and, uh, where I'm going, am I going to set the current tenant? Now? Definitely not on application controller because I want to have resources in my application that, uh, don't require current tenant. I can set it somewhere on organization level. So all the resources that are scoped to organization will, uh, have to have, uh, this in their controllers. Uh, they are going to set the tenant, uh, uh, inside the resources that belong to a tenant or organization. Now you see in inboxes, they inherit from organization space controller, same with memberships. They inherit from organizations base controller. And in the space controller, I have this, uh, before action tool set their organization. So instead of this before action, I'm going to copy the code from access tenant set, current tenant through filter. Uh, the before action will, let's name it, uh, set tenant or it can actually be the set organization. And uh, we'll have, uh, current organization, uh, equals, um, equals, uh, actually it's going to equal uh, this current user organizations signed by Pars organization. So yeah, we have uh, have as in current account, we certain current tenant for excess tenant and uh, we might still want to say at the organization equals current account. Okay. Uh, we can comment out the previous way of how we were setting the current organization. Now we also set the, the organization on exist, tenant level. Let's, uh, try run our tests and see if the tests are possible. Okay. And define method set organization. Yeah, I have to, uh, advise it undefined because I didn't rename this one. Okay, let's run the tests once again. Okay, so all the tests, uh, are passing looks, uh, fine, but, uh, we really didn't, uh, do anything yet. Uh, we need to still say that, uh, each model that, uh, has to be scoped on organization, uh, belongs to it via Access standard. So, uh, going further, we have the example of scoping the models. They're going to say instead of belongs to organization, they're going to say acts as tenant organization. So, uh, which ones artists, these are membership. RB instead of belongs to organization, we'll have as tenant organization. It's kind of belongs to plus a scope. And uh, same with inboxes. An example of, uh, message resource instead of belongs to organization will have extended organization. Let's run the test once again and see if, uh, they're still poss yes, they are still poss. Now let's try getting all the, uh, inboxes in our application inbox. Do all you see, we can get the inboxes across multiple organizations at the moment from our console and we could in theory say inbox destroy all and it'll destroy all the inboxes. And it is, uh, not considered a good security practice to be able to, uh, get all resources from all the organizations, uh, programmatically or from kind of some kind of super user. You don't, you shouldn't have access to resources outside of your organization. So, uh, that's why we are going down to the main security feature of X as tenant. That is, uh, the setting to require a tenant. Let's create this X as tenant initializer. Uh, if you are adding the gem tenant, I think there's uh, no point of adding the this gem if you don't, uh, uh, add the required tenant as true. So let's say require tenant, let's run the tests. Okay? And now that tests are failing with the no tenant set. Interesting. Now before fixing the test, let's have a look at our rails console. So again, I will try to get all the inboxes inbox. Oh, and you see I get this error access tenant, no tenant set. And this is what, uh, this, uh, config required tenant does. So I cannot get all the tenants or all the inboxes. I have to have a tenant set. Let's try sending the tenant in the console acts, uh, as tenant current tenant equals organization dot first. Now let's get all the inboxes. And you see, inbox all gets only the inboxes that belong to this organization. So you see this is an additional level of security, a default scope, having to set the organization to be able to get resources scope to the organization. This is, uh, a good security feature. Okay, so let's go and, uh, make our application work with, uh, this, uh, setting of required tenant equals true. First of all, we see that this, uh, test is failing, uh, organization's controller, uh, no tenants set. So we've got all the tests kind of failing at the moment. Now let's try run our server and uh, try to see something. So you see, I just tried to go to the root path and current user organizations fails because we need to have the tenant set. Why do we need to have the tenant set to just view the organizations where the current user is a member? Well, because we set exist tenant on the membership. So actually you don't want to set exist tenant on the membership of current user. You want to set it on all the other downstream models except of membership. So membership is just going to be belongs to memberships should be accessible without having, uh, like the list of memberships of the current user should be accessible without having the current tenant set. So, uh, okay, now launching the app, folks, I can navigate around. Let's try running the tests again. Okay. And we have just three tests, uh, failing. So, uh, uh, Uh, one is in organizations controller and two in inboxes controller. So organizations controller test, uh, 64. Let's have a look at this. Test line 64 is for on admin can destroy an organization. Let's go to our organization's, uh, controller here for Destroy. Now for the destroy action, we need to actually set the current tenant. Why? Because, uh, it is going to affect the downstream, uh, uh, association. So, uh, to be able to destroy, like organization has many inboxes. To be able to destroy the inboxes of an organization, we need to set the current organization. So here we're going to manually set the, the tenant, uh, I will say acts, uh, as tenant with tenant at organization, do, uh, organization destroy, and this way the downstream, uh, inboxes will be destroyed. Let's run the test once again. And we have just, uh, two tests failing inside our inboxes controller. Test line 23. So you see this so difference is failing. And actually it's an interesting case that we have to manually set the tenant around this request. I found the solution here in this, uh, thread that, uh, uh, tenant doesn't work well with this assert difference. So we need to manually set the, the tenant. I think we should say something like, uh, x as tenant, uh, dot current tenant equals organization here and here. Let's see if this test passes. Okay, we have just, uh, mm yeah, it's still not possible. Maybe I need to set it also before, okay, we have just one failure left. Let's, uh, fix that destroying organization with a certain difference. So one, two, setting the tenant. Okay? And voila, our application has integrated the as tenant with, uh, strict, uh, security. So access tenant has to be always set to access, uh, uh, resources that are scoped to an organization and, uh, all out as person. Everything works. So that's how you integrate, act as tenant with a strict security into an existing application. Thanks for watching and see you in the next one. --- # Moneygun - Multitenancy & Teams boilerplate | SupeRails #208 - URL: https://superails.com/posts/moneygun-teams-multitenancy-boilerplate - Published: 2024-10-30 - Duration: 15:50 - Access: free - Topics: moneygun, Mulitenancy, saas, saas boilerplate In Episode 115 we created a multi-tenant application without any gems. Multitenancy was row-based, and we set current tenant via routes. Episode 115: https://superails.com/posts/rails-7-115-multitenancy-teams-and-roles-without-a-gem Based on this I created a well-tested boilerplate multi-tenant application. The name is "Moneygun". Why? Because adding this architecture early in your app development will help you save & make a lot of money! Save money - on future refactoring. Make money - on making your apps business-friendly early. 0:00 Teams should be an MVP feature! 2:50 Invite users to organizations 5:20 Role-based access 6:52 Switch between organizations 8:00 Different sidebars 8:30 Inspect Gemfile 9:29 Super Scaffolding Moneygun source code: https://github.com/yshmarov/moneygun ## Chapters - 0:00 Teams should be an MVP feature! - 2:50 Invite users to organizations - 5:20 Role-based access - 6:52 Switch between organizations - 8:00 Different sidebars - 8:30 Inspect Gemfile - 9:29 Super Scaffolding ## Transcript Hello friends. In episode 1 1 15, I talked about building a basic multitenant application in Rails. You can think of Multitenancy as if you, as the application owner are a landlord. You own an apartment building and individuals, organizations who rent a space inside your bot building, like for the monthly fee are the tenants over the apartment. So that's more or less how software as a service works where you as the application owner, uh, have the application and the organizations rent a space in your application. Now, in this episode, I build this, uh, basic, uh, application where you could, uh, create organizations and invite the users to be members of this application. And I had a few comments. You can have a look at each of them on, uh, how to build this kind of basic model density application step by step. And the database architecture looked more like this. So a user and individual could be a member of multiple organizations, and in different organizations he could have a different role. And all the downstream resources like projects, tasks or scope to, to the organization. You can think of, uh, a good example of multitenancy when you look at, for example, slack. So here, there are multiple separate workspaces with different users who can have different roles in each of these workspaces. And, uh, you see all the resources are scoped to this workspace. So I go to another workspace, there are other admins. The billing is on a workspace or on the organization level. And, uh, you can also have a look for example, at Discord. Again, the idea is the same. There are multiple workspaces. Each workspace has, uh, uh, users with different roles and you can, uh, invite other users to join, um, an organization. So this is more, less, uh, how, uh, this kind of multitenant, uh, application should work. Now there's also this, uh, wonderful tweet by Matt Swanson, where he talks about, uh, this kind of multitenancy, uh, database architecture can save you, uh, like a hundred thousand dollars. I think it can not only save you a lot of money, but it can like inter factor in, but it can also earn you a lot of money because, uh, uh, B2B uh, organizations are more likely to use your application if they can have team access to resources in your organization. And, uh, talking of this bullet chain, uh, founder Andrew Culver also wrote a wonderful article that I highly recommend you to read. Teams should be an MVP feature of, uh, whatever business application you are building. Now, going back to this, uh, episode, I think it was, uh, quite a good, uh, starter of how Multitenancy should work. But, uh, you just can't cover all the cases, uh, and uh, make this perfect in just a few comments. There's a lot of work, uh, that actually needs to be done to make this, uh, uh, kind of logic bulletproof. So that's why I created Money Gun. You can think of, of it as a multi-tenancy boilerplate or template application where I have, uh, this sort of architecture really well implemented and well tested. So I wrote, uh, tests for all the possible outcomes in the, this kind of architecture so that, uh, it is bulletproof. It's not bullet train, but it is bulletproof. Now, let's have a quick look at how Money Gun works. So let me, uh, restart. I've, uh, got it here. I will start the rail server. Been Dev. Okay, I started the application. I'm going to create an account. So let's create an account. I'm just using the basic device here, and here I have created an account and I can create a workspace or an organization. Let me create an organization. Let's name it Ship Rails, okay? Create organization. So here inside the organization, I've got, uh, users or kind of members of this organization. And I've got one sculpture source that is inboxes. So for example, I want to have an inbox, uh, where I would let users, uh, uh, type which episodes that they want me to talk about, uh, in my future, super Rails, screencast, uh, which episodes do you want in the future? So I've created this inbox, and you see this inbox is scoped to this organization. Now, let's try inviting another user to this organization. I will invite somebody, I will invite him as a member, not as an admin. I'll send the invitation. So the invitation has been sent, the user has been added, uh, as a member, and let me accept this invitation as the other user. So in my locus, uh, I see that the invitation has been sent. I will, uh, copy the link to accept the invitation. I will open it in a new tab in the private tab. So let's, uh, accept the invitation. Okay? And here I'm logged in as this other user. And you see I am, uh, already a member of this organization. I go to the list of users. And you see, I cannot edit the roles of, uh, myself or the other user because I'm just a member. The only thing I can do is leave this, uh, organization. Let's go to inboxes. You see, I don't have access to inboxes. 'cause by default only the organization admin can, uh, access inboxes. Let's try changing this. I will go to inbox policy. I'm using punit to handle, uh, authorization, uh, role-based authorization to different sources. So at the moment I say that only admins can, uh, uh, only members who have the admin role can, uh, edit the organization or an inbox and view everything. Let's say that, uh, any, uh, member can, uh, if you and edit inboxes, again, I'm going to inboxes now, and you see, I as a normal member can, uh, have access to inboxes. I can, uh, edit this inbox. Okay, looks, uh, fine. Now, let's, uh, try, uh, going back to the admin and let's try, let's say editing my role. I will try to go from being an admin to being a member. And you see, I cannot, uh, turn myself into a member because, uh, an organization has to have at least, uh, one admin because otherwise, uh, you won't be able to like, change anybody's roles to lead the organization and so on. So the organization will not be functional if there is no admin. So this is like one small thing that you should think of. An organization should have at least one member, uh, admin. Let's make the other member an admin. And now I think we can change our role to member. Okay, now let's try creating a new organization. So, uh, I'll go to the list of organizations. I'll create an organization named Ong. Here it is. So, uh, you see, I have two organizations here. I'm the admin here, I am just, uh, uh, a member. All I can do is leave this organization. And you see the inboxes are scoped to the organization. So here, organization one has, uh, one inbox, organization two doesn't have any inboxes, and you as a member of both organizations can have, uh, inboxes from both organizations open in two separate tab. So that's the beautiful thing about, uh, uh, root based setting of current, uh, uh, organization that, uh, you can have, uh, two different organizations open into different tabs. So, um, looks fine actually about this solution of organizations. Uh, I really like this kind of sidebar fiction. Uh, obviously it was inspired by Discord and uh, by, uh, slack. And, uh, this, uh, template, uh, application also includes another kind of sidebar that, uh, would not have this kind of double side, but, but just one sidebar. So I have the organization Manning on Open. I see the dashboard uses for it. I can open the dropdown and switch the organization and see the data for that other organization. Okay, let's dive into the code itself. So, uh, usually when I look at a new Ribbon Rose application to me, first of all, I look at the GEM file. Now everything here is just whatever I have. When I create a new Rails, uh, uh, seven or Rails eight application, I just have installed Tailwind. And, uh, here are the unique gems to this application. So I've got device, device, inviable and bonded. Now, uh, I think Rails eight authentication generator isn't ready for production yet. And device plus device Inviable works perfectly for, uh, authenticating users and send invitations to create a new user in the application. Again, I responded for authorizing, uh, uh, organization members, organization members, ems, uh, and organization admins to different resources within the organization. Then for the front end, I've got few component in line SVG to display these SVGs and, uh, active link tool. Active link tool. Basically here I have inboxes. I go deep inside inboxes, and this inboxes, uh, link in the sidebar is still highlighted. And I've also got this gem nested scaffold. Now, this is a really nice one. Let's say I want to create a new nested resource within an organization. Let's say I want to have projects within an organization instead of just doing a scaffold and then having to update all the scaffold views and controllers to be nested within the organization resource. I'm just going to run a command for creating a nested scaffold. So, um, let's try doing it. I have this example, command rail generate nested scaffold within organization. I'm going to have a project, and the project is going to have a name that is a stream. So, uh, let's run this generator rails. Uh, the B migrate, let's have a look at the DB migration. So you see it was automatically scoped to the organization. And, uh, we'll go to our roots. You see the, uh, nested, the scaffold generator had a few like made to assumptions that we want to either namespace or put it within resources organization, but we already have resources organization here lower. So I'm just going to put, uh, resources, projects, inside resources, organization. If we have a look at, uh, our projects, you see they're already scoped to the organization, uh, everywhere. So we don't need to update the controller. Just one minor thing here. We look for the project within all organizations. We want to see, see, we want to look up the project within the organizations where the user is a member. So I'm actually going to remove this set organization completely. And instead of it, I'm going to inherit not from application controller, but from this base organization's controller, where I have a method to set the organization to find the organization from the organizations where the current user is a member. So produce controller and hears from organization base controller. Let's try running the tests. Okay, let's run the migrations rails db migrate rails test. Okay, so tests are failing. This one is failing because, uh, organization has many projects. And then deleting an organization, we should also delete the projects within it. So I will say depend, dependent, destroy. Okay, that test works. Let's open the, uh, projects controller test. So for it to pass, again, you see by default from the scaffold generator from Nest Scaffold Generator, they already have everything set up, even in our test to have a projects within organizations. I'm just going to sign in, uh, a user and I think this test should pass. Okay? We just have one test, not passing inbox. Policy test goes in our inbox. Uh, policy. We said that, uh, currently anybody can access inboxes, but we want only admins to access inboxes for this policy. Let's run the test once again. Okay, let's run our server and try to navigate to, uh, projects within an organization. So, uh, I go to an organization, I want to have the projects in the sidebar. Let's add them to the sidebar. So in this, uh, navigation links partial, I'm going to also add the a link to organization projects. And here we have the projects that are scoped to the current organization. Let's create a new project. Uh, okay, so here we have a project for Super Rails. I will go to another organization. I go to projects, and you see it doesn't have any projects. So projects are scoped within an organization. Looks good. So let's inspect the code a bit more. Uh, so in schema, again, there are quite a lot of migrations for active storage to be able to add an, uh, avatar to an, uh, organization. Let's have a look at an organization. Yeah, we can add an avatar. We have, uh, again, the most important part. So we've got, uh, organizations, we've got users, and the user can be a member of an organization via a membership. And the resources, like projects are scoped to an organization. And Inbox has also scoped to an, uh, organization in the Our Roots. Again, uh, everything that should be scoped to an organization, they also scoped in the roots. This way we don't lose the logical hierarchy and, uh, this kind of, uh, multitenancy that in the Roots works well. Anyway, I'm, uh, really happy about this kind of, uh, boiler plate, multitenancy boiler plate or static kit, whatever. Uh, it has, uh, I think quite a good implementation of, uh, this kind of, uh, um, organizations, memberships and users logic. I have written a lot of tasks to ensure that, uh, it works flawlessly and there are no leaks between data from different, uh, organizations and users can do strange stuff like change, uh, another user's roles. I invite you to have a look, uh, at the, the code here. Maybe you can start your own application based on the, this static kit. Uh, maybe you can, uh, integrate, uh, the, uh, code for, uh, organizations and memberships into your application. Uh, in the future. I'm planning to add a couple of more features, like, uh, being able to accept or decline a membership request to an organization because you can be invited to multiple organizations and not always you want to accept the membership request. And possibly I will add the billing on, on, on an organization level. And if I add the billing, then it's going to be kind of a real, sort as a service, uh, uh, start kit application. Anyway, I invite you to have, uh, a look at Money Gun. Tell me what you think about it. --- # Exploring the SupeRails source code | SupeRails #207 - URL: https://superails.com/posts/exploring-the-superails-source-code-superails-207 - Published: 2024-10-29 - Duration: 25:16 - Access: free - Topics: code review In this episode we dive into the SupeRails codebase! See the database architecture, gems, API dependencies, and admin backend powering the SupeRails web app. Subscribe to SupeRails to **ACCESS** the source code! Join SupeRails: https://superails.com/pricing SupeRails source code: https://github.com/yshmarov/superails 0:00 Feature overview 0:00 Explore the Gemfile 10:45 Admin dashboard 12:40 Run app locally, import YouTube videos 15:50 Stripe Subscription flow 19:25 Explore database schema ## Chapters - 0:00 Feature overview - 0:00 Explore the Gemfile - 10:45 Admin dashboard - 12:40 Run app locally, import YouTube videos - 15:50 Stripe Subscription flow - 19:25 Explore database schema ## Transcript Hello, friends, I have an exciting news to share with you after around four years of doing, uh, YouTube videos. I'm launch@superrails.com, a place where you can watch all the videos from YouTube plus exclusive member only, uh, content. And I'm trying to make it as good as possible. Now, previously I tried making actually a code, a long series about how I am building ship rails, but after 19 episodes, I understood that I can't really make it because it, it was consuming too much of my time, and I would need, uh, I think like 200 or more episodes to really make it good. You can't really, uh, think of, uh, concepts and, uh, spend a lot of time, uh, just, yeah, just thinking value are life coding. You should be delivering something, right? So, uh, yes, I have, uh, built super ales and here's the source code for super ales. And if you are a subscriber to Super Rails, then you will have, um, access to the code base that you can explore on your own. So basically, how does cprs work there through core elements, the most important element is the videos. So there are three videos and pro videos on the videos, uh, page. You can filter by raw or free, and by many different other parameters. Free videos are accessible to anybody. So let me open a free video and, uh, you see I can watch it and if I open a row, video Unity to be subscribed to be able to watch it. Now you see this user, he is, uh, subscribed. So if you can see this, uh, pro video, but if I log out or if I log in as an unsubscribed user, I will not be able to view this video. So I see this pay and I am prompted to subscribe. Then I've got playlists. It is series of videos that are best to watch in the specific order, and there are also tags, just videos that, uh, share something in common. Now let's have a look at the source code for RAs. So I've actually got, uh, the application cloned locally. Let's have a look at it. So, um, whenever I start looking at a new application, I usually start from the readme and, uh, after the readme, I go to the jump file. So what do you have in the jump file? Now? Everything is more standard. So Rails latest, uh, stable version, prop shaft, postgre, QL. Um, I've got request js that is not present in the default rails installation that I am, uh, using to make, uh, post and, uh, get requests with, uh, JavaScript. So actually this, uh, search is done with request js. So I am, uh, type in here and, uh, I make a request from JavaScript and return at Turbo Stream as a response. And you see I have playlists, Tags and videos in the response. Then, uh, I'm obviously using Tailwind. I'm using good job for processing jobs. I'm not using Sidekick because I don't want to have read this as a dependency and I'm not using solid queue because, uh, salt queue just didn't exist when I created this application. Nowadays, maybe I would use solid queue by default, but good job is doing the job and uh, it's good. Yeah, I'm just repeating myself. Then I'm using your B link and robocop to clean up my H tm L and RUBIC code. These are, I think, very important gems for you to have. And I'm using Faker. Now, in theory, I could, uh, just get rid of faker because I'm using, uh, uh, fixtures for my test. But, uh, I do need faker to mock my, uh, only out responses. Uh, I could in theory try abstracting, uh, also and get rid of faker. Then, uh, I'm using the GI in tasks for translations. You see in the application I have, uh, two languages, English and, uh, Spanish. So all the text are available in English and Spanish. Then, uh, this is just for system tests. Uh, then in the application have authentication only via auth. So you see you can log in with GitHub, with Google on Apple. Now, initially I was thinking of doing device and just log in with the email and password, but then I didn't want to connect an email service and thinking about email deliverability, email confirmations or send password. So I thought to go just with the, uh, social login flows. And also in theory, if you are building a content, uh, a, a private content sharing system like Netflix, you would want to fight, uh, against, uh, password sharing maybe. So with uh, social logins, it is hard do use to share passwords, and I'm using Stripe to process payments and subscriptions. Okta kit, because whenever a user subscribes and his subscription is active, he automatically gets access to the super rail code base you see as a private repository. But ascribed user, you have access. Then honeyed for error monitoring. Uh, Google, API client. Uh, I'm using this one for, uh, the YouTube, API. So I get the video data from YouTube. We'll see more about it later. And raw versus valve for performance monitoring. Let's have a look at the, the raw versus valve dashboard. So here is the dashboard where I see the first performing, uh, pages and, uh, maybe queries in my application and, uh, I can improve them. I can also see errors. Let's take last 24 hours. So there were a few errors, but by now I have already fixed them. Then, uh, I'm using AWS, uh, uh, S3 for storage. And the only thing I'm storing in the application is the video thumbnails like this. So the videos themselves are not stored in the application. They are actually embedded as, uh, an external either YouTube player or Vimeo player. Then, uh, active storage validations to ensure that, uh, the images that, uh, are uploaded are actually images and that they don't have too big of a file size acts as list to be able to order videos in, uh, a playlist. You see this one is first, second, and third, but I can change the order if I like, um, acts as voidable because users can add a video to a bookmark or mark the video as completed. Lemme try doing this. So, uh, I will mark like these videos, uh, as bookmarked, this one as completed. And if I go to the list of lessons, I can filter by bookmarked or completed lessons, uh, friendly ID because, uh, if I am trying to share, uh, records, uh, URLs in the socials, it looks better if, uh, the URL is more descriptive than just given an ID to a record, uh, page for infinite pagination. Here, you see I've got a list of posts, and whenever I scroll down, there's this infinite pagination going on. Uh, punit I needed, uh, in this application, I think only for, uh, VO uh, policy. Yeah, so I'm using pundit only for vo uh, ranac because it provides a wonderful, uh, uh, API for searching inside active record, and I'm using ranac for this, uh, search sidebar, uh, noticed for the notifications, actually, I tried going without noticed and building my own, uh, notification, uh, system. But in the end, I ended up with an API that was, uh, similar to noticed, and, uh, it didn't make sense to build my own thin virus. I could use this gem. So it is a good gem and recommend to use it. Then view components. I actually have quite a lot of components, uh, in the application and, uh, I scope the logic within the components. So there's, for example, this, uh, uh, where is it? Cloud component, oh yeah, base item component that, uh, a few other components inherit from and reuse, uh, different elements from this component. Then, uh, uh, meta tags so that, uh, the images I share on socials have nice previews. For example, uh, I'm sharing, uh, a link to this video. And you see this is a, a meta image. This is a meta description, so it looks quite good. Then shared on socials. Then, uh, inline, SVG. So all the S VGs in the application are rendered, uh, using the gem inline SVG. If you search for SVG in the code base, you see all the SVGs are just s VG files themselves. I don't trend the SVGs in line in the H TM L at all, and I think it's very good because, uh, if you just see something like this in your H TM L, you cannot process it with your head. Uh, but uh, I use in line SVG tag and uh, I will usually know, uh, I will see the name and I will know what SVG I'm actually using. Then red carpet and rules for markdown and synex highlighting inside markdown. Uh, I use these, uh, if I go to lessons, let's have a look at the comments. So here, for example, uh, I have a code block and some inline code elements, and, uh, they get, uh, uh, highlighted and they get displayed as markdown. Then side map generator, because if you go to slash uh, side map xml, you see it'll prompt you to download the site map. And I submit the site map to search engines, rails, I and n for translations, active link to actually, previously I made a video highlight link to current page where we implemented our own solution for highlight and link to current page. But, uh, the time I, uh, grew to learn that, uh, it is just better to use this gem that already has all the possible cases covered. And finally, I'm using VO and VO Pro for the backend. So, uh, let's actually try logging into the backend of the application. I can go to any of the lessons and as an admin, I can open here. I have, uh, I'm looking at the specific post from which I opened it. You see, I have, uh, the, uh, check mark two mug, the Episode Pro or free. I have a title, the body of the episode. I can, uh, edit it, like add a new cover image. I, uh, can add tags or add this, uh, episode to a playlist. And here I have a list of all my episodes in avo. I can also view them as a grid, and this looks actually much better, but displays less information. That's, uh, the trade off between a table view and a grid view. Also, I have some filters to see only the published videos or only the unpublished videos, and I have some actions, we'll come back to them later. Then I've got the playlists, I've got tags. So I do kind of all the content management from, uh, and again, a list of all the dimensions and a list of all the comments in the application. I also have users and plans. I'm not going to show them for the video and for privacy. And I also have the jobs from good job. So, uh, yeah, it's the good job dashboard. And I also have ome, let's see where I use ome. So I use it to generate, uh, open graph images. So, uh, basically I use firm to navigate to these URLs, take screenshots and store the screenshots in the application. And whenever I share a link on socials to, for example, the, uh, pricing page or to the text page, then this is going to be taken as the open graph preview image. Okay, so we had a look at the gem file. Uh, let's actually try running the application. Uh, I will say bundle then, uh, rails, the bid drop, the B created, the BI migrate, and, uh, let's run bin dev. So we are starting the server and going to run super rails on local host. So by running Bin Dev, let's have a look at the profile. We run the rail server, we run the, the changes in vin. We start the, uh, good job, uh, worker. And we also listen to the stripe web hooks on local host. So here I have the application. You see, I don't have any lessons. Let's try logging in. I already have my, uh, uh, secret credentials set up so I can look in and, uh, I'm automatically redirected to the pricing page. I don't have any lessons, nothing. Now let's make this user and admin. So I will, uh, say Rails console user dot account, one user first, do update admin. True. Let's click and start the real server once again. Now I refresh the page. And here you see, the user now has admin access. Let's actually change the language back to English. So let's go to admin. Okay, I'm in the admin console. I have just one user and, uh, none of anything else. And in the post I have this action to input videos from YouTube. Now, how does it work? Let's have a look at vo. So VO posts. So we have this post resource, and here we have actions. Inside actions. We have this YouTube list videos job. Let's open this job and see it runs. Uh, so this job from, uh, avo, this, uh, action runs the YouTube list videos job, and the YouTube list videos. Job Makes a request to my, uh, API key for YouTube. And, uh, it, uh, tries to get all the videos from a channel. Actually, I started to get just five, uh, videos on the development. So it's going to make an API call to YouTube, get my videos, and, uh, store them in my application using this create video job. So here I am, uh, creating posts based on, uh, the API response from YouTube. Let's try running this, uh, job. Going back to the console. Console. See, a lot of good jobs are running. Let's refresh. And here I have imported a few videos. Okay, here they are. Looks, uh, quite good. I can, uh, open one of the videos and let's mark it, uh, as a pro video. So let's navigate back to the application. And here you see, uh, you need to be subscribed to view this video, but you can view all the other videos without a subscription. So let's click on this button and actually subscribe. I'm going to, uh, initiate the subscription. So you see it is opening the Stripe checkout. In this case, it is an embedded Stripe checkout. Let's input our card information name. You see, I also have enabled, uh, this check mark tool and purchase as a business so that the companies can, uh, uh, return VAT and integrate terms of service and privacy. Uh, in theory, you can, uh, also upgrade to have two months free with an annual subscription. Let's click to subscribe and, uh, let's see what's happening in the background. So here's the subscription going on a lot of web hooks and so on. Uh, let's have a look at the, the Stripe F Hooks controller. Oh, you see, I was, yeah, I have subscribed. So you see I have the subscription. You should, you see subscription active Next billing date, uh, current plan features, and I have the button to manage billing. Okay, let's have a look at the, uh, F Hooks controller. So here is the great action. I'm doing the authentication. Then, uh, I handle the subscriptions. So when the subscription has been created, I, um, update the, uh, plan of the user. And if the user subscription is active, then I, uh, invite him to access the GitHub private repositories like superl.com and, uh, also, and user within an active subscription has access to the pro videos. So let's now try having a look at one of the pro videos. Going back, let's open the Pro video. And you see I can view this video. Now, going back to my profile, I can go to manage billing and it opens the Stripe billing portal where you can, uh, either cancel a subscription or upgrade the subscription and see the billing history. So you see, I'm trying to outsource as much payment logic, uh, from my application to Stripe as possible. But there are a few things that I still keep in my application. For example, uh, that logic of being subscribed is the user subscribed or not. So a user has one plan, and when a user is, uh, created, we create the plan for the user. So let's have a look at the plan. Basically, the plan is the place where the store all the information about the current subscription. You see, we have user a plan update, and we update the user's plan whenever he has, uh, changed his subscription status in Stripe. And, uh, here the main thing is to check that the subscription is active. It is active if the end date of the subscription is in the future, and if the subscription status of column in Stripe is also active. So going back to the application here, the user is active and he can manage his billing looks good. Now, I was thinking of maybe using a gem like pay to, uh, manage subscriptions, but, uh, actually doing subscriptions, uh, with Stripe without any additional gems except just the Stripe. API Gem was, uh, enough. Okay, so we had a look at subscriptions. Now, usually after having look at the GEM file, I have a look at the schema. So let's have a look at what models there are in the application. So we've got a lot for active storage attachments. It's all for attention images to post. Then we've got comments. Uh, a post can have multiple comments. Initially I was thinking of doing comments polymorphic because a comment can belong to a post and to a comment within a comment. Uh, let's have a look at some comments. So let's go to lessons. Most commented, you see there's this three of, uh, comments and, uh, each of these comments belongs to the post. So we can know how many comments the post has in total, but each of them can in theory also belong to another comment. And you see this quite easy to build this kind of comment, trees, and you can reply to any and create new trees. I will open one of them and you see, I can, uh, each comment has its own page and I can go one level up, one level up and, uh, or just view the post all the comments. So I'm quite happy with the way the comment structure is built. Then we've got the good job tables, quite a few of them then mentions. So you can mention other users, like at the, um, somebody and the user should get a notification that he has been mentioned. Uh, noticed advanced notifications. So it is again for, uh, this feature to have an inbox with, uh, mentions or information about new videos being published. Uh, then we've got, uh, plans. We already talked about plans. A user has one plan, and then the main logic of the application. So, uh, a lesson can be in a playlist, so in multiple playlists and a lesson can have multiple texts. So, uh, post has many playlists through playlist posts. Post has many tags through post tags. This is the core of the application itself, what the application is delivering. Then we've got the user's table. And again, I, uh, even don't need to have an email, uh, for the user because all the users send in with, uh, uh, their social accounts. So email can be null in my case. And, uh, votes for X as voidable that I'm using for bookmarks and marking lessons as complete. Okay. So we had to look at the schema and usually have to look in the gem file and the schema. I also have a look at the roots of the application to have a good understanding of what is generally going on. So this is all generated by a new rails, uh, eight, uh, applications, uh, generation. Then site map to submit to the, uh, search engines feed to submit to RSS readers. Uh, if few static pages about application, then the authentication, you see, it is all built from ground up without device and any other external libraries, just the omni au uh, libraries. Then only a user that has an admin role can access AVO to manage the content. And a good job to see the job processing. Uh, I've got, uh, testimonials. Again, only a signed user can access the checkout s billing portal. Obviously notifications read all. So, um, here I have this, uh, kind of clear button or read all to hide all notifications. I click it and you see the notifications are empty. Now, uh, the pricing page, uh, the Stripe webhook, that has to be, uh, accessible without setting current user. Uh, search for this global search that is accessible on any page. Uh, post, have many comments. A post can be marked complete or voice listed. Uh, the landing page. So static index, uh, playlists only index and show, uh, on the front end. They cannot be managed. It, it's all managed like creation, uh, edit and delete is managed through. And the, the tree of comments. So you see the roots are also quite simplistic. Now, I've actually written quite a lot of tests in the application. I'm using the mini test, and, uh, I'm trying to leverage, uh, uh, fixtures as much as possible. Initially, I was more used to using APEC and uh, factories, but, uh, I, uh, came to like fixtures and, uh, they said that re using fixtures is faster than re using factories. Okay. So yes, this is basic overview of, uh, the application. So we managed to, uh, have a look at the basics of the code base. We managed to launch the application locally, set ourselves as an admin and access the AVO content management, uh, part of the application where we can, uh, create new posts. Uh, we can input them from YouTube if you have the API keys, uh, and, um, add the post to a playlist or add text to post. And, uh, yeah, I think, uh, that's about it. So, uh, I invite you to subscribe to SIP Rails. It'll really help me, uh, keep, uh, creating, uh, good videos, uh, uh, for you to watch and learn from. And, uh, yes, uh, if you subscribe, you also get access to the Superl code base. So thanks for watching and see you in the next one. --- # Sentiment Analysis: No AI, just math | SupeRails #206 - URL: https://superails.com/posts/sentiment-analysis-superails-206 - Published: 2024-10-21 - Duration: 08:22 - Access: free - Topics: AI Sentiment analysis is a way to analyse a text and quantify its positivity negativity based on keywords from a "word positivity database". In this episode we will extract all my Udemy reviews and perform a sentiment analysis on all of them. How many are actually positive? Maybe these days "AI magic" could provide a better score, but THIS here is a mathematical approach. Episode source code: https://github.com/corsego/206-sentiment-analysis/commit/f1e8e33a34d8daf7739d31801ddb096685e0b98d Gem "sentimental": https://github.com/7compass/sentimental Did you enjoy the episode? Leave a testimonial here: https://superails.com/testimonials/ ## Transcript What is sentiment analysis? It is basically a way to rate a block of text as negative, neutral, or positive, and also maybe give a number, uh, rating like minus two or plus five. Now, how does sentiment analysis work? Basically, you can have, uh, a lot of words, and some of them are positive and negative. Let's sort them into positive and negative. And now you can, uh, understand that some words, uh, have a stronger meaning than others, like, uh, against is not as, uh, negative as fraud. And based on this, you can have a database of roads and, uh, of, uh, how strong the world is. So the negative emojis in this example, rated as minus one, the positive emoji is plus one. Uh, a word as, uh, adorable is plus three, a word as abusive is minus three. So this is a basic example of a database that can be used for sentiment analysis. Here's another example, a list of emojis. And, uh, here's a text, uh, description of each of the emojis. And all these emojis can be rated based on the text description based on, uh, what is found here. Now, in the world of ruby, there is a gem named sentimental that you can use to, uh, say the, uh, string is positive, negative, or neutral. And you can also give it an average score based on, uh, this kind of values. Now the gem comes with its own, uh, data example. So we have, um, the English words, Jason, and he has a library of words and how strongly positive or strongly negative they are. And, uh, in total there are more than, uh, 18,000 folds in this library. So let's see how the gem works here. I have already installed the gem in my application, gem sentimental, and, uh, I'm going to try to run it in the rails console. So, uh, I will say rails console. We've created the analyzer. Now this empty, I'm going to load the default, uh, roads library. So I will say analyzer, load defaults. Now, if we type analyzer, we have, uh, the defaults, we can say, do world scores dot count. There are 18,000, uh, words that they're going to, uh, use for our analysis. And, uh, let's try analyzing something like analyzer sentiment. I love ruby. Uh, I, uh, uh, hate Java, for example. This one is strongly positive. This one is strongly negative. We can also analyze, uh, do score analyzer. Score. I love U oh 0.9. I, uh, hate, uh, him, uh, minus oh point 43. This is based on the world's love and hate that were found in the sentiment, uh, dictionary. Okay, now let's do something more practical. So here, uh, is, uh, the review page, uh, that I'm looking at as an instructor in Udemy. Previously, I used to create lots of ude courses, uh, before I started a YouTube channel. And, uh, here I have, uh, all my reviews. I exported them to CSV, I received, uh, the CSV as, uh, an email. I loaded the CSV and put it into my res application in them slash db slash data. So here I have over 500 reviews that I've have imported into my application. And, uh, each review has a rating that can be like four or 4.5 or five, and many of them have comments. And based on the comments, I can, uh, perform a sentiment analysis on all my comments and see how many comments are positive, how many are negative, and how many are neutral. So let's try doing this. I will, um, actually not code it all from the beginning. I'm going to run the generate, uh, run some command that I have previously prepared to make things faster. So first of all, I will, uh, as a warm up, try to get all the ratings from, uh, all the, uh, reviews and get an average rating, uh, without doing the sentiment analysis. So I'm going to, uh, pause the CSV and get all of the rating column. I'm going to save it as, uh, uh, float. And, uh, I'm going to have an array of all the ratings from the CSV. So let's run this Ratings. We have, uh, 524 ratings, and we can, uh, uh, get the average rating by, uh, summing them up and dividing them by that quantity. So in average is 4.5 out of five. This is a very good rating, I think. Now, let's, uh, get all the comments. So in a similar way, I'm just going to get all the common trolls. Okay, we have comments dot count 115, and now we can do the sentiment analysis on all the hundred and 10 comments. So I will start with the, an, uh, empty hash. We will have, uh, zero positive, negative, and neutral. And I'm going to run the sentiment analysis on each of the comments. And, uh, based on what the analysis gives, positive, negative, and neutral, I'm going to increase the counter. So I run this analyzer, and you see it runs, uh, in, uh, basically instantly because it doesn't, uh, put any external APIs. Like if you were running it with the open ai, if you were sending each of these comments to open ai, for example, and getting the sentiment, it would take a bit longer and it would cost you money. So let's see, what is the average sentiment? Okay, you have eight, seven positive, 16 negative, and 10 neutral. Sounds quite good. Now let's get the average, uh, score. So, uh, to get the average score, I will just run, not analyze that sentiment, but analyze a score. And, uh, let's see. The total score, it is 120, but it doesn't mean anything because we want to divide it by the amount of comments. So let's get the average score. It is positive 1.06. So I think it, uh, is, uh, quite good. And this is basically how you can use sentiment analysis. Now, uh, I have to say that this, uh, data is not, uh, perfect. There are some issues here. Uh, for example, uh, uh, it says that, uh, not bad is negative, and actually it is, uh, positive. So it could be better. Uh, you can find different, uh, databases of, uh, sentiments online. You can, uh, use them. You can add, uh, files for different, uh, languages you would like need, if you are doing some analysis on French text, you would need to have a, a library of French text. And, uh, again, it is, uh, uh, good approach that you can, uh, use if you don't want to use, uh, AI for this. So thanks for, well, AI as, uh, like or something like that. Anyway, thanks for watching and see you in the next one. --- # Authentication-Zero: What Rails 8 Authentication Wants to Be When It Grows Up | SupeRails #205 - URL: https://superails.com/posts/authentication-zero-what-rails-8-authentication-wants-to-be-when-it-grows-up-superails-204 - Published: 2024-10-20 - Duration: 16:25 - Access: free - Topics: authentication Some people don't like Devise because: - it feels too much like magic - it can be hard to extend Rails 8 authentication aims to solve these objections by generating all the authentication code INSIDE your app. No more outsourcing of authentication logic! BUT Rails 8 authentication is still very raw. It lacks registrations, session management, test generators & more. I don't think it's production-ready (yet). Authentication-Zero already has everything Rails 8 authentication generator could ever want to include! In this episode we will run the Authentication-Zero generators and see how a few of its plugins work. Even if you are not planning to use the gem now, it is a wonderful learning source for building different authentication-related features. Authentication-Zero source code: https://github.com/lazaronixon/authentication-zero Based on this blogpost: https://blog.corsego.com/authentication-zero Follow Lazario Nixon: https://x.com/lazaronixon ## Transcript Device, the most popular Ruby Rails authentication gem. If you are a URS developer, most likely you have used it. And actually in one of the annual Ribbon Rails servers, it was rated both the most liked and the most hated Ruby Gem. Now, there are lots of threads about like, uh, uh, is device cool? Should you use device or not? Should you, can you use device correctly with rail seven? And I think all the criticism for device, uh, mostly boils down to a couple of things. First of all, it feels too much like magic. So you just run the gem, run the, uh, installation, and you have authentication working in your application without actually adding a lot of code into your app. So you don't have to dive deep into how the authentication, uh, uh, registrations, uh, work. It just works so too much magic. And the second is that it can be a bit hard to extend device out of the default, uh, flows. So for example, if you want to add to factor authentication, then you need to override the default device, uh, controllers quite the, a lot and it feels a bit hacky. Now, there are different other alternative approaches to authentication. There are lots of rubber jams. There are external services like All Zero. Actually, in one of my applications that I inherited, the, they were using the old zero for authentication instead of a ruby gem. And it was a big pain to maintain it and to just make it work good enough rails. I don't recommend using external, uh, authentication if you have so many wonderful Ruby Gems now Rails eight ships with its own authentication generator that I have covered in a couple of previous videos. But unfortunately, uh, the authentication generator is not, uh, good enough for production yet, in my opinion. So, uh, there is still a lot that needs to be added for this moment. Uh, you can just, uh, sign in as an, uh, existing, uh, user by default. You don't even have registrations and, well, that's what my second video was about, adding registrations to the authentication generator. You would also want to add confirmations. You would want to add, uh, uh, the possibility to change, uh, uh, the email. For example. Uh, you would want to add the default tests for the authentication. And it's still all like in from the default rails eight authentication generator. So it is all been developed and maybe someday it'll be a very good solution, but until, uh, yeah, and it, it's supposed to cover a lot of things that the device, uh, doesn't cover. So with this, you generate all the authentication logic into your application. So you see all the files, you can change the authentication inside your application. You get all the authentications related to files, uh, so it will not feel like magic and you can override anything you want. But again, it is, uh, still, uh, some way to go To have, uh, the whole authentication logic inside this, uh, authentication generator. And for now, I think the best alternative to this authentication generator might be authentication zero by Lazaro Nickon. So the cool thing about this gem is that, uh, it is basically everything. This rails authentication generator wants to be in a mature state. So with it, you would be able to generate, uh, all the authentication, the route views, controllers, models, and the, again, it is not like measured 'cause you see all the files, it, the connections between them inside your application, you can edit them. And the the cool thing is that it has, the gem has a lot of different options, uh, out the box. So you can add two factor authentication that just adding dash dash two factor. And you don't have to find a hack bay to integrate it into like device. You can add the, uh, even basic multitenancy basic functionality to log in as another user. I think that's, uh, really cool, uh, suitable. So like you have to enter a password to view app protected page. So, uh, let's have a look how, uh, we can use authentication zero in a new Ribbon Rails application. Now here I have, uh, a new, uh, ribbon Rails application and I'm going to try to install this Gem authentication zero. So, uh, run into generator. Now let's, uh, run Rails generate authentication. And you see it generates a lot of files in, uh, my, uh, Ruby app. Let's a look at them in the Git. I think it's a comfortable way to look at what has been added to our app. So, uh, first of all, in the application control, we have this method to set the current, uh, user current session. Um, I like starting to look at it from the migrations. So let's look at the migrations. A user has an email and a password. And by default, uh, user email is not verified and a user has many sessions. So, uh, a user can be logged in, in the same, uh, uh, device from two different browsers. And this would be two different sessions and you'd be able to log out of one of the sessions, uh, uh, from another one. So you can like remotely log out from another device. This is really cool. And Rails eight authentication generator also has this feature. Now let's have a look at the roots. So we get the roots for logging in, for signing up, for changing the password, uh, for viewing, uh, where you are logged in and logging out remotely, uh, for resetting the password, for verifying your email. And you also get a basic homepage. Let's have a look. So we have a homepage where we can see the, uh, basic, uh, code for like signing in, signing out, and seeing what user you assigned in with. Uh, then again, user we use has secure password. You see how the tokens are generated for email verification for password resets. Again, the user has many sessions validations. So, uh, there's everything you wanted. You don't magically extract, uh, or outsource your authentication logic to device. You have everything in your app. And again, it is now your burden to uh, make sure that this logic, uh, is not broken by your further actions. Okay, let's, uh, run Rails, tob my and run the application. Okay, let's open local host. And you see on the new application, it gives me the send in page. 'cause uh, I have to be sent in to use the app. Let me sign up@bar.com. Okay, at list 12 characters sign up. You see I'm signed in. Uh, I can uh, go to the console and you see whenever I will signed, uh, uh, I created an account, I get an, uh, email to verify my account. So by default, my account is not verified. You can see it in the, uh, migration or in the schema. I have this user verified falls by default. Let's open it in the console, rails console user dot account. Do first you see verified false. Now, uh, by default, uh, users that did not confirm the email can access the application, but it is, uh, for you the developer to potentially limit the un verified email access to your application. You could do it as, uh, in device, either, uh, straight away, not allow unverified users to access the system or after some period of time after the user account has been created. Okay? So let's, uh, try to open the link below and, uh, verify our user. I will open in you tab. Okay, thank you for verifying your email address. I will go to the console user first. You see verified True. Okay, I'm sent in as a user. I can, uh, change my password. I can, uh, change my email and here has the school tab devices and sessions. I'm quite sure that the rails authentication generator down the road is also going to add something like, uh, this, uh, page. So, uh, I can click log out and log out from the device remote. Let me try it for the new B browser tab, local host. Okay, signed in and now I'm going to try to send out of this one remotely. You see, now I refresh. I'm sent in from two different, uh, uh, browsers. I will click log out here. Now I refresh here and you see I'm, uh, logged out. So it is wonderful. You see, I managed to remotely log out. Okay. Uh, so this is the like basic, basic authentication logic, and you can extend it, uh, with the different plugins. So there are these plugins for like, uh, uh, enabling two factor authentication, uh, tracking activities and yeah. And then we'll also maybe try multi-tenancy. So, uh, let's save our changes. Uh, add authentication zero. I will also add a link to the G. Okay? Now the cool thing and good way to learn, uh, how the generator works with and without all these plugins is trying to add them one by one and, uh, see the difference in the code generator. For example, I'm going to rerun this generator with the two factor. Let me run rail generate authentication. Two factor, okay? Uh, I get a conflict, I will say force. And uh, you see, uh, here is the diff between the initial generation and generation. If I run it together with two factor. So you see the sessions creation controller is somewhat different. We get all these, uh, controllers for uh, two factor. Uh, then a user has, uh, many recover codes and uh, the views have also been updated and the migrations are also somewhat different. And you see we have the roots for two-factor authentication. You see quite a lot of roots because, well, there are quite a lot of views involved with two factor authentication. Let's run rails to be drop, to be create, to be migrate. Okay, let's run the rail server and see how could this, uh, two factor authentication work. So lemme sign up. Okay? Okay. Now I have this, uh, tab two factor authentication. You see, I click on it and I get this whole guide to add to, to authentication. I can scan this code with my camera and, uh, well, the thing will work. So you see outta the box, you have two factor authentication. Now, again, uh, running the correct generation, uh, of all the, uh, plugins is best to do. I think when you're just creating a new application, uh, this way you will have, uh, all the lo logic that you need for your application out of the box. Uh, let's try something else. So, uh, I'm not going to save these, uh, changes. I'm going to try to add, for example, multi-tenancy. I will, again run rails. The B drop, the B create, B create, I will run rail, generate authentication, and I will say tenable and force. Okay, let's, uh, run the migrations and let's see the difference. So see, we have an account. So users belong to an account. Uh, we have a concern. We have, uh, current account and a user belongs to account. So, uh, you see with this, uh, authentication zero approach to multi-tenancy, uh, one user with one email by default belongs to one, uh, account or organization or team. And if you wanted to implement, uh, the possibility for one user to belong to multiple accounts, you would need to still do it, uh, manually. But this, uh, very, very good, uh, start now, uh, in many cases for multi-tenancy, really it is enough for just users with one email to belong to one organization. I've worked in, uh, some organizations and it was just fine. But if you're building, uh, an app, uh, like Slack or like, uh, discord where one user can be a member of multiple organizations, then you would need to add an additional layer. User, uh, has many accounts through membership, but, uh, for many cases this is just, uh, enough. So in the schema we have, uh, a table of accounts and users belong to accounts. Let's, uh, have a look at this account middleware. So it is a way to, uh, I think find the current, uh, account, uh, based on the ID in the roots, I think. Okay, well, I, uh, showed you, uh, what the jam is, how it generates different files. Uh, I invite you yourself to run each of these generators on top of the default generator as added. So like I added authentication, zero base basic without any plugins. And on top of it, you can try all the plugins, see what code it, uh, each of these plugins generates. It's a a good way for you to learn how To create all these features. Uh, Lazaro did some really good work for this. And, uh, yeah, one thing you should definitely keep in mind if you are importing all the authentication logic into your app, but not, uh, just, uh, outsourcing this work to an external library as device, it's your responsibility to manage this code. Now, uh, it is not the libraries. It is not like device. There's no more device that is in the background, authenticating all the users. You own the code inside your application and you can override it. And, uh, sometimes, uh, overriding the default path can lead to errors. So be mindful, then run out your own authentication and, uh, making changes to the defaults 'cause you can break something unintentionally. Uh, finally, I don't know whether I mentioned it, uh, uh, it, uh, the gem imports, uh, default tests, uh, for the generators. So, uh, you have broken tests for your authentication flow and this should make, uh, the possibility of errors lower. Anyway, I invite you to try out authentication zero. It is, uh, vote Rails eight authentication, wants to be in an ideal state. Thanks for watching and see you in the next one. I. --- # PWA in real life. The Rails World 2024 experience with Campfire and Agenda app | SupeRails #204 - URL: https://superails.com/posts/pwa-in-real-life-the-rails-world-2024-experience-with-campfire-and-agenda-app - Published: 2024-10-16 - Duration: 07:41 - Access: free - Topics: PWA During Rails World 2024 the 1000 attendees had the experience of using 2 PWA apps. The Agenda app by Telos Labs, and a Campfire instance. In this video I will show you how the two apps work. I am very grateful to Telos Labs for making the agenda app open source, so that everyone can learn and (reuse?) it as a boilerplate their events. Telos Labs Agenda App source code: http://github.com/TelosLabs/rails-world Buy Campfire: https://once.com/campfire ## Transcript France as Rails World 2024 has come to an end. I would like to talk a bit about the applications that we used during the conference. First of all, we had the, the Rails World Agenda application, and we also had an instance of campfire. So the agenda application was actually really cool. We had the full agenda of all the events by date, and if you clicked on a specific, uh, uh, event, you could see the details of the event. You could click on the speaker and see if he's taking part in any other, uh, talks during the conference. You could, uh, see his socials and, uh, for future events, you could add them to bookmarks. So you see, you kind of add them to a bookmark or to your schedule. And if you add an event to your schedule, then you will get a notification about the upcoming event, 10 minutes before the event starts. So it'll be a push notification. You would also have, uh, access to your profile where you would have a QR code and, uh, your social links. And, uh, the beautiful thing about this application that it was developed, uh, uh, and released as an open source application by TELUS Labs. So, um, it, uh, looks like a gift for the whole community. If you are creating an event and you need an agenda, you don't have to reinvent the wheel and, uh, create a new application, you could possibly just reuse this, uh, application. Actually, I recommend you to have a look at the application, see how it is built, and, uh, specifically you could be very interested in see how the push notifications are implemented. Let's also have a look at this application from mobile. So you see I am not sent in here and there is no food, food navigation. We do have it here. So, uh, login in, we have the agenda. Everything is beautifully responsive. The NEV B is stick to the bottom, and you see it's opened in a web browser, so it kind of doesn't look, uh, really cool. Let's, uh, open as A-P-W-A-I will edit to the home screen. Again, both apps, uh, were meant to be used as PWAs both, uh, agenda, application and the campfire. So here I've opened it as a pwa and it looks and feels much better. I don't have it as a browser tab, I have it as a separate, uh, instance, uh, on my phone I have it, uh, as an icon on the phone, so it looks good. Now, uh, if I go to my profile page, I also get this, uh, uh, prompt to enable push notifications. I will allow it, but again, uh, I have no way to reproduce it right now getting a notification because that set up to the s 10 minutes before, uh, talk in your schedule. So again, it's a wonderful application and you can, uh, have a look at the Source code at Teles Labs slash Rails world. The second application that you should have already heard about is mfi. So it was developed by, uh, three seven signals. And, uh, you can purchase access to the source code, uh, at advanced com slash mfi. And, uh, it's wonderful code base to learn from, from the makers of, uh, rails itself. And, uh, here I'm logged in, uh, to campfire. In the web, I have, uh, a list of channels like in Slack or Discord. You would have different channels and you can, uh, send the direct messages to, uh, users. You can upload photos, you can react the photos with the text, you can react with the emojis. Looks nice. And, uh, let's try, uh, actually doing direct messages between two users. So here I'm logged in as myself and, uh, in the phone, I will log in as, uh, uh, another user. Now here I have the login screen. I will actually also add it, uh, as, uh, PWA. So add into the home screen. Here are the two PWAs that we use at Trails World. Let's open the campfire application. I will sign in. Okay, so I sent in, and you see I have this prompt to, uh, I think enable notifications. I click on it. I have this, uh, button to allow notifications. Okay, looks good. Let's now send a private message. I can, uh, select one of the users from this scrollable list, or I can, uh, search a user by a name. I'm going to select myself and send a message. So let's say hello. And you see, uh, the other user has this popup that, uh, there's an new on Android message from this user. I open it, you see I'm typing here. And, uh, on the phone, the other user sees that the, the other user is typing a message. So I think it's a really wonderful, uh, animation to, to give the users a sense of anticipation. Okay, so looks, uh, quite cool. Now, um, let's try, uh, downloading this one as a desktop application. So going to file, I will, uh, click add to doc. Actually, another thing that Basem does well, uh, with, uh, uh, kfi is it, uh, gives explanations. It gives instructions on how to install it as a web app. So you see click on file, click add to doc, click on file, click add to Doc. Okay, add, you see I've added Rails via as a PWA on my, uh, computer. I click on it. You see the offers me to enable, uh, notifications on the computer. Okay, now, here again, I will enable notifications and let me send a message. And you see I have this, uh, notifications counter showing up. So, uh, it's quite neat. And, uh, yeah, that's, uh, basically it. Uh, I'm really happy that, uh, like a thousand result attendee had the, uh, chance to experience, uh, PWAs. Everybody had them on, uh, both apps on the phones. Everybody was getting push notifications. And, uh, again, there's just trails apps that, uh, uh, are built, uh, to be mobile, uh, responsive and friendly. Both, uh, use push notifications, uh, very well. And, uh, you should also build an application that, uh, is responsive, looks good on mobile, that can be downloaded as a PWA, and that has push notifications. Rails 8.1 should, uh, actually have this, uh, action notification, I think is the name, uh, new Edition that, uh, should make, uh, adding, uh, native notifications easier. So looking forward to Rails 8.1 and, uh, see you later. --- # Nav Button Component | Hotwire Native Bridge | SupeRails #203 - URL: https://superails.com/posts/af2918d5-fcb6-4cc5-ab0a-1917a219d479 - Published: 2024-10-13 - Duration: 15:27 - Access: free - Topics: hotwire native, iOS - Playlists: Hotwire Native In this episode you will: - Learn to install & use Bridge Button component. - Extend Bridge Button to accept images. - Learn what happens if there are multiple bridge buttons defined in your HTML? (The last definition gets rendered) Resources: - [✅ Blogpost](https://blog.corsego.com/hotwire-native-bridge-button) - [Hotwire Native iOS source code](https://github.com/hotwired/hotwire-native-ios) - [iOS Bridge ButtonComponent example](https://native.hotwired.dev/ios/bridge-components) - [UIBarButtonItem docs](https://developer.apple.com/documentation/uikit/uibarbuttonitem) - [Hotwire Native Bridge repo](https://github.com/hotwired/hotwire-native-bridge) - [Hotwire Native Bridge installation guide](https://native.hotwired.dev/reference/bridge-installation) `./bin/importmap pin @hotwired/stimulus @hotwired/hotwire-native-bridge` ## Transcript Hello, France. In this episode, we're going to add a bridge button component to our Aya native, uh, application. Now, a bridge button component is, uh, basically a button that we can add, uh, uh, on the top right corner or also top left corner of our screen. That can click anything on, uh, our application. It can be a URL or it can just trigger some JavaScript. Now, uh, here in this native example, we have an example of implementing this button components. So let's try implementing it in, uh, application. First of all, I'm going to open my iOS application. I'm going to do it with the demo app. I found the demo app again by going to, uh, GitHub slash hot fired slash hot fired native iOS. And here inside this repository we have this demo application. So, uh, I launched it, uh, locally in the Xcode, and now I'm going to add a new button component. So inside this bridge folder, I'm going to go to file new empty file button component. And, uh, in this button component, I'm going to copy all this, uh, code. Okay, and now we need to initialize this button component in our scene controller. So you have a list of, uh, components. We need to add this component to the list button component itself. Okay? And, uh, now we need to add the, uh, HTML and the stimulus jes to our application, to our web application. Now I'm going to do it an example of super ails. So, uh, here I'm, uh, going to, yeah, run the application locally bin dev. And, uh, in the demo of our iOS, I'm going to say that I'm going to point to local, local host 3000. Okay? Now I'm going to restart this application. Okay? Here I, uh, have the application running, uh, wrapped into, uh, hot native application. And I want to add a button here to open some page or to open the quick search. You see, when I click, click search, it opens up this, uh, dialogue. So how can we do it? I'm, uh, going to first of all, install the native breach in my Rails application. Now, if we go to the hot five, the repository, he have this, uh, uh, let's search for hot fire, hot native Bridge. We're going to install this, uh, library in our application. So the command to run this library, we can find it, uh, in reference bridge components, no, not here. Uh, bridge installation. So we're going to run this command to install, uh, hot native Bridge with, uh, input maps. I will, uh, stop my server. I will run the command. It should, uh, inject something into my input map. So going to input map, you see it injected the hot via native bridge, but uh, I need to push it to a new line. Yeah, here. Okay, and it updated the stimulus. Okay, now I can start my rails application, but yeah, actually I need to first maybe add the, the stimulus controller. So going back here in the example for bridge components, I have the stimulus controller. I will put it in the bridge folder. So, uh, here I have JavaScript controllers. Inside I will create a folder bridge inside, I will have button controller do gs. I'll paste this here, and, uh, now I can try having this h tm l somewhere in my page. Let's run m dev. And let's say in application html, uh, I will add this, uh, link. Now, I don't have a slash profile in my application, so I will make it slash uh, tags. And now, uh, let's see. I have the rails application run in. Yeah, I have this button, view tags, it directs to slash uh, tags. And let's see if it works in my, uh, iOS application. Now, I will, uh, restart the iOS application. And, uh, no, I don't have, uh, a button visible here, but I have this view tag. Let's, uh, double check. So here the data controls should be bridge slash slash button because, uh, this is nested in the bridge folder. Okay, let's try again. I will refresh the page. Okay, now I have this text. I click on it, it redirects me to text. And, uh, on all the pages in my application, I have this, uh, text button. I can make it visible only on one page by just going to a specific page, like static index. And on the static index page, I will have the stacks. So I'm refreshing here. Now, I think we still see it because it is ced. Let's, uh, restart our native application. Okay, so I'm on the homepage. We have this, uh, tags. I go to another page. I don't have tags. I live on tags. Yeah. So now we have this, uh, button visible on, on one specific page. Looks, uh, quite good. Uh, what if we want to, to make this, uh, button not text button icon? Well, we would need to make some changes. So first of all, in the button component, currently we don't accept icons. And you see we generate this UI bar button item. If we look at the docks of this UI bar button item, we can specify an image. So let's try specifying an, uh, image. I will, uh, say go my image equals image, and where we're going to define this image, let's define it here. Let, uh, image equals uh, uh, we are going to find a UI image, and we're going to find it based on the system name. And the system name is going to come from data image. And in our, okay, uh, has no member do data image. So we need to define it here. It'll say lead image equals stream. Okay, so now in our stimulus controller, we need to pass in the data image. Let's, uh, go to our stimulus controller. Here it is. And here we have, uh, we pass in the title, and we, they're also going to pass in the image. So let's say let the image equals bridge attribute, uh, iOS image. Okay? And, uh, in the control itself, they're going to pass in and, uh, image per, so I will say, uh, data bridge, iOS image equals, and, uh, what kind of iOS images are there? Let's open SF symbols application, where we have a list of all iOS images. Let's, uh, have a tag image tag. Okay, now, uh, I will restart, uh, the application and, uh, I expect, uh, our HTML to pass the I image to our stimulus controller. The stimulus controller will pass it, uh, to our button components, swift in button components Swift, I pass the image variable to UR bar button item. Let's see if it works. Um, it looks like it doesn't. I think, uh, I know why it doesn't work because, uh, here by the send currently we send just the title and we also need to send the image. You see, it is even, uh, blurred out that, uh, it is not used. So I will say title go Goma image. Okay, let's, uh, try again. I will restart my iOS application and, uh, let's see if it works. Okay, wonderful. So we have this tag icon. I click on the tag icon and it redirects me to slash tags looks, uh, fine. So, uh, what if they want to conditionally have either an icon or text? Let's see. So, um, here on the homepage, I have, uh, an icon. And, uh, if we remove, uh, the image declaration, let's see if it works. Uh, yeah, let's restart the application. So I think that if we don't declare the image at all, it'll, uh, not display anything. So if we don't want to have the image, uh, present, but want to just have the text, it should just leave this blank, but it should still declare it. Um, let's start now. Okay, so, uh, here I have the tags. I click on tags, and, uh, it redirects. Let's, uh, try using this condition in another place. So on the homepage, I will have, uh, an icon to go to tags. And on tags, uh, uh, index, I will have, uh, text to go to the, uh, homepage. So slash and, uh, home title will be home and, uh, no image. Okay, so, uh, I'm going to text and I have the button to go home. So the text is an icon, and the home is, uh, text looks good. Now let's try having, uh, not, uh, a URL, but, uh, triggering stimulus, for example, opening this, this, uh, dialogue. So this dialogue is defined in search slash form. And, uh, here the, the, this is, uh, the button that gets clicked. So here I'm going to also define this code. Um, let's see. I will say data controller equals bridge button. Um, bridge iOS image equals, let's add some kind of search. Um, I will cope with this. So magnifying glass circle and, uh, bridge under score title equals search. Okay, I need to add the coma here. Now the syn index should be correct. Uh, yeah, also coma here. Okay, now let's, uh, refresh a page and see if it works. I will, um, refresh here. We still have the tag. Let's navigate to another page like lessons. And here we have this, uh, search. I click on it and you see it, uh, does open the, the same search dialogue. So it looks good. You see it is not A-A-U-R-L, it just triggers some stimulus. Um, yeah, looks good. And you see, let's, uh, try to have a look at the, which button declaration takes precedence. So again, I open the homepage and we have the text icon, but, uh, this is also defined on the homepage, but we still see the text icon, I think because we have it declared later in the H TM L than, uh, this one. So if we go to the homepage and, uh, put it on the bottom here, for example. So we declare the tags, um, yeah, if we declare the tags, uh, before the search, uh, let's see if it changes anything. Okay, so you see the last declared, uh, uh, bridge button gets, uh, displayed. If I had the, this button declared after the search button, then the text button would be displayed. Let's see. Okay, it works. Um, yeah, I think, uh, that's it for this episode. So we, uh, added the button component. We have a conditional display of a button that is either a text or an image and had look at, uh, uh, which, uh, button gets, uh, displayed if there are multiple buttons, uh, declared on, uh, uh, an H TM L page, the last one in the H TM L gets displayed. Thanks for watching and see you in the next one. --- # Hotwire Native - Rails World 2024 Lightning talk (Unofficial recording), Yaroslav Shmarov - URL: https://superails.com/posts/yaroslav-shmarov-hotwire-native-rails-world-2024-lightning-talk-unofficial-recording - Published: 2024-10-06 - Duration: 09:31 - Access: free - Topics: iOS, hotwire native - Playlists: Live talks, Hotwire Native Slides: https://speakerdeck.com/yshmarov/yaroslav-shmarov-hotwire-native-rails-world-2024-lightning-talk You will learn: What is Hotwire Native? How is Hotwire Native different from Turbo Native and Strada? How to prepare your Rails app to work as a Hotwire Native application? (Core topic of the talk) Sorry for the quality! Why? Lightning talks are something new at Rails World. This year they were not officially recorded. I recorded this on my own device and added the slides. I hope you appreciate the work and enjoy the presentation. ## Transcript Please welcome. Yeah, it's good. Yes. Um, hello France. Matt, we happy with you? So the talks make initial levels ative, but it's kind of outdated now because maybe already stolen the news, uh, from seven Signals they have announced for radio. Now, what is basically, first of all, I think it should have been me given the stock have been either the ology or in the stock at the big stage. And look at this, uh, like budget version. You have the ads version version of a sticker. It's, uh, me, I, uh, gave out last year. I talked about, I also run the Explore channel and also I've been doing a series of videos about but dominated. As I said, it is outdated and this is a very good thing. It's going to make life easier for you. So if you haven't learned terminated yet, it's good 'cause now it's gonna be easier for you to learn it. Now what is the main problem? So that's for terminated. That was started and many people were asking me positive events between Terminator and uh, Strada and the for Strada to be released to start working on that. You already do it, the Terminator for a few years. So, uh, I think it was kind of a relevant issue and yeah, many people was discussion was Differe, Terminator, and Strada. I tried to make this kind of, uh, graph to show the place of top native and study in the for X system how it look, just need you go trade a new I application, be a to native application. So you trade a new I stack, you would install a to native, you would install on a specific branch that you store the ator it's application live the need install Strada to install str with need another like 10 files another day. You just hate your life just by the moment you have found the set to start the, the application. But luckily it has all been merged together. So through the litigator to the IO and uh, Strada merged together into a for library that is going to have a better branding, better documentation and it's going to be easier for you to start pulling mobile applications. And now if you haven to work on LY mobile applications, the smallest how for the look for you, you have the Excode, uh, editor. You have a virtual, uh, iPhone running. It is, uh, running your, uh, local host in your red server. You have, uh, your app server and uh, you do like just so your, your local host inlet your I run. So the net bar and the ER will be sold as native. And inside of inside is just your Rails application. That is sold as if look on a small screen. So this part is uh, native. This part is just your rail application as if look on that salt screen. So going back to the topic, how to make your rails application ready to work with the native and uh, how to submit application so that it works and looks well on uh, uh, inside, uh, native application. Now first of all, make it look work with a small screener, but you open your rails application from the tool. It should log suck, it should work nicely. Have a look that x, this is not the X app. This is X open via the browser and it looks and feels like an app. It has the auto navigation, it has the uh, N bar. So the x uh, mobile, uh, respons solution looks just as the app does say that. And uh, you can better here they have the, they have main contact and the remember this. So rule before building any mobile application, just make application find on a small screen. And here couple examples of uh, like with and link it in and applications that built that. All field fiber with a small scrap go further. Conditional logic. There are some cases that you bought to run something on a mobile application, something on uh, that application. You can use the help durable make app and the bullet on help us in the big files Control don't have about from that. Uh, this is the perfect setup to have conditional on the mobile and the on the back. So for example, in the states have a, uh, blank bedroom on the back and the five bedroom on the applications. Good. Further the disabling durable. So if you would take to false on the link that not be applicable inside the by native application. So try to avoid this, but there are a couple of cases that point to give disable this, uh, data to data. For example, if you're like doing strike checkout or if you're trying to do site in and this kind of flows will usually not work in your uh, ator application, you will need to find other base to create this kind of flows in net application. Good and terms example of a data to confirm, you know, for this information, that model of the cript works in your browser. And this is how looks and the works in uh, so by application device works out the box. If you install the new authentication is the broker out the box. If you out your own authentication, try to store the the user not in session but in good for that. So traditional stack navigation. Now here are a couple of examples. So first you I'm clicking and uh, it stacks navigation. So you see the drug prompts and uh, you I can go like further and back the same case by click and it replaces the navigation. So there is some cases where you do want to the navigation stack, like if within the browser you want, don't replace the current screen and picture couple of those hip that can help you in have a conditional different navigation patterns in the the web and in mobile go further if you try to, if you have target blank set up in your application and you sort click it in the side, the application, it's going to look like this. You see this, I'm clicking a link inside the application and it's opening in the, in the popup browser and it looks top. So this is a pattern for link tool but you can uh, set the app so you want like, uh, to disable target bank within your application also and make applications, uh, going further app review. So it can be hard but uh, you should fight for it. Uh, deliver that does Earth Development gave me a couple of fields as uh, how to find fight it. So first of all you should not the enable billing inside your for five application. You should enable it into application. And in some cases you log all disable sign ups in your application. You'll need to allow you just to sign up only in the that application. But signing in also like they'll be able just to sign in inside the native applications and you can see the pattern in base. And uh, so, uh, hate for example, uh, doing further. I think that there is space for couple all plates in the technician and uh, you'll be able to like the IS scope itself is more or less uh, user set advance and also do advance and you can use it around all your applications and uh, you don't need to have like a sort different I code for each application and uh, about strada rich components. So Strada, forget the mistake existed, have rich components. Now there are these different patterns for having the native elements in your page and uh, I see space to have a usable likelihood of components currently in the open source. There are couple of components but there can be more. Uh, going further, I uh, invite you to have a look at Native top Director trading, but Rob so is a big collection of uh, applications that have also built App store that use uh, software native and uh, some of them could have a source code that you can mix. Yeah. And uh, finally there is a bus route here that uh, is organizing podcasts and they launched their application with all the App store and it is also using the Profile Native. So just the part in the middle of Australia is just the Respons app, but the top and the bottom is native. And again, uh, I tried to have, look at Super Rail, I done lots of the videos, I done the series of and I'm going to do food Time naked. And uh, I invited to subscribe 'cause it help me continue doing what uh, I do. Thank you. --- # Hotwire Native Tabs | SupeRails #202 - URL: https://superails.com/posts/hotwire-native-tabs-superails-202 - Published: 2024-10-04 - Duration: 14:53 - Access: free - Topics: iOS, hotwire native - Playlists: Hotwire Native Let's add native iOS tabs! Each native tab has its own browsing session. We will also customize Navbar & Tabbar colours (make it them transparent). Finally, we disable Force Touch (hold a link for a long time to open it as a preview in an in-app browser). And like this, just with a few code changes, our iOS app becomes a near-perfect boilerplate for our next Rails + iOS app! 🤩 - [✅ Blogpost](https://blog.corsego.com/hotwire-native-customisation) - Hotwire Native iOS: https://github.com/hotwired/hotwire-native-ios ## Transcript Hello, France. So previously we had a look at tfi native iOS repository. We ran that demo application in the X code and uh, by click and John, we emulated the application on the, and the iPhone. And we had a look at how different behaviors work. And after this we installed the, well, we kind of updated the URL that we use in, uh, our demo application to be a real URL or to be our local host, 3000 where we're running our rails application locally. And we installed the, uh, bridge components and, uh, bridge components inside our Rails application. So we basically made our existing Rails application work with, uh, native. And now we are going to add new behaviors to our, uh, iOS application. First of all, we're going to add the, uh, tab bar. So if you're building a ribbon rails application, it's very likely that you will want to have a few tabs on the bottom for different navigation. Let's try adding a few tabs, uh, uh, to our app. So actually I'm going to code up the whole solution. I've already done it and I'm just, uh, going to speed things up and copy it and go through the code. So first of all, we're going to add this tab by controller to our application. I'm going to see in controller, and on the bottom of everything, I can add this tab by controller. And I'm saying that we're going to have, uh, uh, three tabs. The first will have the default title home, then post, then playlists. Uh, because in the super ails here, the main, uh, pages are the homepage, the, uh, lessons or the posts and playlists. I could also add tags, but uh, it's, uh, for another time. So, uh, then I also added the default icons house, play circle, and list the number. Where did I get these, uh, icon, uh, names from? So there is this app sf, uh, symbols with a lot of, uh, uh, icons that, uh, are available by default out of the box in iOS. So you don't need to input your own icons, you can just use these icons. So for Play circle, I just found it here, here we have this play circle. There are different options with the just play circle. Okay, so here we have, uh, we say that we are going to have, uh, these three tabs with these names and these icons. Next, uh, I'm going to, uh, add these navigators. So here on top we have navigator, and we're going to also add these, uh, navigators. Okay? Uh, next I need to add the, uh, URLs. So we have navigated root here, uh, no, not here. Navigator route. Okay, here in the syn controller. So we basically say that the redirect to the root URL and root URL is defined, uh, in our, uh, initially in our demo. So root URL is set. Uh, here it is demo current. Uh, demo current is remote. So it is this URL. And I'm going to say that, uh, the first URL is going to be root url. So it's going to be this UL and, uh, the other two tabs are going to be slash post and slash playlists. So it's going to be whatever the URL they have here or here slash post or slash playlists. Okay, let me add these. Uh, removing this one. And now we need to say that we're going to use tabs instead of, uh, just the root view controller. So instead of, uh, uh, kind of this, we are going to use, uh, tabs. So, uh, where is it? Uh, navigator root view controller. Instead of, uh, this, we're going to use the tabs. Okay, now I'm going to refresh our application. It is launching, let's wait a bit. And here, uh, we have our, uh, tabs on the bottom so I can scroll. Uh, we are on the homepage. Let me go to another URL. So going here, you see navigation is stacked. I go down to slash posts. These two pages do not work because, uh, slash posts and slash uh, place list do not exist in the demo application. So again, actually change this URLs to something that exists in the demo application where I know that we have slash one and slash two. Let's, uh, refresh once again. Okay, so we have have our home Now while we have, uh, the text hot native demo, but not the text, uh, home because, uh, if, uh, the page has a title, then the title overrides whatever text you put here. So you should, uh, be cautious about the titles that you're set in. Anyway, let's try navigate. And so I'm going to navigate another page inside this tab. Then I go to post and you see, uh, yeah, I will try. Okay, maybe we don't have slash one and slash uh two in this, uh, demo application. Let's, uh, have a look. Okay, it actually should, uh, exist. Let's go back. Maybe I can try just one and just two replace. Okay, so now it works. Now, uh, when I had slash one and slash two, it didn't work because here we already have a slash so we had a double slash, so we should be cautious with having the same, uh, kind of, uh, slashes in local and, uh, remote not to have, uh, this kind of this narrow vac something that works in one environment with one URL, but doesn't work in another one. Anyway, here I'm navigating to this tab, navigating to the other tab. I'm navigating within this tab. And, uh, you see navigation is stacked within this tab. So I'm going back here, let's try navigate. And you see here I'm where I was. Before I click once again, I go to the homepage of this tab. I go here, I'm in the same place I was. Before I click again, I go to the homepage of this tab. So think, uh, of, uh, this bottom tabs as, uh, separate browser tabs with, uh, their own, uh, browsing history. This is, uh, really, really cool. Okay, so we have added this bottom tabs and they kind of look awful. They are, uh, above the content of our application. Now, interestingly, you see if I'm scrolling, uh, down, also there is content scrolling above, uh, the title above all the native elements of the phone. So I don't want this, uh, content be scrolling up. Also, interestingly, if I open, uh, a native view, this is a native view that is, uh, present here. Here we have our numbers view controller, and here we have this numbers, uh, swift file. So see in the native view we have, uh, a background here and we don't, uh, overflow on the top. So we don't want, uh, to overflow on the bottom and on the top in our, uh, native, uh, web views. So to do this, I'm going to again, copy some code. Here is the example for UI tab bar. So the UI tab bar is this bottom navigation element. Again, I'm going to copy it and paste it in the bottom of our scene controller. And here I'm saying that the, I can update the background color. I can use some default system colors, but I can also use, uh, my own, uh, colors. Now these, uh, swift or iOS colors are a bit veered. So, uh, I actually found the call that I wanted and I use this website UI color io to turn a hex into a Swift UI color. And, uh, that's why I got this specific color. Okay, so I'm going to add this, uh, UI tab bar, and now I need to, uh, add it inside my application. So in the example I added above the bar controller where we define the window root view controller. So, um, here I'm going to add this UI tab bar replace now launching the application. And you see, uh, we have the color of our choice in this, uh, bottom tabs. So looks good. And there is no overflow. Now we're going to do the same for our Neva or header of our application so that we don't, uh, overflow like this. So, uh, to do this again, I'm going to talk over with this UI navigation bar extension. I gonna put it on the bottom of this file. Again, it is not the best practice to like put everything in one file. Possibly you could, uh, have that tabs in, uh, advanced Swift file. You could have this in the, uh, style or design or appear, uh, separate file. And yes. So, uh, I'm going to again say that I'm going to have, uh, a background color of my choice. I'm going to also update the, uh, title color to be white. So I will also add this, uh, where was it here? So I will say UI navigation bar configure with, uh, our background colors. Now let's restart the application. Okay, let's, uh, see, I'm scrolling around and you see we have the color of the text in the top bar of our choice. We have the color of the background of our choice. Let's try opening the foam example. And here you see the other elements are shown as, uh, they should be shown. Okay, uh, let's also fix one more thin. So, uh, what if I click, make a long click on the one of the links. I make a long click, and you see it tries to open a preview in an, in a browser. And this doesn't look really like a behavior of a real mobile application. You would want to possibly disable this, uh, uh, kind of click for a long time to open a preview. So to disable this, we can again, uh, add the an extension. Now, the thin that forces this long click to open the preview. It's called, uh, allow link previews by default. It is true, and we're going to sell it to false, and we're going to, uh, do it in an extension of hot wire config. Now, if we have a look at the hot wire code base, here is the code base itself. And inside the hot wire file, we have this, uh, hot wire config make custom web view. And, uh, it also says here that, uh, we can, uh, use it to customize our, uh, web view. So we are going to cope with this. I will put it, uh, I don't remember where the best place is. I think, uh, after configure bridge, before configuring the root view controller. And, uh, let's save. Okay. Uh, this will not work with debug. Uh, we are missing an argument. Let's see which argument we're missing. Um, frame. So frame do zero. Okay. And, uh, here we will say web view, allow link preview equals false. Okay, now I will, uh, restart the application again. Okay, let's now, uh, make a long click on this link. And you see it doesn't open up in your tab. Uh, it, uh, acts kind of if you were clicking on a button, uh, for a long time. So, yeah, looks good. That's, uh, it for the episode. So in just a few lines of, uh, code, we added the, uh, tab, tab navigation. We added the, uh, our custom, uh, background on the nav bar and on the footer, and we disabled the link reviews. Thanks for being with me and see you in the next one. --- # Hotwire Native on existing Rails app | SupeRails #201 - URL: https://superails.com/posts/hotwire-native-on-existing-rails-app-superails-201 - Published: 2024-10-03 - Duration: 18:10 - Access: free - Topics: iOS, hotwire native - Playlists: Hotwire Native In the previous episode we ran the Hotwire Native Demo app, explored the code and example behaviours. Now let's run our own Rails inside the Hotwire Native "wrapper". We will install Bridge in our Rails app. We will ensure that existing Bridge components (Form Component) work in our Rails app. We will add new Bride components to our app (Button Component). We will try opening pages in a native modal. Hotwire Native official docs: https://native.hotwired.dev/ios Hotwire Native iOS library: https://github.com/hotwired/hotwire-native-ios/ Hotwire Native demo web app: https://github.com/hotwired/hotwire-native-demo Hotwire Native Bridge library: https://github.com/hotwired/hotwire-native-bridge Install Native Bridge library in Rails app: https://native.hotwired.dev/reference/bridge-installation ## Transcript Hello friends. So in the previous episode, we explored Hot file native that refreshed the rebranded approach to create the mobile applications with Ruben Rails and iOS or Android. So previously we had tur native and Strada. Now hot native is Tur native and Strada combined. And also in the previous episode, we had a look at, uh, this hot five native iOS repository. We opened the demo application in, uh, Xcode. So here have the demo application opened in Xcode, and we click the run button to open it in a simulated, uh, iPhone 16. And we could, uh, navigate this, uh, website and, uh, interact with it as if it was a mobile application. And now let's try run it not on just a default, uh, example, uh, URL, but on our own application. So let's first try and run it on superl.com, the production URL to do this for the remote. I'm inside this demo file. Just going to update the uur L from Hot Native demo Dev two super rails.com. I'm going to rerun the app. And uh, again, this demo is invoked in the scene controller. Here we have root UL equals demo current, so demo current, and then root URL is uh, invoked. Uh, here in the scene controller, we navigate to the root URL. So okay, here we have our, uh, own application launched. You see that title is uh, uh, a native element, and it's the same title as I have here in the browser tab title. Uh, the logo is, uh, a bit, uh, squashed. Maybe we don't want to show it or we want to change the CSS to show it in a better way. Uh, this, uh, works so I can type something and search. You see, search works. Okay. Now these, uh, buttons will not work because I have data turbo false on all of them. Uh, so if you have data turbo false, then it'll not work. Um, then scrolling, uh, you see, uh, scrolling goes, uh, above this area so it doesn't look very nice. We would want to, uh, not make, uh, content visible behind this error. And then we have this, uh, uh, footer. We, I have a couple of tabs. This is actually not native footer. It is just the responsive design that, uh, I have, uh, for opening the browser in a small screen. So there's not native tabs. This adjusted my HT ML tabs. Now, if I go to, uh, URL, you see the navigation gets stacked, so it can click another URL. You see the title gets updated to the title of this page, and the navigation gets stacked so it can go back. Now if these were native, uh, tabs, usually each tab would be like a separate browser tab. So navigation would be stacked inside this tab. Uh, but now these are just, uh, HTML tabs, uh, not native ones. So navigation gets tab, uh, gets, uh, uh, stacked when navigate between these tabs. Okay. Uh, so again, there's buttons not work. Uh, let's try running it now. Not on another remote, URL, but on a local host. So to do this, I'm going to run my application on local host that I have already actually done. So here I have the application running on local host in the, I have a rail server, runin, here it is. And, uh, I'm going to say current will be not, uh, remote URL, but local URL. So they're going to run the local URL and it's going to be local host 3000. Okay, let's, uh, try running our local host 3000. And, uh, I will expect it to try to make requests. You see that this iPhone, this, uh, simulated iPhone is making requests to our application. Now, I'm going to click on lessons. And you see whenever I click, uh, we get well requests made to our local server. So it's kind of working and it looks small as the same as it did in the production, but this is run a local host. So, okay, let's now see if, uh, which components would be broken. So, um, but if all to have a phone component, a menu component and overflow menu component, let's, uh, try adding a potentially a new component. We have in the example here, let's have a look at the example, a bridge component, an example of a button component. So we can have, uh, a button in our H TM L and it would render a button here in the, the native, uh, header in the top right corner. So let's try adding, uh, uh, bridge button and see if, uh, uh, bridge components for the work out with the box. Let's try it in this button. We can edit, uh, in our layout, for example. Okay, I will, uh, refresh the page here. We have this view profile, uh, link and need to add the, uh, stimulus controller inside the bridge folder. So, uh, I'm gonna create a folder named the bridge. Inside our JavaScript here we have JavaScript controllers. Inside bridge, I will create a button controller js. Okay. And we'll need to add a swift component in i our iOS application. So I'm going to just copy with this. I will go to X code. And here in the bridge folder I'm going to create a new file file, new, uh, empty file bridge component or button component. Okay, I'm putting in this, uh, content of the example button component. And uh, I will also need to say that we are using this button component in our, uh, scene controller. Here we have a list of, uh, bridge components that we want to use. So I will add button component self. Now, uh, how can we know that, uh, we are using this components from our Rails application? We can actually go to application HTML and display the request user agent. So, uh, on the web, if I refresh local host, uh, I will have this user agent. And, uh, on mobile I will refresh. And here you see I have, uh, native iOS turbine, native iOS, and a list of bridge components in the an array form menu and overflow menu. Now, why don't I have a button? Because I need to restart the, the iOS application 'cause I've done some changes. So I'm going to replace the application And now it should, uh, also initialize the button component in the request user agent. And here it is. So we have four menu, overflow, menu and button. Okay, this seems to be working, but uh, we still don't see the button here on top. The thing is we need to, uh, install, uh, bridge inside our, uh, rails application. I don't know whether it, uh, gives this error that like we don't have bridge installed. Let's have a look. Yes, fail to register Controller bridge button, uh, module name hot fired Native Bridge does not, uh, resolve. So we will need to go back to Hot fired. And there is a library that we need to install. Let's find it. Um, I think it is hot via Native Bridge. Yes. So this is a Geoscope library that we need to install inside our, uh, rails application. Actually, I think it is documented here. So we need to install the hot via native bridge JavaScript package. So we need to run this command. Let's run it. It'll make sure that we have stimulus installed. And hot via native bridge is a new package that we are installing in, uh, our input maps. So let's open input map. Now, it did, did not input correctly. I will need to add a new line here. Okay, this should work. Now. I will start the server once again. Okay, will I still get a JavaScript error? No JavaScript error this time on the server. Now, uh, let me try refreshing our iOS application. Okay, maybe I need to actually restart application now. I think, uh, the view profile button should be displayed or maybe the URL cannot be resolved. Let's see. Yeah, so view profile still does not, uh, display here on top. Yeah, maybe because the URL is uh, invalid. So let's try making it slash uh, posts. And here we'll write posts that a bridge and the title would be posts. Okay, let's, uh, restart once again. Okay, it is still not, uh, rendering here. What can the error be? Now let's, uh, have a look again at our HTML. So the error is simply here. So we imported this and we have data controller button, but actually our button is, uh, nested inside the, the bridge folder. So we need to have it data bridge button, and uh, this should reference the correct button controller. Now I'm going to refresh the page and I think I will see yes. So I see the post button here and we see it on all our pages 'cause we have it inside our application layout file. Okay? So we managed to make a button, a native button, work on, uh, our application, uh, on iOS. And we don't want to display this view profile or both link in our, uh, web application. So again, going to the docs, there is an example of hiding, uh, uh, an element, uh, on mobile. So I'm going to try to put this into our application. CSS, uh, let's, uh, try this. Okay, it doesn't work. Let's, uh, see if it works on local host. Okay, let's have another look. So, uh, in the code that we copied, actually, I will just use it like this. We would say data controller breach button display, none. So now I'm going to refresh, uh, my application here. I will, let's say I will comment out this display none. I have this link to post displayed. Okay, so now I'm not displaying it. And now on mobile, I will restart the application. Okay? And you see I'm not displaying the link to post here anymore. It is hidden as a bridge component, and I only display the link to post in the top right corner. So this works. Okay, let's try some other native behaviors. Let's try, uh, changing the path configuration. So here we have some patterns as slash new added send in strata form. And these are open in a model by default. What if you want to try something wild? And we want to open playlists in a model just playing around. So I can just add the uh, slash playlists because this URL is slash playlists and I'm going to open playlists in a model. Let's see what the behavior is going to look like. So I'm going to restart the application. And now when I click on playlists, you see they are opened in a model. I have the done that is kind of a cancel or close button, and I have posts that is present on all the pages in my application. So looks, uh, weird, but, uh, looks, uh, well the behavior of open a model on playlist works. Okay, let's try adding another Strada component here. We, we have the example of foam component by default. So let's try adding this foam component. Now we can get the example code for this foam component. Again, I will go to the example code base, uh, in, um, hot fired. Let's search for hot wired, uh, native demo. I'm going to find the form controller here. We have the form controller. I'm going to copy it and paste it into my own application. There is, uh, Okay, here I have bridge. I will create a form controller js. I'm going to paste this in. And, uh, let's have a look at, uh, how the form has been used here. So form in JS B, okay, we have this form example. Let's actually paste it somewhere in our application, for example, in, uh, playlists slash index. Here I'm going to paste in this form. Let's have a look at, uh, it in our application. So I go to slash playlists. Here I have this form that I have copy pasted. And it is, uh, just, uh, uh, simple HTML form. And in the vap it works as a simple form. But, uh, in, uh, the native application, it should, uh, hide this, uh, submit button. And instead it should display the submit button in the top, uh, right corner. Let's, uh, just in case we start the application, so it reit initializes everything, close the cache. And, uh, again, I added this inside our playlists. So I'm going to go to slash playlists. And here you see we have, uh, this form, we still have the submit the button visible. And, uh, you see we have the submit the button here. So, uh, the submit button is overriding the post button. The form component is currently overriding the, uh, button component. And if a class press submit, it's actually going to try to submit this, uh, form. Well currently submit it, just the loads, the current page. Okay? So we managed to run this, uh, uh, form component. It, uh, managed to replace, uh, whatever we had in the top, uh, right corner with the submit button that actually submits this form and disables the submit button during the submission process. Well, I think it's, uh, enough now. So we managed to install, uh, bridge, uh, in our Rails application. We managed to use, uh, uh, model. We managed to use the, uh, bridge form, component and bridge, uh, uh, button component. So in the next one. --- # Hotwire Native: Getting Started. Build mobile apps with Rails | SupeRails #200 - URL: https://superails.com/posts/hotwire-native-getting-started-build-mobile-apps-with-rails-superails-200 - Published: 2024-09-30 - Duration: 11:32 - Access: free - Topics: iOS - Playlists: Hotwire Native Turbo Native is now deprecated. Use Hotwire Native instead! Hotwire Native is a combination of Turbo Native + Strada + Turbo Navigator. Also, Strada Components have been renamed to Bridge Components. Hotwire Native offers much better conventions when starting a new app than Turbo Native did. Hotwire Native is a Native (iOS/Android) wrapper around your Rails app. It is the leanest, fastest approach for you to release a Native app as fast as possible. Because you just render your Web app in a wrapper! In this episode we run the demo application and git into its code. Official docs: https://native.hotwired.dev iOS library: https://github.com/hotwired/hotwire-native-ios Demo web app: https://github.com/hotwired/hotwire-native-demo ## Transcript Hello friends. So recently I've been recording a series of videos about how to build mobile applications with Ruby on Rails and Turbo Native, but it is now outdated five because just a couple of days ago, uh, AYA native has been released to replace Turbo Native. Now what's the whole point of this replacement? Well, basically then you wanted to start building the turbine native. You would need to go to the like Turbo iOS repository. Uh, you'll use this as the base of your application, and then you would need to install that Turbine navigator, uh, extension. You would possibly want to use this, uh, pull request that hasn't been merged in the, like a year into Turbo iOS. And then on top of this, you would want to install Strata. And the whole starting out approach was really not straightforward. And, uh, the idea of Native is to merge, uh, strata Turbo Navigator and turbo into one, uh, uh, starting application so that, uh, it has some conventions and you don't have to break a neck each time you try to start a new, um, turbo native application. So basically Hot File native is the combination of Turbo Native, turbo Navigator by Joe Melo and of Strata. So, um, in this episode we are going to have a look at the AYA native. We are going to start an application with it. So here we have this native, uh, iOS repository. I have just cloned it locally and inside we have this, uh, demo. So I will navigate to demo and inside we have a few different, uh, files. Now, demo Xco code project file is the one that we can open with the Xcode, the, uh, tool for basically developing, uh, iOS applications. So if you don't have Xcode installed, do it now. Assuming you have Xcode installed. Let's keep going. So I'm going to write open and I'm going to open Demo Xcode project. This is going to open, uh, Xcode editor. Here it is. And here is the demo application. Now let's have a quick look at this application we have. Uh, okay, we have, uh, it pointed to the remote URL to this URL. Let's open this thing in the browser. So this is more or less the same application that was used for the example of TUR native. Uh, and we are going to see how we can navigate this application on a mobile application. So we have couple of examples. So by default that point into the remote URL, but we can also point to local URL. Let's have a look at this Syn control. It's like the syn delegate and have, have, uh, the root TRL that, uh, uses the one from demo current that is remote. This one. Then, uh, we use path configuration. So path configuration is this, uh, js o file that defines some behaviors in our application. Uh, then we have, uh, some breach components previously Strat components already installed in this application. So form component, menu component and overflow menu component. Um, an example for authentication and uh, example for using, uh, native pages instead of our own pages. And yeah, here we here we basically define configure bridge, configure view controller, and here we, uh, use them. Okay. We also have this app gate file where we can add some, uh, settings for our application. So button display mode, minimal, and so on. We will go deeper into it later. Anyway, let's try running this application. I'm going to click on this run button and it is going to open a virtual iPhone, and it's going to run this application as a native app on this virtual iPhone. So, uh, here we can kind of see the progress of the installation on top. It can, uh, take a few seconds. And importantly, so we have this like demo application, but we also have the native iOS library set up on this project. So if you go to the, like project settings here, package dependencies. Oh yeah, well, we don't have it in package dependencies because it's in the same file system, but uh, yeah, this apple, this demo depends on the hot file native iOS library. Okay, it's launching. It can take a vial. Okay, wonderful. So the application is running. So basically we have an iOS wrap that just serves this, uh, remote URL. We can, uh, try and navigate, and you see I have this, uh, demo application running. I will click on it, I open the application, and we can, uh, play around with the navigation. So let's try opening a page in a model, you see it opens in a model. If we try to open this page, uh, in the browser, you see it as slash new. And, uh, our native application knows that we want to open slash new in a model. Why that? Because in this path configuration, we have set up all the slash new pages open up in a model, and we cannot pull to refresh. So like pull in the refresh. Okay, let's, uh, have a look at some navigation patterns. So navigate into another page. You see the navigation gets, uh, stacked so I can, uh, click back or I can swipe to the left and it'll, uh, navigate me back. Then uh, there are different types of navigation. So advancing, you see now I have two stacks of navigation. There is, uh, replacing, so I click replace and the navigation was, uh, not stacked. You see there's just one level of n navigation. Then you see that there is this native, uh, loading animation. So scrolling around, going back to the same point, uh, on the screen after going back, then the native error page. And uh, yeah, let's try opening an external link. You see it, uh, tries to open an, uh, in a browser. So it's kind of open a browser in a model as you would have with many modern applications. And, uh, the user is still inside your application. He doesn't, uh, open in actual browser and, uh, bridge previously strada components. So here is an example of a form component. Instead of, uh, display in the submit button on the bottom as we would have it, uh, lemme go back as we would have it here. So here see in the web application, we have the submit button on the bottom and it submits. And here in the the native application, we are hiding the web submit button. Instead, we are displaying a submit button in the top right corner. And by clicking submit, it actually submits the hidden, uh, uh, web button. So this is a native button. And, uh, to see how this native button works, uh, we have this bridge folder, have have the form component here is all the code needed to make this, uh, native button work on the, uh, iOS side. And if open the source code of this, uh, web application, we will be able to see how stimulus is set up. Let's, uh, go back to hot fired and have, have this hot via native side. And, uh, people look for form, no, maybe not hot via native side, but, uh, hot via native demo. Let's see. Hot fire native demo. Yes. So he have this, uh, bridge form controller. So in our web application, we have this phone controller that, uh, matches this phone component inside our iOS application. And, uh, we also have, uh, we also have, uh, I think an app delegate where we configure bridge. Yes. So here we have a list of, uh, we need to have a list of all the, uh, bridge components that we are using. So here we create them, and here we have a list. Okay, going further, let's, uh, go back to this application. So we have this, uh, form component that submits a hidden button. We have this menu example that opens a pop up menu on mobile. And, uh, let's see how it works on the web. So we have, we have this menu, it's like a dialogue, and there's also an example with, uh, overflow plus menu. So you see we have this, uh, three dots, and these three dots open the same kind of menu. It looks really nice with three dots, in my opinion. Let's try it here. Here it looks the same as, uh, just a dialogue. Okay, so yeah, I think, uh, this is a very good starting point. So we have this, uh, demo application. You can try running this demo application on the local host by just, uh, sending this, for example, to local host 3000 and using local instead of, uh, remote. You can, uh, try looking at the, uh, native demo, this one, and, uh, go with these, uh, stimulus controllers into your application. And, um, let's also see where it is applied. So let's go here form EEGs. And here's the example of actually using this, uh, stimulus controller with HTML to make this, uh, to display the form and to make it, uh, evoke as, uh, with a native submit button on mobile. Okay, I think that's it for this episode. We had a basic overview of, uh, the native, and in next episode, we're going to run it in, uh, uh, rails application. --- # Turbo Native 7. Strada Form Component (Native submit button) #196 - URL: https://superails.com/posts/turbo-native-7-strada-form-native-submit-button - Published: 2024-09-22 - Duration: 08:36 - Access: free - Topics: bridge (Strada), outdated - Playlists: Hotwire Native Strada Form pattern lets us hide the submit button we display in web views, and replace it with a Native submit button in the top-right corner of the screen. I think this should be the default pattern used for most Turbo Native forms. [Episode source code](https://github.com/corsego/tinder/commit/83bf8f2c4def9539627150e461bc47d01bbb452c) [iOS source](https://github.com/hotwired/turbo-ios/blob/main/Demo/Strada/FormComponent.swift) [html/rails source](https://github.com/hotwired/turbo-native-demo/blob/main/public/javascript/controllers/bridge/form_controller.js) ## Transcript Hello friends. In the previous episode, we installed Strada and added a button component that would let us, uh, display native buttons in the, this, uh, nav bar. So I can add a button on the left or on the right of the, uh, native nav bar. I click at the button and uh, it, uh, works just as a link. And now we're going to add another start component that is a form component. So the form component is going to let us have a submit button on the top right corner of the npa, as you would have in many, uh, native iOS applications. And we're going to hide this, uh, update button from the form. So whenever we have a form open, we can say that we're going to use the Strada form component. And we are going to have the submit button here on the top right corner. Now we can take this, uh, form component from Turbo iOS. We go just to the demo application here we have Strada here, we have a few components and they're going to use this form component. So I'm going to copy the file name, go to our iOS application, and here inside components I'm going to create a new empty file that I will name form component Swift. I'm going to copy it. The accordance of this file, I really don't want to dig into this. Uh, you can have a look at it later at your own pace, but uh, I'm just going to reduce this component that is given to us. So, uh, we have this form component and now importantly I need to go to this bridge Component Plus app. And here I'm going to add the form component itself. So, uh, previously when the, uh, opened our request user agent here have bridge components, just button, and now it should be bridge components button and Form. So our I application is communicating to the web view that we are going to use one more Strada component. Let me refresh, uh, the iOS application, I'm going to restart it now. Okay, so here we have bridge components, button and form. And now we need to add a correspondent stimulus control in our Rails application. So, uh, as we have this turbine iOS, uh, demo application that runs with, uh, this turbine native demo repository, and from this repository, I'm going to find the form controller that is as stimulus controller that would go hand by hand with this form component. So I'm going to create this, uh, form controller inside our Rails application. Here. I'm going to controllers bridge. Inside I will have form controller js. I'm just going to copy the contents. So, uh, this form controller is going to communicate with, uh, this form component in the iOS application. And, uh, now we need to apply this form controller to a form in our application. So, uh, let's say, uh, when we visit the edit user registration path, we are going to display the update button in the top right corner right here. And, uh, to do this, we need to, well apply this from controller to our form. The form is in device registrations edit. So, uh, here I'm going to say data, uh, data bridge form, uh, no data controller equals bridge form. Okay, this one, uh, we have this dash dash because, uh, the foam controller is Nest inside the bridge folder. And we also need the, the submit target. So here we have the submit button. So I will say data, uh, bridge form, submit, uh, bridge form target will be named Submit. I think the Synex might work. Let's see if, uh, it works. I will restart the iOS application. Okay, now I'm visiting the added user path. And you see I have an update button here. I click it and it does in the form. Looks good. Now we can hide this other update button because it became redundant. So I'm going to do this with, uh, uh, what we did previously with, uh, the terminated variant. So on terminated, we are hiding this button. I will say, uh, gloss termin native hidden. Okay, let me refresh the page or resubmit it once again. And you see the update button was hidden in the Terminator view. And if I look at the application in the web view, I still have it, uh, just as it was before. So looks good. Now going back, let's have a look at how this form controller is used in the demo application. So we can go to views. And in the views we have Turbo, uh, they have strata form. So, uh, we have the form on the form, we say data controller bridge form, just as we do in our Rails application. And it also says Data action turbo submit, start bridge form, submit start and submit end. So it is also utilizing this submit start and start end. Let's also add this line. I will say, uh, data action. And this must do it. Let's see if, uh, the H TM L looks valid, uh, on the web. So data, action door submit, start bridge form, submit and submit end. Submit end. Okay, let's, uh, see if we see any differences in the mobile view. Now let's try clicking update. Okay, uh, let's restart the application. Now I will go back and resubmit. And you see the button is being disabled for a bit more time while the form submission is happening. So here we utilized the submit start and submit, and that will disable the submit button. And that's, uh, basically it. So now we already are using two separate native, uh, uh, Strada components, one for buttons in the npa and one for, uh, one for the form. Now, what if I move this button to be on the right? Let's have a look. I'll put the button on the right. I will restart the application. Okay, so now I'm browsing the application different pages. I see the profile page here. And when I'm on the actual profile page, I don't see the link to the current page. So that's kind of good. I have the update button and it submits the form. So looks good. And that's it for this episode. We learned how to use the Strada form component, see in the next one. --- # Turbo Native 6. Strada. Navigation Button Component #195 - URL: https://superails.com/posts/turbo-native-6-strada-navigation-button - Published: 2024-09-22 - Duration: 08:44 - Access: free - Topics: bridge (Strada), iOS, outdated - Playlists: Hotwire Native Unfortunately currently TurboNative + TurboNavigator + Strada don't just work out of the box (yet). The setup is quite tricky. I installed Strada the same way it is done in [joemasilotti/daily-log](https://github.com/joemasilotti/daily-log). In this episode I will not copy-paste the setup file by file. Rather I will walk through how Strada works when it is already imported. We will also import a Strada Button component. Strada git repo: https://github.com/hotwired/strada-ios Episode source code: https://github.com/corsego/tinder/commit/767c5b530612abb1e016af12cccecaf8c22967c1 ## Transcript Hello, friends. In this episode, we're going to continue building a mobile application using Rub Rails and tur Native. So in the previous episode, we learned about conditional logic, conditional uh, rendering, uh, so that we can have, uh, different HTML or different, uh, rub Logic in the web view and, uh, mobile application. And now we are going to add strata into the mix of our application so that we can have, uh, native components in our mobile application that, uh, do not exist, uh, inside the, uh, normal browser. And we are going to add an example of a button component. So you see here I have the mobile application, and here I have this, uh, uh, image. And this is actually a button. I click on this button and it, uh, opens the added user path. Now you see I don't have the, uh, user's email with a link here in the N bar, though I do have it in the web view. So here, I click on the, this, uh, link and it takes me to edit the user. I don't have it visible on the mobile application. Instead, I'm hiding this button on the mobile application, and I'm showing it, uh, here in the top, uh, right of, uh, the native part of our application. So how can I do this? Now? First of all, we need to install, uh, Strada. How do you install Strada? This is quite, uh, hard. So, uh, if you go to the Turbo iOS, uh, uh, repository and open the demo application, you will see a folder name Strada, and it has a few Strada components form, menu, and overflow and bridge component that lists all of them. And the Strada is kind of already installed in this demo application, but, uh, we are not using demo application. Here we are. We have created a new application and we are using, uh, this Turbo Navigator branch. And, uh, it, uh, has quite a special way of, uh, installing Strada. So install Strada is not that easy. Uh, now I found my solution in, uh, the repository by Joe Malo named Daily Log. So here he is using the latest version of, uh, uh, turbine Navigator. And, uh, on top of this branch, uh, we install Strada. So I found the code here, I found to iOS, uh, app, and here we have a few folders that are specific for installing Strada. Now, I'm not going to copy paste all the code, uh, in the, this video. Uh, you can either do it yourself, uh, by following the changes, uh, in the, the get the history for this episode, or you can just check out, uh, this brunch. But I wouldn't, uh, copy paste. I would just check out this, uh, brunch myself. So let's have a quick look at how, Uh, uh, the code is structured for making Strada to work. So, um, first of all, I install Strada iOS package in my application. So here is my application, Tinder, iOS, and here in package dependencies. Previously I installed Turbo with a Turbine navigator branch, and also I added Strada with, uh, this GitHub URL and the branch main. So we installed Strada Looking for further. We create this bridge component and bridge component. We create just one button component. So, uh, heavily need to list all our native components, and we created this button component. Again, I took, uh, this button component as inspiration from the daily log application. Here we have components, and here we have a button component going further, this web view controller is just a, a copy-paste of just setup that we need to have. Uh, and this one is quite interesting. So in this durable configuration file, we say that inside the user agent, we are also going to list all of the, uh, native components that we have here. So we're going to have the component name button, and we can double check it by going to our Rails application. And here we have request user agent, let's, um, comment it. I will, uh, refresh my web application. Uh, I see that we're using the web. Uh, I don't see the name of the component, but if I refresh, uh, a page in my mobile application. Okay, lemme refresh here. You see, uh, I have this added bridge components button. So, uh, we are kind of communicating from our iOS application to our Rails application that the, the iOS application has a bridge component named button. So the iOS app is saying that this button component exists and inside our Rails application, if we need to have also a button component. So I have this button component inside the, uh, stimulus controllers inside a bridge folder. And, uh, you need to have a component in the Rails app, and you need to have a you component, uh, in the iOS app. So this two components work together and, uh, what do I do? So here I can have a link. Uh, the link has the URLA link can have, uh, an image and, uh, the placement either on the left or on the right, but it's placed just on the right. So, uh, The Rails app sends this to the iOS app and the iOS app. If it sees that we are sending a button, then it displays the button here on top. So, uh, here in the application layout, I added data controller bridge button, the name of the image from the iOS images and placement in the right. If I say left, then the button should be placed on the left. Lemme try refreshing. You see, uh, now it is on the left, but it's still on the right because it was ced. So maybe I can restart my iOS application. And, uh, I suppose I will see the button only on the left. Let's wait for it to start. Okay, so the app has relaunched and you see the icon is placed just here. Looks, uh, good. So, uh, yeah, this is the basic, uh, installation that we need, uh, to have, uh, start work in an application that uses, uh, to navigate a branch. I have the stimulus controller for the button. I have, uh, the Swift component for the button that work, uh, and communicate between each other. And also, another thing that I needed to do is, uh, install Strada inside the Rails application. To do this, I run the command bin, input map bin, hot wire Strada, inside the Rails applications folder. And, uh, now basically the web application works just as it worked before. But, uh, inside the iOS application, if a page has, uh, a button with, uh, that data controller bridge button with a valid image, then it's going to place it, uh, in the native, uh, header. Also, if you're going to use different iOS images, you will need to install the app locally, uh, SF icons. So this, uh, app has a list of all possible, uh, icons that you could use. Uh, inside the io s You don't need to like have your own icon assets. You just can use the iOS, uh, icons. They have quite a big library. So, yeah, that's, uh, basically it. We have installed the Strada. Again, I did it based on the way it is installed in this daily log application. And we added a button c. --- # Turbo Native 5. Conditional Rendering (only Web/only Mobile) #194 - URL: https://superails.com/posts/turbo-native-5-conditional-rendering-css - Published: 2024-09-08 - Duration: 16:37 - Access: free - Topics: iOS, outdated - Playlists: Hotwire Native use html title, conditional rendering with turbo_native_app? helper: https://github.com/corsego/tinder/commit/a5b88ae1d606008d851da2d7b633d9cfebae2862 display app name in title only for Web: https://github.com/corsego/tinder/commit/8e0d7a09b862d3eaf7347ff62de6687550350d6e tailwind turbo-native variant: https://github.com/corsego/tinder/commit/f7738d4efc16280f37a536dde49535f78ce8bbba ## Transcript Hello friends. So in the previous episode, we made the log in logout, uh, and basic navigation flow feel, uh, quite nice in our application. And now let's do some more improvements. So, um, first of all, you see I go to the homepage. I go to the dashboard, and uh, I have the page title visible here, but uh, I just have the app name written on top. And what if we want to change the text, uh, based on what page we are on? So how can we do this? Uh, you see here in, uh, super Rails, I have the application name and the page title here on, uh, this application that we're building. I have just the application name on each, uh, tab. So you see on Superl I navigate to tags. I have, uh, tags in the title playlists. I have playlists in the title. And here in the application you have just the, the application name via. So, so if we go to our application, h, tm, LERB have have the meta, well, not just meta, but the title, uh, H TM L tag. And we have, uh, Tinder Rails, the name of the application written here. If I change it, let's say I put one in the end, I refresh here, we have a one in the end, I go to our mobile application. I refresh it, I have a one at the end. And we want to conditionally display different titles based on which page we are looking at. So, uh, to do this we can pass in the content for title. You see we already have this outta the box and they create a new application. So let's go to our homepage. Uh, it is static index and we are going to set our own title for this page. Let's, uh, say content four, title, coma, homepage. Okay, I will save, I will go to the web application. I refresh, I go to the homepage. You see we have the homepage in the title I go to dashboard. We have, uh, uh, just the basic application name in the title because we don't manually set the title. Let's go to our mobile application. I will refresh, go to the homepage, and here you see we have homepage instead of the application name in the title go into dashboard. We can have the application name. So let's say the title for our, uh, for our dashboard, go into dashboard. Title will be dashboard. Okay, refreshing you into dashboard. You see we have the dashboard in the title Looks good. Let's do something similar for some other pages. For example, here we have, uh, the edit my account page. It is edit User here. So let's uh, go to this, uh, registration edit. Here we have this edit. Let's ensure that it is it. I'll just put something. You see we have one to two. Okay. And, uh, here we can say, uh, content for title. And here I will just put, uh, the same kind of text edit. Okay. Refreshing. Okay. Edit user. You can better maybe say edit my account. You refreshing. Okay, we have Edit My Account. So now they are leveraging this, uh, uh, page titles that we get in Terminator applications out of, uh, the box looks. Uh, okay, we can do the same for sign in. Uh, sign up, let's try doing it. So log in, have, have login here. I will, uh, go to sessions new and I will add log in here. Okay. And for registration, I will do something similar. Registration new, and here I will have sign up. And uh, it's really good to do it not only for the mobile pages, you can also do it for the, uh, app pages itself. Uh, on the web it would be good to update the browser tab name based on what page you're looking at. Okay, looks, uh, fine. But uh, you see here in mobile we have sign up in the page title. Then we have sign up here. Same with the login. We have it in the page title and we have it here. And we don't want to like have this login text, render the device. It's just like a duplication. So, uh, we would want to hide this login text if we are looking at this page on mobile. Now, the simplest way to do it for be using the TUR native app helper that we demonstrate. So we have this check Turbo native app question mark. And if I refresh here, you see the same tool. And for our web application, it's same false. Where do we actually get this help from? So we go to GitHub, hot wired, durable rails, and we go to navigation helpers, navigation rp. Yeah, we have this help method terminate app, and basically it checks, uh, if the request user agent has the text terminate in its, uh, name. Let's actually try displaying the request user agent, uh, in our application. So, uh, let's have a look on the VAP used. We have Mozilla, uh, safari. I'm opening this page in this very browser. And if we have a look at the user agent on mobile here, you see, uh, we have, uh, tur native iOS text written here. So if I was opening an Android application, it would be Termin native Android. And actually we get this, uh, helper thanks to this Turbo navigator branch from, uh, uh, turbo iOS repository. So, uh, we know that we're open this application, uh, on mobile and we have this, uh, helper that is true or false based on whether we are opening it in mode in mobile. So using this, uh, termin native app helper, we can, uh, have conditional rendering logic and we can also have conditional logic, not just in rendering, but in controllers, for example. So, uh, let's go to our sessions new and let's say, uh, unless termin native app then render this, uh, login text. Okay, let's, uh, refresh. So see now we're not changing this, uh, login text here, and we can do the same for some other pages. So for registrations added here, I, uh, will display edit only if it's a web application. And for registrations new like this, so going to register, we don't have this signup text here. We have it only in the header, but if we open it in, uh, web application, we have the edit user or log in, sign up, text displayed in line here. So it is, uh, some basics of conditional rendering. Let's, uh, save our changes. Um, use title, use HTML title and, uh, conditional rendering with, uh, termin Native App Helper. Okay, let's just have another quick look at, uh, the code changes. Looks cool. Now, um, let's see, you see here in the f we have just signed up. We don't, uh, have like name of the application. I also like having the name of the application displayed the, like here I have the page title and here I have the name of the application. Let's try displaying the name of the application in the title, but only for the web application. 'cause you don't want to have like the name of the application just taking up space here. So, uh, it'll be quite simple using the BER native app helper. I will just have, uh, uh, content for, yeah, let's, uh, have, lemme think. Maybe we can have one for title slash Tinder rails like this. I will refresh here. Okay, we have Tinder rails displayed here. We also have Tinder rails displayed. Let me display this only conditionally if this is not a TUR native app app. So, uh, we can have another condition. So they will display this string, uh, unless turbine native app. Okay, let's, uh, refresh our web application. So in the web application we are showing the application name and, uh, here we are not showing the application name. So looks, uh, good. Uh, let's say we'll save our changes saying, uh, display app name in title only for web. Okay, it looks quite good. Now, uh, so far we are hiding or showing HTML based on this terminated app helper, but sometimes to make the flow look better, you would want to do it with CSS so you don't have conditional CSS and using CSS hide or show something only on termin native or only on the web application. So, uh, to do this, we can go to our tail vendor config js and add the separate variance. For example, here we would have like, uh, instead of hiding this edit button with, uh, this if else, we could have something like gloss tur, ball native hidden, uh, or non turbo native block. So how can we make this kind of synex work? Uh, first of all, we'll go here and I think we need to add the cons plugin, relevant plugin. And here inside plugins we will say plugin function, add variant. And here we will add add variant, uh, turbine native. And, uh, this variant will be added if, uh, in the HTML we have data TUR native. Uh, let's see if, uh, we can now apply this. So let's go to our application, H tml, ERB. We'll add the data TUR native to our HTML tag. Let's do it like this, for example. And here we can try rendering something conditionally. Let's say, uh, if, uh, we have TERMIN native set, then we will have, uh, another color of the background. So cloth, uh, turbo native background, whatever. Let's try on the web. Okay, it is broken. Uh, let's try here it is also broken. Uh, let's try, uh, setting this termin native to true or just setting this termin native only if, uh, we are actually opening a mobile application. Let's first try false and see if, uh, engine changes. Uh, let's try, um, like this, it is still here. Let's try removing data. tur native, and now it's not, uh, colored anymore. So let's say, uh, we'll add the data tur native only if, uh, we, if it is a termin native app. So, uh, uh, like we can say data and here it would be, uh, let's, uh, how can we name this condition? Let's name this, uh, data platform identifier. Okay? And let's set this, uh, platform identifier. So, uh, let's go to our application helper. And here we will, uh, say dev platform identifier. And here we will say turbo native. Uh, if this is a termin native app, okay, so, uh, it should be like just an empty data attribute, uh, on non-term native. And in terminate application, it should be data termin native. Let's refresh here. Let's, uh, refresh here. So you see, uh, in the termin native application, we have this style applied and in the non-term native application, if we don't have it, uh, applied looks, uh, quite good. Now let's try another helper, uh, for things that'll be available only on desktop applications. So let's say non-term native, non-term native. And here will have h TM L not data tur native. Let's try to see the folks. I'll just refresh this page. And, uh, let's go, let's say to our home index. And here I will say, so turbo native background green and, uh, non-term native background. Uh, yellow 500. Let's refresh, go into the homepage. So, uh, I might have made a mistake here, like if I got a coma, yes, if I forgot a coma, be refreshing. It doesn't seem to be applied. Let's see. It looks like I made a st syntax mistake here. So H TM L knot, and this should be in braces, something like this. Okay, yeah. So now it seems to be applied. So, uh, you see on the web, this has a yellow background. Let's go back to application HT ml. Here we have, uh, oh yeah, it was in our index. So in non-term native applications, we have this with a yellow background. And in Terminator applications we have it with a green background. Okay? So, uh, like this, you can have, uh, conditional, uh, conditional CSS with, uh, NT based on, uh, ti a mobile application or TI of application. Okay, I think that's it for this episode. So, uh, we worked on conditional, uh, rendering and conditional styles and, uh, page titles for the web and for mobile. Thanks for watching and see you in the next one. --- # Turbo Native 4. Better Authentication flow. Turbo Confirm. Turbo Replace #193 - URL: https://superails.com/posts/turbo-native-4-better-authentication-flow-turbo-confirm-turbo-replace - Published: 2024-09-08 - Duration: 11:53 - Access: free - Topics: iOS, outdated - Playlists: Hotwire Native - confirmation modal to sign_out: https://github.com/corsego/tinder/commit/03757800b050af752d672dc1d5cde9b18cfe4a6b - don't render devise views as modals: https://github.com/corsego/tinder/commit/cbb5bbf3de81f82a0598ca2e4ab6882d9151229c - navbar - do not stack navigation (breadcrumbs): https://github.com/corsego/tinder/commit/9e4b9a24241e11e3b98c02df6775e096351eec97 - generate devise views: https://github.com/corsego/tinder/commit/c8f9df797b9fddd2d71cff491fe44ef6c20a0ccd - devise forms - do not stack navigation: https://github.com/corsego/tinder/commit/ca4770a89b96226da69e56f26c9281b21418f374 - devise remember_me forever: https://github.com/corsego/tinder/commit/5cee2c20854205356446c524fd7eceb561f66342 ## Transcript Hello friends, let's continue building mobile applications, bound rails and Termin native. So previously we added the authentication of the device, and we can now log in, uh, on desktop and on mobile. And if you try to log in on mobile, let me click on log in. You see it opens up in a native model. Let me log in. So email, password, login. And here I get this, uh, double direct. Uh, so it's not for snappy, but I'm, yes, I'm logged in now. And now let's, uh, continue making the authe authentication flow work smoothly on the mobile. So first of all, I'm going to start by adding a confirmation model. When you want to log out, you know, we have this like, uh, kind of confirmation model with JS on the desktop. Let's try adding them. Uh, so I will go to my application layout where I have the sign out bottom, and I will add this data, confirm. Are you sure? Now let's refresh the page. I click on log out and you see we have this, uh, confirmation model to log out. I can cancel, I can actually click okay and it'll log me out. Okay, let me just log in back and delete work on mobile. So I've added it. I don't need to restart the whole application because I didn't do any changes in the iOS app. I did only change in the Rails app, but I will need to refresh the page. So let me refresh it. Okay, I will click on log out. And you see I have this native, uh, confirmation model. I can cancel, I can click okay, and it'll log me out. Looks good. Now, uh, let's actually save this change. So, um, uh, sign out confirmation model. Okay, now, uh, let's see. So at the moment when we click on log in, on register, opens up as a model, and we'll also have a model if we try to edit our account. Like if we go to this page, we will have, uh, this, uh, page open up as a model on mobile because our rules, let's open our path configuration controller. They say that all the slash new slash edit slash sign in and slash sign up pages should open up as a model. But, uh, realistically, uh, I would not open the device pages as models. I would, uh, open them as full page, uh, redirects. We did it previously just for the sake of example. So I'm going to remove this and let's, uh, once again, try to log in. Now I have a refresh. I click on login. So, uh, I think it has, uh, this page, uh, kind of cached, that's why is still opening up as a model. Let me try To, to restart our iOS application, click on this bottom replace, let's restart in our iOS application. Okay, um, maybe we can see here that it made a request to path configuration. You see the io s application made this request to path configuration. So to get, uh, this JSO and let's look and log in. So see it, uh, does open up as a fully new page. But, uh, let's now log in and have a look how the edit password looks. So login in and I go to edit my profile. And you see it opens up as a model and we still see all this navigation links. So this doesn't look, uh, very nice. Let's not open this one specific, uh, page as a model. So, uh, we don't want to have device pages open up as a model. Let's, uh, add an additional rule that slash user slash edit. The slash user slash edit does not open up as a model, but opens up as a a full page. So I'm actually going to go with this, uh, rule, and here I will have a pattern. It'll be, I will set it like this slash users slash edit. And the context will be not model, it'll be default. Okay? So it will say that like usually the slash new and slash edit, like create a new post edit. The post are going to open up in the model, uh, slash user slash edit will open up, not in a model, but uh, as a default page to direct. Let's, um, again, replace our application. We can observe here how we are going to make another request to this path configuration. Here. We made this request and let me log in once again. Now I'm going to edit my profile. And you see it is a full page direct. Okay, let's save our changes. So what we did is, uh, uh, device pages are not models. Okay, let's go, uh, back to our mobile application. And you see we have this kind of weird navigation. So we go anywhere and it's like, uh, navigation. We get this kind of breadcrumbs. So Go back, go back and, uh, what if in our kind of n links, we don't want to have, uh, the stack navigation. So to do this, we can, uh, go to our application external ERB, and on those links we can add the data. Uh, turbo, what was it? It was, uh, uh, turbo action replace. I will add this to the other links. Let's add it to all these links. Now I will refresh the page and now I will click on dashboard. If you don't have the stack navigation, I will click here. We don't have the stack navigation. I, uh, will, I can log out and it'll not have the stat navigation on login and register pages. So, uh, this kind of our NPA for now, maybe we'll later on we'll are going to make a native, uh, photo navigation. But for now it looks a bit, uh, better. Okay, let's save our changes. Uh, NPA do not stack, uh, navigation. Okay, looks, uh, quite good. Now let's uh, try signing out and, uh, I will click on login one scan. I just want to, yeah, I want to see the flow. So, so pay attention. Now I'm clicking on log in and uh, we had this kind of, uh, navigation. So we navigate to, to the next page. And you see if I click back, I'm kind of looking at the not logged in page. So it's not cool. I don't want to be able to access this page like this. Now of course, if I read, refresh it, it's going to look good, but I don't want to have the static navigation to a not logged in page. So I'm going to add the, this kind of data to action replace to the device forms for this. First of all, I'm going to, uh, generate the device views. So I will say rails generate device views. Okay, let's, uh, come these changes, they'll generate device views. Okay, now, uh, let's start the server once again and go to our, uh, sign in page, uh, for device. So, uh, sessions, new device, sessions new, and here we can try adding data. Turbo action. Replace. Let's try doing this. So, uh, going back to our application, I will log out, I will go to login page, click on login. And you see I, uh, don't have this, uh, navigate back button. So there is no other page that I can navigate to where I was not, uh, logged in. So this makes the kind of behavior snappier. I will do the same data tober action, replace for the other form for registrations or registrations new here like this. Now let's try creating in your account. Log out, register, sign up, and you see it looks, uh, a bit snappier. Okay, let's, uh, save our changes. So, uh, uh, do not stack navigation on device. Uh, sign up and sign in. Okay, looks, uh, quite good. Now, uh, let's see, what would we like to do, uh, next? Yes, there is actually a thing. So, uh, there's was this nice post, uh, on Twitter by Joe Otti, where he also suggests, uh, adding a device, remember me settings, uh, for, uh, our application for a longer period of time for mobile. 'cause like, you don't want to just log out the users on a mobile, uh, uh, randomly or all the time you want to give them sent in, of course, unless you're building a banking application. So, um, device remember me for two years and in user IB dev remember me as in this true, okay? I will, uh, actually copy the link to the street, uh, in the description of the come and I will say, uh, device remember me forever. If you are not using device, if you're using like your own authentication, you should store the user, not in the session, but in the cookie. It has to be a permanent cookie and you like have to remember it for 20 years or something. This is better than, uh, logging the user out and frustrating him all the time. Okay, looks, uh, quite good. So now we have a snap air log in logout experience and, uh, navigating around this links does not feel really weird anymore. That's it for this episode. See you in the next one. I. --- # Turbo Native 3. Turbo Navigator, Native Modals, PathConfiguration #192 - URL: https://superails.com/posts/turbo-native-3-turbo-navigator-native-modals-pathconfiguration - Published: 2024-09-07 - Duration: 14:41 - Access: free - Topics: iOS, outdated - Playlists: Hotwire Native Install Turbo Native the right way using turbo-navigator branch, open /new, /edit, /sign_up, /sign_in as a modal: https://github.com/corsego/tinder/commit/f669abacf4115276f0bd653fe0125b62d3534c50 Push Path Configuration JSON from Rails app to iOS app: https://github.com/corsego/tinder/commit/c3da8d266b931f952aa9395d0b07d11f02833abc ## Transcript Hello, friends. So in the previous episode, we created a new GitHub repository, and inside we created a new Rails application and a new iOS application. And then in the Rails application, we installed device for authentication. And in the iOS application we installed Turbo iOS so that we can, uh, run the Rails application not only in the the web like this, but also as an, uh, apple, uh, on mobile. And here we have this emulated, uh, uh, iPhone 15 that we opened in Xcode. And you see now we can, uh, sign in with the device, both in, uh, the web and in mobile. And now we're going to start adding some native mobile, uh, uh, interactions. So we're going to start with models. Now it's common for, uh, pages like, uh, new record or edit check code to open up and in models. And in our case, we're going to open up, let's say edit my account and, uh, pages like login and register in a model. So how can we do it? For this, we're going to use, uh, uh, let's call it the docs of turbio. And we have this path configuration. And here we have, uh, example of like all the pages that are slash new and slash edit should open up in a model. So we are going to make, uh, uh, some pages in our case log in and register open up in a model. Uh, to do this, first of all, we are going to install, uh, turbo iOS in the latest, most modern way. Now, at the moment, we have it running as the, uh, just main branch. So we have Turbo 7.1 runin. If we go to our application dependencies, we have the latest major version of Turbo iOS runin, and instead they're going to run this, uh, turbo Navigator branch. So it is a branch that will have many more behaviors, models included outta the box available, um, new Turbo native applications. So hopefully this branch will be merged with Main soon, but for now, I'm going to cope with the branch name. I'm going to, uh, put it here. So instead of next major version, I will say branch, the branch name. I'm clicking enter it is going to rebuild the dependencies. And going back to this branch, I'm going to files changed and I'm going to open the, um, docs, uh, quick start guide. I'll open in your tab. And here I have this, uh, updated syn delegate For the Quick Start. I will, um, remove all the content from here, put the updated syn delegate. I will, uh, say HTTP, local host 3000 Save, um, run application. So it's going to run, uh, the application with the, the latest code from, uh, this, uh, turbine Navigator brunch. And for the moment, everything should work just as, uh, before. So we can click around, but uh, still no models. So to make models work, let's, uh, open up docs for path configuration. And here we have this example, uh, JSO. And we are going to store this kind of code in, uh, our application in a file named Path configuration Do js. So, uh, I'm gonna go to file, uh, and you file Now, uh, this is the right way to create files in, uh, x code. You can just, uh, open in the, uh, another code editor, uh, and, uh, create a file because, uh, it'll not create correctly. So I create this file, create a file, okay. And if you look at the GI changes, you see, it tries to like project whatever that it registers in your file. Uh, path configuration, do, Jason, if you created this file in the, uh, via code, for example, or RubyMine, it wouldn't, uh, register the file like this. So that's why you need to kind of work with iOS projects in this, uh, iOS, uh, code editor. And anyway, we created this path configuration. We're going to copy the JS O and, uh, what else do we have? So we have this example code. I will try putting in scene delegate, but with a small fist. So we have Lead Path configuration and uh, we don't have this kind of session defined, so I'm going to put it inside our Tool navigator, part configuration, equal part configuration, save run application. It's run the app and it, uh, should read, uh, the kind of settings we set in the spot configuration here. So all the slash new and slash edit should be open the in a model, okay? If you click on login and register and you see it's still not, it's a model why it, so, because if you go to our Rails app, you see the slash sign in and slash sign up. So let's, uh, try login in and edit our profile. I will go to, what was my login? Okay, I will go to my profile. And, uh, it still doesn't open as a model. Let's see the part here. So it is user slash edit. So I would expect this to open as a model. Anyway, let's try, yeah, there, there is a reason, because here it should be not presentation, it should be context. So here, if I look for the word context in this, uh, pull request, you see the replace presentation with context. So here I will have context model, and uh, yeah, we have actually also an example for slash sign in. So let's add, uh, context model for sign in, or we can just, uh, move sign in here. So comma slash sign in, uh, and sign up. Okay? So slash new slash edit slash sign in and slash sign up should open in the context of a model. Now, I will run and scan. Okay, so you see I ruined the application and it kind of logged me out. But you see, hey, we opened the sign in and send up in a model. So we have basically installed, uh, uh, this capability to have some pages open up as models in our application. Now, if you go to dashboard, uh, yeah, homepage works outside of model. If I click on dashboard, it tries to open the dashboard, but I'm not logged in. So it redirects me to sign up and it opens it as a model. So looks, uh, quite cool. Now, uh, let's, uh, save our changes. Uh, so use Turbo, navigate a branch, um, path configuration for models. Okay? And, uh, now we're going to actually make this, uh, path configuration file. We will read this path configuration file not from our iOS application, but we will serve it through our Rails applications. So if you want to change something in this particular integration, you don't have to resubmit your application to App Store. You can just make this change, uh, inside your Rails application and push it, uh, as a URL instead of this, uh, static file to your iOS application. So, um, do this. We are going to create, uh, an endpoint and, uh, endpoint inside our Rails application. Let's, uh, open our Rails application and, uh, I'm going to create a new controllers. So let's go to controllers. Inside I will have, uh, a folder named Turbo. Uh, inside I will have a folder named iOS, and inside I will have, uh, um, a file named path on the integration controller. Uh, you don't have to have, uh, all these, uh, uh, nesting. You could just have like path configuration controller or iOS controller. But this way I think it's a good way to describe everything. So I will say Class Turbo, the parent folder, iOS path configuration hurts from application controller, dev show. Uh, okay, it's a wonderful auto complete. And, uh, they're going to have this kind of, uh, json inside. So, uh, uh, we have rules, okay? And, and, and, and, uh, I will also ensure that we don't require authentication to access this. So skip, uh, before action, uh, authenticate user just in case if at some point of time you'll put, uh, before action authenticate user inside your application controller. Okay? So, uh, I have this Jason that we can serve through our rails, uh, application. I need to also add the root for this. So, uh, I will, uh, go and say, uh, name space, turbo name space, I get path integration, uh, comma, tool path, configuration show. Uh, or I can actually just say, uh, resource, resource, uh, path configuration only show. Okay, let's try navigate into this, uh, inside our web browser. So slash turbo iOS, uh, slash path configuration. Do json, uh, okay, I initialized. Let's search for path configuration. Do json, maybe I made a typo somewhere. Okay, an initialized constant. It might be because here I have this what to generate path configuration where it has to be as plural for resources. So it's path configurations, controller. Let's see if it works. Okay, uh, now I have some kind of type of here, maybe too many, uh, braces. Um, let's see. Maybe we can simplify if we don't need these settings. Okay, maybe like this refresh. Okay, uh, has not been defined because we did not set before action authenticate user in the application controller. Let me comment it out. Okay. And we have this j uh, served via this URL, and now we need to serve it in our, uh, send delegate. So here we are reading, uh, this path configuration from this local file. Let's actually delete the local file, move to trash. And, uh, here, instead of reading from a file, we are going to read from, uh, our URL. So the source is going to be, not the file, but the server. So I will say server. And here I'm going to have, uh, uh, they hold URLI think let's try having this URL. Okay. And, uh, I get another error. Cannot connect. Okay, cannot convert. So maybe I should say it differently. I will say, uh, server. I will have root URL. We have this, uh, root URL and I will say dot append. And we are going to append the path, and the path is going to be, uh, not the whole path, just, uh, turbo iOS configuration. Okay, let's save. Okay, I'm not getting an error. I think I will, uh, rerun the application and see if, uh, it works. So I'm going to log in. It opens in the model. Now let's, uh, go back. I will, uh, command this lines out. I will just remove them. I will, uh, rerun the application and I expect, uh, the login and, uh, register pages not to open up as a model. So I click here and you see there's a full page load. So see from our Rails application, we kind of send commands to our iOS application. And I think it's really cool. And, uh, this way you would have to submit your application fewer times to the app store for, uh, review if you want to make some changes in your app. So, um, uh, path configuration from your Rails app. Okay, I think it's, uh, really cool. Now we can open up some pages in our application as, uh, models. --- # Turbo Native 2. Create new Turbo Native app. Basic navigation and authentication #191 - URL: https://superails.com/posts/turbo-native-2-create-new-turbo-native-app-basic-navigation-and-authentication - Published: 2024-09-07 - Duration: 19:13 - Access: free - Topics: iOS, outdated - Playlists: Hotwire Native Generate new Rails app: https://github.com/corsego/tinder/commit/f33d77a6beab8f8ce958ce97aa102ec442df2f70 Generate iOS app: https://github.com/corsego/tinder/commit/576644e9eb9e15f7b4b534d83d6bfbc8cf01f6ad iOS - install Turbo Native: https://github.com/corsego/tinder/commit/cff231a8eb660d36ff3facbfb003cad220f33e2d Rails - static pages: https://github.com/corsego/tinder/commit/966ea101fff82a9b30f801c32b2a8cdfc41727e5 iOS - use localhost: https://github.com/corsego/tinder/commit/e13daaa466b886c63378dca39478bbfbdf4793e7 Rails - install devise for authentication: https://github.com/corsego/tinder/commit/aab60f384a2b31bd84756242d30444ca2430c00b ## Transcript Hello friends. So let's talk about building mobile applications with turbine rails, using Turbo Native. In the previous episode, we had a look at the Turbo iOS Library where we had a demo application that we managed to run using the X code. So we opened this application, clicked the the run button, and it opened us and emulated the iPhone where we could scroll this, uh, application that is available at this, uh, URL and see how, uh, web application can be wrapped into a mobile application. And now let's start building a new Ribbon Rail application. And alongside we're going to build an iOS application. We, they're going to see their interdependencies, they're going to build them together and, uh, make the application work well on the web and work well as a mobile application. And as an example, I was thinking about building an application like Tinder. Tinder is a classic example of a very snappy, good mobile application that has, uh, uh, mobile navigation, that has swipes and so on. So let's try building this kind of application that work both on the web and on, uh, mobile. They're going to start by creating a new application. Let's start by creating actually a new GitHub repository. Let's name it, uh, Tinder. Uh, okay, create repository. Now I'm going to clone this repository. I will, uh, say GI clone git@github.com gi Okay, open this repository. And inside this repository, we are going to create a Rails application and an iOS application. Now, usually you would, uh, create the Rails application in the root folder, but uh, in this case, we're going to have, uh, a root folder named Tinder. And inside we're going to have a Rails app application, and we are going to have, uh, an uh, io s application. This way we can track the GI changes in the two applications, in the iOS application and in the Rails application at the same time and see the dependencies. So let's start by creating a Rails application. I will say, um, rails, uh, uh, new in the, uh, rails, uh, database, PostgreSQL, uh, CSS Tailwind. Okay, let's create the application. Okay, I have generated the Rails application. Now let's, uh, get type GI status and, uh, we want to commit everything. So we have created a Rails application inside this folder, gi, uh, add all. Um, so I couldn't, uh, add, uh, that Rails repository because Rails repository itself is a Git repository, and I'm trying to push a Git repository inside Git repository. So I need to make the rails up, not a Git repository. I will open this, uh, repository and I will say RM remove Git. Now, going back, um, okay, going back and now I can say Git, add all Git status. So now I managed to add the rail repository, Git comt, uh, message, uh, generate Rails app. Okay. Uh, so let's do GI Push and have a look at our reposition. Now, here is a new, uh, Git repository. And you see inside it we have a Rails app. So, uh, usually I'm used to like having the Rails app as the root folder of, uh, Git, but in this case, it's going to be a child folder. And I'll generate a new iOS app to do this. I will, uh, go to file New Inside Xcode and, uh, new project, a new app. I will name it Tinder io s uh, organization, identify com Super Rails. Uh, yeah, that's fine. I could have Tinder or whatever else as the kind of sub domain, but, uh, yeah, that's fine. I just have com Super Rails. I'm not changing any other settings. Uh, clicking on Next, and I need to slide the folder. So, um, okay, why don't they see folders? Strange. Yeah, I just clicked, uh, back and forth and now it's broken. So let's open this, uh, folder, uh, Tinder. And inside this Tinder folder, we're gonna create the Tinder iOS, uh, application. Okay, here we have generated this new application. Let's, uh, type GI status. You see, we have generated this new, uh, application. Let's actually add a GI Ignore. So, uh, in the core, I'm going to say Kit Ignore. And here I will type, uh, uh, iOS, uh, swift ui, and I'm going to have a more Gi Ignore. Inside Tin iOS, you see, we have a default GI Ignore in the Rails repository. It's ignoring the stuff for Rails. I'm going to ignore this, uh, uh, globally, but I could maybe also do it inside Tinder, iOS. And here I'm going to have another GI Ignore dot, uh, git ignore. Here I will say XC user data. So this way we're gonna have a bit less, uh, spam. Now, let's again type get status. Okay, now there's, uh, a bit less visible. Okay? Uh, so we have generated this new iOS app. Let's, uh, save our changes GI at all. GI com message generate iOS app. Okay, GI push. Let's, uh, have a look. So here, inside this Tinder, we have Tinder, iOS, and Tinder Rails. Let's try running the iOS application. And let's try running the Rails application. So I will run Tinder, iOS, and, uh, as we haven't done any changes, it should open a new iOS with application. It should be like a blank, uh, screen. Well, this is basically it. And we can try running the Rails, uh, application. Let's say Bin Dev. Oh, yeah, we need to open the Rails folder. In the Rails folder. I will say Bin Dev. Okay, let's create a database and run migrations, okay? Have, have rails running. Looks, uh, uh, good. Now I want to run Rails, uh, in the Tinder iOS, uh, folder, the, uh, emulated application that is currently empty. So, uh, they're going to make this, uh, Terminator application. Now, to do this, I'm going to go to Turbo iOS, and, um, go to the, uh, docs, uh, quick Start Guide. So, uh, inside this, uh, iOS application, I'm going to install TUR native, uh, turbo iOS. Specifically to do this, I'm going to go to the application project. So here, I'm going to project package dependencies, add a dependency. Now, uh, I already have it in recently used, but you might need to have the full URL. So this would be this URL, you would put it here. You would find this repository. And, uh, by default, yeah, let's use the latest version. So add package, uh, ensure that there is not none, but this Tinder iOS application that you've created as the target at package. Okay? And, uh, now I'm going to go to this SYN delegate. You see, as it is said in the ClickStart guide, you should replace everything in the syn delegate with the this code. So I will replace it, okay? And now I will run the application. Okay? And we have the quick start guide running, uh, in our new, uh, IRS application. Let's save our changes, get status, get add all, um, get commit message, um, iOS, uh, install Turbo native. Okay? Now let's, uh, run our Rails application, our local host, uh, here. So I will say Bin Dev in the Rails application. And, uh, let's ensure that we have it running, uh, locally. Yeah, here it is. It works. And now in the Xcode, you see, we're going to replace this, uh, URL with the, uh, local host. So let's say HTP, local host 3000. Okay? Saving, uh, rerun the application. So you have to like rerun the application each, each time. You make some changes here so that it takes effect. Let's see how Rails Console, if anything's happening here. Um, I think it's still, uh, in the process of rendering the application, it can take, uh, a vial rendering the application for the first time. Okay, yeah, basically now we have, uh, an empty screen running, but now it's trying to run local host 3000. So let's add some, uh, screens to our application. I'm, uh, going back to my Rails application. I will, uh, add the couple of static pages, let's say rails, uh, generate controller, uh, a static. Um, it'll have an index page and a dashboard page. Let's open our code editor. And here I'm going to add a route page. So route will be to static, uh, index, and we're going to have a path to the dashboard. So get the dashboard to static dashboard, okay? Uh, let's add the this to all navigation. So going to application, h, mel, e, r, b. And here I will say equals link to, uh, home route path and link to dashboard, dashboard path. Let's try running this in our rails. Application bin Dev. Okay, something didn't work. Let's see why. Okay, I made a mistake in my roots. Get dashboard to static. Uh, dashboard. Yeah, it should be like this. Now run bin Dev again. Okay, so here we have static index by default, and we can go to the dashboard. Uh, let's see if it works in our iOS application. So I'm going to rerun the iOS application. Once again, let's have a look at our logs in the Rails app is the application from iOS making request to the Rails application while it's trying to launch. You see it made a request. And here we have static index. So we have homepage and dashboard, and you see the navigation gets stacked on top of each other. So yes, it, uh, kind of works. Let's, uh, save our changes. But before, let's try running Rails test. So the autogenerated tests should be failing now because we updated our route. So let's go to static controller test, and here we'll have, get the root URL success and get dashboard for URL ET response success. Okay, so, uh, now we managed to run local host, uh, uh, on our Rails application on mobile. This is really cool. Let's, uh, save our changes. So, uh, I will, uh, actually save changes separately for the Rails app and for the iOS app in this case. So here I will say, uh, rails generate controller, static index and dashboard, and, uh, in tin the iOS, I will say that point, uh, URL to, uh, local host, but not to a random URL. So, uh, iOS use local host. Okay, uh, looks good. So we managed to run our rails, uh, on, on New Rails application on, uh, iOS. And we have a couple of pages. Now, let's add authentication with device and see if, uh, it works out of the box. So I will say, uh, bundle add device, again, inside the rails folder, rails generate device install. Uh, I'm going to follow this ClickStart guide. So put this line into development. rb, uh, we already have a root path. We're going to add the, this alert and notice, we'll put them in application, h ml, ERB. Then we will add the rails. Generate device user, make the user model authentic rails, the beam migrate. Okay, let's, uh, run the server. Oh, yeah, and let's, uh, require authentication to see the dashboard. So let's open static controller. And here I will say before action authenticate user only for the dashboard. And index doesn't require authentication. Let's see how it works on the web. So here I'm on the homepage. I click on go to dashboard, and you see it requires a sign in and it opens up the signin form. Actually, this design is quite ugly. I'm going to go to application HTML and, uh, set flex, uh, to flex, uh, column. Okay. This, uh, design looks a bit, uh, better for the closed design that we can have to defaults. Um, okay, so, uh, we have homepage and dashboard that requires authentication. Let's, uh, allow the user to sign in. So, um, sign up, sign The user signed up, and he was directed with the dashboard page. So I send up user can visit both homepage and dashboard page. Let's now allow the user to, uh, see his username, so his email address, change his password and, uh, log out. So, uh, to do this, I'm actually going to copy it from some where I've done it before from my blog. Uh, I might have something in this blog post. So I will add these, uh, links here. So, uh, if user is sent in, show his, uh, email and path to edit his, uh, uh, email password to delete his profile, uh, button to log out. I don't think we need data to false anymore. Um, advise, login, or register. And, uh, yeah, let's, uh, run our application again. So here we see the username. It leads me to this page. I can log out. Okay, so here again, I have homepage, dashboard, login, and register. Let's try to see if it works on, uh, mobile. Actually, I will just try to refresh the page. Okay. And you see, I didn't have to, uh, rerun the mobile application because I already, well, I didn't do any changes in the mobile app. I did changes only in the Rails app now. So, okay, let me try using the same, uh, uh, email and password. Uh, I think it was yam.com. Uh, can I type yam.com? Yaro at.com, uh, login. And you see, I managed to login. It worked wonderful. I, I'm logged in. I can, uh, see the dashboard homepage, uh, go to Regi, uh, change my account and I can click on Log Out. Let's try clicking on logout. And, uh, it worked. So, uh, wonderful device seems to work outta the box. Uh, with, uh, Terminator. Let's, uh, save our changes. So what have we done? Uh, we have, uh, uh, installed device in Rails, install device for authentication. Okay, looks, uh, quite fine. --- # I'm launching Trezy Classifier on ProductHunt! API tool to analyse bank transactions #190 - URL: https://superails.com/posts/i-m-launching-trezy-classifier-on-producthunt-api-tool-to-analyse-bank-transactions - Published: 2024-09-05 - Duration: 10:30 - Access: free - Topics: api The API tool: https://classify.trezy.io I built a big part of this app for a company I've been working with. The app allows you to analyse and categorise bank transactions, and companies behind them. Super useful for fintech companies! It's one of the coolest projects I've worked on! The backend logic is 🧠🧠🧠🧠 Today, we launch on ProductHunt. Episode source code: https://github.com/corsego/190-trezy-classifier/commit/ddc6f1ef38a6f777a635dda9b12a163dcb072df1 ## Transcript Hello friends, I have a very exciting day today. An app that I have built is launching on Product Hunt. It's called Classify by tr. Classify is the name of the app, and TR is the company. So feel free to support it. And basically the app allows you to categorize, to analyze bank transactions. So, uh, if you're building a FinTech company, if you export bank transactions, uh, you want to make a sense of this information. And often this innovation is not very readable. You can get like a description of a transaction and amount. Often the export format from some banks could look more, less like this. Like you'd have a date, description, amount, currency, and that's it. But, uh, really if you analyze the transactions, you want to know the category. Like is it for rent? Is it for subscriptions? Is it for groceries? You want to know the information about the vendor, like is it for R McDonald's or is it from Uber? To better analyze the transactions. And this is what the app basically allows you to do. It is an API that lets you to send information about your transaction. It'll analyze this information and give you back a response with the information about the company that, uh, uh, send you the money or to whom you send the money and the economic activity and much more. So let's try to create account and see how it works and see how you can use it in a Rails application. 'cause here I have a Rails application that I imported this list of transactions and I want to add the, the information about like the category of the transaction, the, uh, company name that, uh, sent or receive this, uh, transaction, the logos and so on. So first of all, I will go and, um, sign up. Okay, signing up. And here I have created an account. I have 20,000 free credits, so 20,000 transactions that I can categorize for free. And I will go to the playground here. Okay, I need to get an API key. Let's create an API key. Here it is, uh, I will go back to the playground and uh, here is an example. So I can, uh, uh, add information about a transaction, like label amount, uh, control language, uh, click on enrich, it'll make, uh, it's, uh, analysis and give this kind of information. So here we have this, uh, uh, transition category, the company. So G seven is like a taxi service in France. The economic activity of this company, uh, logo, URL, in this case, if we didn't find the U logo, URL, but for most companies it would find the logo, URL. And we even have the website, yeah, this taxi service. So we know that, uh, this transaction was sent to G seven taxi. And you see we don't even have G seven anyway in the transaction label. But, uh, magically the tool managed to figure out that this specific, uh, thing is usually written in transactions to this, uh, uh, company. Okay, so let's make it work in the Ruben Rails application. So We'll are going to get this kind of information as an answer and we need to store it somewhere. So let's add a, a column that would be a classifier payload, and it would be adjacent column. We're going to keep this as Jason in our application. So, uh, let's say rails generate migration, add the classifier, payload two transactions. Let's open our migration. And, uh, we're going to store this information in JSON B. It's, uh, usually better and more performance than json. It is not accessible in the ES as of yet, but, uh, it is available this format in the Postgres scale. Okay, rails be migrate. Let's now display this classifier payload in our transactions list here and here. Okay, well, at the moment it is obviously empty. And uh, we, they're going to get this information from, uh, our API. So we're going to make this kind of, uh, request to the trace classifier, API for each of our transactions. We're going to get this response and they're going to store it in our, uh, application database. So let's create a new job that they're going to run for each transaction. I will name the job classify rails, generate job classifying. Let's open the classify job. They're going to run on a transaction. And how do we adopt this, uh, CRL request to our application? Well, this is actually quite, uh, easy. We can, um, uh, use JI PT or GitHub co palate for this. So I will say, um, I want to adapt this serial request to work with rails and fer day per day is, uh, a gem making the HTP request with rails. Okay, yeah, let's add the gem Faraday bundle, add fer day. And uh, here we have this example. So response, we have our API key that we received here. Uh, then they're going to have labeled will be transaction description, because here in the database we keep it as description amount, transaction amount, uh, country will be fr for all of them, and language will be French for all of them. So we are going to get this response and we are gonna say transaction, do update, classify a payload. Uh, we don't need to pause, just response.party. Let's try running this on one of our transactions. I'll open the rails Console transaction, yeah, equals transaction. First, let's have a look at this transaction. Here it is. Uh, I'm going to run this, uh, job on the transaction. So I will say, uh, classify job, perform now on the transaction. Okay, it performed. Let's have a look at the transaction again, it has the specified payload. Let's have a closer look dot ified payload. And here we see that, uh, the category is EMP 0 7 0 0. That is, uh, sellers and wages. We now know that this was a payment for a salary of an employee, and, uh, potentially we can get other information like, uh, the company and so on. Uh, for this one specifically, we didn't find the company. Maybe there was not enough information. Okay, let's, uh, have a look at our application. If, uh, this ified payload was saved, if we can have a look at it. Yes. So for the first transaction, we have this classified payload. Okay, now let's try categorizing all the transactions in our database so that we can run this, uh, job on, uh, each transaction, let's say transaction, all each, uh, do yes, do transaction, uh, trans and will run classified job dot perform Now on each transaction. And, and, and this is supposed to run, uh, the classification on each of our transactions. This can take, uh, a vial. Yes, you see it's happening. And if go back to Tracy, we can see, you see there are fewer and fewer free credits on our account. We have already categorized 15 in our 19 transactions, and we can have a look at them here. So, uh, you see for some of them, we even find the logos of the companies. Like, we know that this company, uh, has this website. It has, uh, uh, a logo. Let's try displaying the logos of, uh, the companies that they found. Um, so, uh, let's have, uh, another field for logo, company logo, and how can we get this? So, uh, let's see an example. Uh, and let's Again ask, uh, GitHub copilot. So transaction payload equals this. How can I, uh, display logo with image deck? This is going to help me pause this, uh, j payload and display the logos. In the meantime, in the console, you CVS still analyzing the transactions. It usually takes I think, half a second to analyze fun. You have already analyzed around 200 of them. Okay, let's see. So here we have, uh, this example code. Let's paste here. And let's see. Yeah, so you see it found a lot of, uh, company logos, looks, uh, really nice. So you would have the information about the, uh, economic activity, the, yeah, the category, the information about the companies that, uh, received all that sent you, uh, these, uh, bank transactions. And, uh, yeah, that's basically it. So, uh, this is an API for categorizing bank, uh, transactions and making sense, uh, out of them. Great for Fin the Companies. Again, feel free to support, uh, my project on, uh, product Hunt. Thanks for watching and see you in the next one. --- # Use JSON to store and display company information | SupeRails #189 - URL: https://superails.com/posts/use-json-to-store-and-display-company-information-superails-189 - Published: 2024-09-01 - Duration: 17:44 - Access: free - Playlists: International company database Bravo, you made it to the final episode in the series, dear friend! To store the whole company information and make it searchable and readable, we should use JSON, not a TEXT field. The easiest way to store all the company data in a JSON will be to make an additional call to the company registry API after the company is created, and store the response in the JSON column in our database. By the way, if you use Postgresq, I recommend JSONB (it has better performance). Otherwise use JSON. Episode source code: https://github.com/corsego/181-fr-company-search/commit/29347e1f16ba69dd4facc2241cf88ce4d66bbdc5 ## Transcript Hello friends. So previously we created a global company search engine that allows us to find company information across different legislations and, uh, have basically have a form to create a company in a Rails app. And, uh, to find a real company from the database, uh, from national database, I would be able to select a country code. I can, uh, search for a company by the name of the tax registration number in the country that's search in France, for example. Find this company in the name of this person. I click on my selected result. It auto replaces this info field with the, uh, registration number I save. And here I know that, uh, I saved a legit company with a legit registration number in my database. But what if I want to not just, uh, save the registration number, I want to save, uh, all the company information and be able to pass it. Well, the simplest thing I could do with, uh, my current approach is just, uh, uh, auto complete this info field, not just with the company registration number, but with, uh, the whole information. So let's try again. I will again search for France. Ian, click on it. And you see it, uh, kind of looks like, uh, uh, uh, ruby hash. I click on Create company, and here I have this whole information, the whole payload saved in my database. How can I pause it? Let me go to the company partial, and let's say I want to get company info. And inside it, I want to get the, uh, complete name of the company. So non-complete. Normally this should work, but it doesn't. Uh, oh yeah, I'm doing it on the country code. Of course it doesn't work. So I do it on this info and you see it doesn't work. It just finds, uh, this specific, uh, element. I will make it, uh, not exactly this element. And you see it doesn't find it. Okay. So, uh, this kind of search doesn't work. Okay, let's try turn it to Jason. Let's say JS Pass company info and find noncompliant. It doesn't work because we have this UNP token error, most likely because, uh, JPA doesn't work, uh, with, uh, this, uh, symbol. So I would need to say company info G sub. So I'm going to replace this Ruby symbol with, uh, this symbol. Let's search now and it still doesn't work. So, uh, the problem is that, uh, we get a ruby object as a result when we create a company, when we search for a company, let's, uh, search for a company. You see it is kind of, uh, a ruby hash. Then we send this H as HTML string into our controller, the store it as a string and it becomes very hard to pass it. We want To save it, uh, as, uh, js o straight away, not in text field. That's why JSON format kind of exists. So let's try storing the estimation as JSO. Uh, let's rate migration rails, generate migration, add the payload, uh, two, uh, companies. I'm going to add, uh, JSON payload column two. Companies add column, uh, to the company's table named payload type js O. And also I'm going to rename this info field into a registration number field because, uh, basically we are store currently the registration number of the company. So, uh, I will say rename column in companies table. Uh, initially it's named info and now it's going to be made the registration number rails DB migrate. Okay, duplicate name payload, maybe already did this kind of manipulation. Let's try once scan, I will just drop my database rails. DB drop db create db migrate. Okay, let's have a look at our schema. So have the registration number, the payload. Let's just replace, uh, the word info with, uh, registration number across our application here in the form. Uh, anywhere else in the controller info replaced with the registration number. Okay, so, uh, let's just see if the app works. Now with this new migration, I'm going to go to the list of companies, create a company. We have this registration number that we cannot manually edit. I'm going to find the, the company, okay? Create a company, okay? And define method info. Yeah, because, uh, info whether we still have this info. Yeah. Validates info, validates registration number, presence through, let's try again. Okay. Uh, I created the company. The registration number seems to be empty. Let's have a look. Rails Console company. Oh, okay. Yeah, it is present, but uh, it doesn't look very nice. Let's have a look at our company registration number. Yeah, we have all this text. So let's go back to our search front and just store again the registration number. I will destroy this company, create a new one up save. Okay, we have the company's registration number and country code, and now we want to, uh, store the payload. So all the company information as Jason. And I think the best way to do it, considering that, uh, this form basically currently works to find the registration number of the company in a, a specific country to know that it's legit. So we already do this, and now we're just going to, uh, make a separate call to the API to update the payload, uh, with the JSO information for the company. So here I will say after great commit, uh, update, uh, payload. Now I will say dev update payload. And uh, I'm going to make one of these two API calls. So I will say if, uh, uh, country code equals France, I'm going to say company dot search France, and the query is going to be the registration number. Here, I will have the results. So results equals company search France by the registration number. Let's try doing it first in the console. Um, company equals company do first. Uh, then I will say company search France based on the company do registration number. And this is going to be the results. Okay? Results. Okay, here we have the results. Inside the results, uh, I have this hash of results, so of elements in the search, and here I will have results. Results, okay? And here it is, uh, an array of results and let's say count dot count, okay? So we searched exactly by the company registration number. So, uh, Uh, they have only one result. And it is, it is very good. So, uh, uh, if results, results equals one, then I'm going to update the payload. Self update payload with results. Results the dot first. Okay? So when the company is created, we make another API call to update the payload with, uh, this information. Let's, uh, quit. Let's actually just try running, update payload on a company. Company equals company. First company. You see the payload is currently nil. I will say company do update payload company. Okay? It's still nil. Uh, advise it nil. It means something may went wrong here. Uh, let's try running again. Uh, company do registration number results. Inside results, uh, I will get results once again. First, okay, this looks, uh, good and uh, I will say company update payload, the results first company. Okay, now the payload has been, uh, updated. So this seems to look well, let's get the console and try creating a new company. I'll go delete this one. New company again, b Create company. Let's, uh, see if, uh, we have the payload updated. So company dot payload, okay, it is empty. This is, uh, not good. Why is it empty? Maybe let's try calling, uh, self registration number. Self country code, just in the case again, delete company, create company payload, uh, is still empty. So let's put a binding here and see what's happening. Binding B gonna create another company. Okay? Uh, self country is France. Let's run this. Let's run self. Okay, it didn't fail for some reason. Let's try to see line by line. So we get the results. Let's see if there is just one result. No, there seems to be more than one result. Let's see. Results. Results dot count. Oh yeah, I should have put the dot count here. Sure, yeah, I didn't count the results. Yeah, this kind of dynamic mistakes can take some time. Okay, let's try create another company. Create company. It's run the a p call. Oh, no, I didn't know yet. Yeah, I forgot to remove the bin. But yeah, you see the payload has been populated. And the final, let's check if, uh, uh, we can pass this payload as Jason. So go into the company payload, CRM. Okay. And we get a company registration number. So looks, uh, good. And now we can just, uh, uh, display it in a nice way. So I will say each do key and value. Then I will have equals key equals value. And, and I will put them all in a separate line. So div on the start, diviv on the end, uh, I will make the key bold refresh. And you have all the company information, uh, displayed in a nice way. So we actually store the company registration number as a Columbia database, the country code, and we have the payload that can be different for each company in each country. And we just display everything we found looks good. Let's make sure that it works not only for French companies, but for UK companies. So here I will make a very similar thing. Uh, I'll make it Elif, uh, UK and for uk we just have results about, uh, items. And here search, uh, uk. Now of course you can make this, uh, you can optimize this, uh, if you want, but it's not for this example. So, uh, let's find another company in the the UK database. Okay? Company payload, uh, is empty for some kind of company. That's why I get an error. So, uh, here I will say at each, okay, so if company doesn't have payload, we don't, uh, try to display the past version. Let's, uh, remove the company's so it is clear to see. Okay, now it's create a company in uk, Skippy, create company. And you see we have, uh, all the information, uh, in the payload stored as Jason, so we can easily pass this js and display it in a nice format. That's it. That's, uh, why I would use Js to store, uh, and pass, uh, hash like data and not just text field. Also, another small thing I would like to update, uh, going to our company search controller. So, uh, I wouldn't, uh, search for companies only if there are more than three symbols because there can theoretically maybe big companies with, uh, fewer symbols in the name. So, uh, I'm going to remove this condition. And also instead of having headers accept the text to stream, there is a simpler way we can just say, uh, that response kind will be to stream, okay? And this, uh, should be now, uh, possible. Of course, we can also try like a syn and evade. Okay, let's see if it works. Now, uh, go back to companies at a new company, uh, French create company, and, uh, it worked. And, uh, we have all the information about this French company. So that's it. Here we conclude our series of building a globally company search engine. This episode was just like making improvements, making sure work. And, uh, I, this year all the best, uh, in, uh, real company information in your applications. Goodbye. --- # Build a international company search engine | SupeRails #188 - URL: https://superails.com/posts/build-a-international-company-search-engine-superails-188 - Published: 2024-08-31 - Duration: 15:37 - Access: free - Playlists: International company database In #181 & #187 we searched French & UK company registries via API. Now let's add a country-select field, so that users can search for comanies across legislations in one form! This basically turns our app intro an international company search engine! Episode source code: https://github.com/corsego/181-fr-company-search/commit/af49b372ed22928c3d8394b059ae209d88766cce StimulusJS Display or hide HMTL based on field input https://blog.corsego.com/stimulus-display-show-hide-div-based-on-value (#81) ## Transcript Hello friends. So previously we talked about, uh, searching the French national database of companies via API and the British National Database of companies via API and this we build this basic application where you can, uh, search for a company. So here I'm searching for British Company. I click on it and it, uh, populates this information field with the company name, uh, registration number and address. And we also made it work for France before. And now we are going to make it so that it works, uh, for different countries. Let's say you have, uh, an application where you want to create, uh, companies from different, uh, countries and you want to get their legal information. So here is the example of application functionality that they're going to build. Here I have a counter select. Let's say I'm gonna select France. And here I have the search in the French company database. So, uh, let's search for this company, Rezi. Here we have this company. It auto fills this, uh, registration number. Now I'm going to search in the British Company Registry. That's search for the same, uh, company name. You see, the results are different 'cause we're searching in another company database and you have saved the company, uh, registration number for the British company. So here I have, uh, let's put, and here, so here I have the company registration number, this, and we noted the legislation is, uh, great Britain. So let's build this kind of functionality into our basic application where we have, uh, the search field and let's, uh, have a selector on what database research in the French or the British. So, uh, first of all, let's uh, go to our form, to this form and we're going to have, uh, let's say two different search fields. So, uh, one is going to be search, uh, front, and uh, that will be search, uh, uh, create rhythm. Okay? So here we have two search fields, and at the moment, both of them make, uh, the request to company search controller and it makes a request to slash company slash search. In our application going to companies controller, we have this company search. And you see here the manual set company search for UK or for France. Yeah, we have used uk. Let's be consistent. Have also used, not gb, not Great Britain, but United Kingdom. Okay? So, uh, we want to search, uh, different databases based on what we submit. If we search here, we want to search the French database. If we search here, we want to search the UK database. Uh, to do this, we can add the, another data attribute that they're going to send in the, the Pres. So based on the contract that we send in the pres, they're going to search, uh, in different database. Let's say, uh, uh, country here will be France, and here country will be, uh, great Britain or U let's say uk. Okay? So, uh, if we can access this data country in, uh, our company, search controller, uh, how can we do it? We would also append it here into the query. So let's say, uh, let, uh, country code or let's say let country equals event target dataset, uh, country. Yeah, we have undefined like this and we are going to add country to our search, uh, query. So we'll say add the country equals country. Let's see. We have country in the pres here. Uh, let's, uh, put a binding, binding b I'm going to refresh the page to Alize, the seamless controller. And let's search. So, hello. Okay, we previously put, uh, here to search only when there are modern three characters. So I had to put three characters, let's say pres. And in the premise we have the country fr continue. Now let's, uh, search into other field. We hit the bind again. Let's sees in the premise we have country uk. Okay? So, uh, based on the country, we can make different search cures. Country equals Ram's country. And uh, we'll say if country equals France, we will get the results for search France. Elif country equals, uh, create Britain, no, uk. They're gonna search in the UK company database. Okay, let's, uh, continue. Uh, I'll refresh the page and let's search in French database. I'm searching for Tracy. Okay, I got this. Yeah, I got an error with pers. Okay, let's search again. Okay, I get this undefined method each. Let's search uk, UK works. So, uh, the France doesn't work because, uh, here in search to stream we need to have, uh, a different, yeah, we get a different payload for UK and for France. So for France it'll be not items, but results. And uh, the result, uh, attributes would also be different. So let's create a separate, uh, response for France. Let's say search France to stream DOB and search uk. So search UK does need any, need, any changes, search France will have results. And let's have a look at the result. Uh, yeah, vice versa, UK looks good and France is currently broken. Okay? So here we'll also say that they're going to respond to different tablets. So, uh, they're going to render the template, uh, render template, um, search France and here will have search, uh, uk. Let's see if it works for uk. Now I will refresh. Okay, it, uh, isn't Fcon, um, uh, maybe I forgot a syntax for rendering the right template. Um, what for it run the template, missing template, search uk, uh, maybe slash companies. Okay, now it's V for UK and for France, uh, it's also V. Okay. So, uh, let's just have the right information. For France, we will need result, uh, CRM, that is the registration number, the full name, and, uh, likely the address. Uh, let's have a look at the Sears. The address will be inside Sears. Yeah, address. Okay, like this. Okay, let's refresh. So now we can search in two different databases. Let's search, uh, let's skip B here. Okay, let's search, uh, Rezi here. Okay, so yeah, we have two different fields to search in different databases. Looks, uh, quite good. But let's, uh, as we did here, add a counter selector and, uh, not show, uh, two different search fields at the the same time. Let's show one search field based on the selected country. So, um, to do this, let's start by adding the country code to our company. So I will say rails, uh, generate immigration. We're going to store the country code, add the country code to companies country code stream. Let's look at the migration, add the country code. Okay, looks good. Rails to be migrate. Now, uh, I'm going to add the country code selector. So here I will have, uh, country code, uh, that name is going to be country code. Uh, let's remove everything else that form, not text era, but select. They're going to select the within, uh, France and uh, uk. Let's see if, uh, it gets rendered. Okay, we have the selected, but we cannot, uh, yeah, we always have a conscious selected. We want to also allow blank. Uh, let's add, include blank. True. Okay, looks, uh, good. So, uh, we have the country selector. Let's also, uh, make it so that we can save it in our database. So going to the company's controller company programs, I'm going to enable country code. Okay? And now let's display only one of these fields based on the, our selected country. Now I cover this functionality. Previously we did it, uh, with episode 81, show height form field based on selected value. So I'm not going to, uh, go through it all. Once again, I'm just going to create this, uh, control and copy the code. I recommend you to have a look at, uh, this episode. Learn more on how you can show a different field or different diffs based on a selected form field. So I'm just going to go here and, uh, I'm going to create a stimulus controller named show failure rails, generate stimulus show value. I'm going to open the controller and, uh, paste everything from, uh, this tutorial that I previously created, not reinvent the v. And uh, going back to our form, so we will add this show value controller to our form. And here is like the value. And they're going to show this if the value is France and this if the value is, uh, uh, uk. So, uh, here in the example I have, uh, data show value, target input, uh, here data. The, this is the input target and these are the output targets, uh, input. When I input, uh, so when I kind of change this selector, it is going to go to show value controller toggle action, and it's going to have look at the output targets. So this is an output target and this is an output target. Uh, data, uh, let's see, data show value, target output, and value France. And here we're going to have the value uk. And this might be it. Okay, so you see I have no country code selected and I don't have any search fields. I select France. I have the French company search, I select uk, I have the uk, uh, company search. You say the same company doesn't match, uh, in the UK company search. Uh, let's, uh, yeah, this kind of works. Uh, one final thing. What if you don't want to input all the information, uh, that you find, uh, here, like this information is use useful for search, but what if you want to input only the, uh, company registration number so you, as we do it in this application store only the country code and the tax id? 'cause it's all we need in this application. So, uh, instead of pasting all this information into this info field, let's say we will paste on the company registration number. Uh, to do this, I would go, uh, back to this company auto complete controller, and here instead of paste in the, in the text. So instead of paste in the all this, uh, text, I will paste only the country code and, uh, oh no, the company registration code. So, um, to do this, I will go to the search fields, and here I will add the, as a data parameter, the company number. So I will say data company ID equals this, and I will do something similar for the French search data company ID result CRM. Okay, so, uh, we are going to pass company ID as a data attribute, then click uh, this and here in our auto complete controller. Instead of having the inner a text, I'm going to, uh, target, have target dataset, company id. So let's Refresh fresh, let's search for France. Tracy. It inputs the company id. Let's search in the uk, Skippy. It inputs the company id. And that's basically it. So we built models, this, uh, application where you can, uh, uh, select the country, search for a company based name or company registration number, find the company, and, uh, input the company registration number from different countries into an info field. Save it. And here have this, uh, company that is, uh, in the UK that has this company registration number. So, looks good. Thanks for watching and see you in the next one. --- # Find and update UK🇬🇧 company information | SupeRails #187 - URL: https://superails.com/posts/find-and-update-uk-company-information-superails-187 - Published: 2024-08-31 - Duration: 09:08 - Access: free - Playlists: International company database Previously I wrote about finding a company in the French national company database. Now let’s search 🇬🇧UK companies via API. Episode source code: https://github.com/corsego/181-fr-company-search/commit/b397f5375f0a221c1d37e8c5f02a9b71b3546e51 Based on this blogpost: https://blog.corsego.com/uk-company-database-search ## Transcript Hello friends. So in a previous episode they learned how to connect via a i to the French National Registry of Companies. And we built small revenue rail application where we have the possibility to create a company. We have this information field that cannot be manually filled in, but we also have a search field. And as you search, you will see information pop up about the different companies. So, uh, let's say this company matches my search result. I will click on this company and this information gets populated in my info field. I can click create company and the company is created with the, the name and the registration number, uh, in the info field. So looks good. But in this episode we're going to focus on, uh, the UK data. So previously we did France, now do the same, but for UK. And uh, here they have this company's house website. And on this website they have the API documentation and you can, uh, go to search for companies. So we have this endpoint, get the API slash search slash companies. We have a Q3 parameter and we are going to make a a p request. But for it to work we'll also need to have an API key. So here you would need to go to the developer hub and create an account. I've already done it and I'm going to go and create an application. Actually, don far it is a free service. You don't have to pay, you just need an account. So create an application. Uh, super rails testing. Uh, now last time I tried test mode. I didn't manage to make API calls. So I'm going to go with live mode, go into the application, create new API, key name, description, rest create key. Okay, here I have the API key. Now the copy doesn't seem to be working, it doesn't work for me, so I'll just have to manually copy and let's copy it somewhere. So I'm going to have to make requests to this URL. Let's first try making not in Ruben Rails, just in the, uh, terminal using the CURL or curl whatever, whatever it's called. Uh, so I'm going to say curl, uh, header authorization. And uh, it'll be basic and I will input this API key and then have the URL. Let's try making the request. Okay, inval authorization header. So it in first because we need to encode this, uh, key to encode the ski will need to type, uh, something like, uh, echo, uh, M and the ski to base 64. So here is the encoded version of this key, And we're going to make the same API request with the encoded version of this, uh, key. Let's make the API request. And here we have, uh, a successful response with no results because we didn't have a search query. So let's add a question mark. Queue equals, I know there exists a company named Skippy. There are two of them. Okay, so I have the results. Uh, there are items, uh, items page, and the so on. So I managed to make the uh, API calls to the uh, UK company's database in the terminal. Let's do it in the application as they built previously for French data. So, uh, what we're going to change is going to company rb. Previously we did search France. Now we're going to do search uk. Uh, we're going to search Now name is not nice peram because the search not only by name, but like company registration number. So let's just say query base. URL is going to be this one. Yeah, it has the base URL for the API. Um, let's not add page per page and code it. The query. Yeah. Also, I will replace name with query everywhere. We don't need page and per page. And uh, we'll have, uh, this year is going to be base year l slash uh, companies slash search slash companies. So here slash companies query our encoded query. And, uh, let's see if this would work. Let's say query equals skip B. Let's try making this, uh, in the rails console. I already have a verday installed. Okay, so again, I have, uh, the authorization, uh, error. And to pass this authorization error with verday, I will need to have something like, uh, connection equals further day new, uh, do connection. And here I will say connection, headers, authorization and equals basic. And I'm going to add the, the encoded API key. So I'll need this, uh, API key and I will need to encode it. Let's say API key equals one that we got from the API. And here I will have basic and the base 64 strict and code 64 API key. Let's try running just this thing in the console. Yeah, we don't have API key defined. Okay, so this works. Let's try making the request. So we'll have, uh, response equals connection. Get URL, this URL results, and we have the results. So this looks, uh, good. Now let's, uh, go and, uh, make it work in our form in the, this form. So now we search for French companies. We need to make the request to search uk, not search France. Let's see that search. France is defined in the company's search controller. So instead of search France, I'm going to have search, uh, uk. I refresh, I'll search for Skippy, and it doesn't seem to be working. Let's see, the console will have an error. So the error is because, uh, we don't have results in our response. You see, we have items. So you see the APIs are slightly different, France and for the uk. So in search, double stream, EIB. Instead of having results, I will need to have, uh, items and the possibly little bit, not noncompliant, but name. Let's just see the results. So let's search for Skippy. And you see we have the UK company information. Let's, uh, select one of them and let's see what information we want to store. We want to store the, let's say address snippet, the company number. This is very important. And the name, yeah, in this case it is the title of the company. Okay, let's, uh, this page, just these results. So search for the company. Skip. You see, I have two results actually, let's search for anything you see, it always gives me the same results because here I forgot, uh, removing skip from the query. Yeah, this is a rookie mistake. Okay, let's search for any company. Let's search for Beckham like David Beckham. Okay, we see a few companies, uh, connected with him. Let's, uh, click on, uh, this funnel and the name of the company, the company registration number, and the, the address are populated. Let's create the company. Okay, so looks, uh, good. This is basically how you can use, uh, the company's house, uh, API for UK to search for different company information. Uh, one more thing that we can cover in the next episode would be to make a country selector. So you'd imagine you can select the country and search in the company, in the country's company registry. So you can search either in the UK registry or in the French registry. So I think this is a good topic for the next episode. --- # Mention users with TributeJS | SupeRails #186 - URL: https://superails.com/posts/mention-users-with-tributejs-superails-186 - Published: 2024-08-25 - Duration: 25:19 - Access: free - Playlists: Mentions, Hashtags In episode #95 we focused on parsing text and converting hashtags and mentions into links. Now let's add an @ mentions dropdown to any text field, so that you can select a user that you want to mention! Episode source code: https://github.com/corsego/186-mentions-tributejs Based on this blogpost: https://blog.corsego.com/create-mentions-tribute-js 0:00 How mentions work 1:35 Scaffold Messages 2:23 Install TributeJS, display mentions dropdown 5:24 CSS styling for mentions dropdown 6:10 Scaffold Users 7:29 Search users in database 16:00 Scaffold Mentions 16:42 Create mentions in database 21:08 Display mentioned usernames as links in text ## Chapters - 0:00 How mentions work - 1:35 Scaffold Messages - 2:23 Install TributeJS, display mentions dropdown - 5:24 CSS styling for mentions dropdown - 6:10 Scaffold Users - 7:29 Search users in database - 16:00 Scaffold Mentions - 16:42 Create mentions in database - 21:08 Display mentioned usernames as links in text ## Transcript Hello friends. So previously I recorded episode number 95, where we had the text field and we passed it to find mentions and the hashtags. And now we're going to actually create mentions. So here is how it works on super.com. I will type hello, I type at, and you see I have this, uh, search pop up no matches found at start typing ov. And you see I have a few match and, uh, usernames, uh, pop up. I can select the username and keep typing. I can select another username. And when I create a comment, you see I have, uh, the usernames that are turned in to actual links that I can click and direct to the user's profile. So the user has been, uh, mentioned and, uh, we're going to build this kind of mentions in a application. Now. Now, uh, initially I was thinking of, uh, building SST controller to, uh, create mentions out from zero, but then I understood there's no point in recreating the ve so I decided to use tri BGS. That is actually a really nice JavaScript plugin for adding, uh, mentions. You can test it on the official playground here. Um, you see I type at and, uh, I have, uh, results pop up. I type normal text. And then just when I type at I, I see this, uh, results pop up. And here is the GitHub repository for tri BGS. You can also have a look at it here. So let's, um, add, uh, this kind of mentions to, uh, usernames in a new results application. So let's create a new results application here I have just generated one and I will open in my code, uh, editor. Okay. First of all, I will need some kind of text field where I'm going to add mentions. So I will say I will create a new, uh, message rails, generate the scaffold, uh, message and message will have body that is text field, let's say rails db, uh, migrate. Uh, I will say, uh, yeah, I will go to our route. And here I will say route to messages index. So if I run the server with bin Dev, because I would have installed VIN CSS, I have, uh, a list of messages. Okay, let's save our changes scaffold messages. If you make, uh, lots of comments, uh, comments often, then it'll be easier for you to track the change history. Okay, so here I have generated our messages and now I've install, uh, tri BGS to be able to display, uh, this ad mentions in, uh, this field. So I will say, uh, bin input map bin triple js bin slash input map bin tris. It is going to add triple js to input map A, B and add the JavaScript inve slash JavaScript slash trigs. And I will create a new stimulus controller rails generate stimulus, I'll name it, mentions. Let's open our mentions controller. And here I will, uh, connect tribute gs. So I will say import tribute from, uh, tribute js. And how do I initialize tribute? Let's see. I will, uh, go to the docs. And uh, here I have, uh, this example. So I will say, uh, this tribute equals new tribute, and I have a couple of values to choose from in this, uh, example. And I'm going to attach this mentions, uh, controller to our search, uh, to, to field. So I will go to the form field, uh, for the message, and I will say, data controller mentions I will start the server. Okay? I will type text, I will type at, and nothing's happening. Uh, okay, maybe I need to have a look at the console. So nothing in the console. Maybe I need to, uh, say that, uh, I'm attaching tribute to this, uh, element. So I will say, um, what is it? This tribute attach this dot element. And this element is, uh, the one where, uh, the control is initialized. Okay, let's refresh. Hello. And you see we have the user names pop up, but it is not styled. Uh, I can type you see, type in the works. Uh, so we need to add some stylin. Uh, first of all, let's save our changes. So, uh, display tribute, uh, search field. So to add some stylin, we can go to the library. And here we have, uh, this, and we have the example CSS. We can just input this tiny bit of CSS into our application. So I will go and open assets style sheets, application CSS, I will input the CSS. And you say it is all scope to tribute, so it'll not override other CSS in your application. Let's, uh, go back to our app refresh. And you see the STA looks, uh, much better. Again, if you want, you can start it to look more install with your application. Okay, so we can tag users. Looks good. Let's say our changes that we've added, the tribute styles add tribute js CSS. Okay, looks okay, but we don't want to have this predefined list. We want to search from our users. We want to have a list of users in our database and search users from the database. So let's add the, a user model where a user has a username and search from this user model. I will say rails generate scaffold, A user and user will have username. Okay, let's, uh, also add users to our seeds. So, uh, here we'll have a predefined list of usernames. I will say usernames. Do each do username, and I will create a user with each of these usernames. User dot create. By this username, I will run the seeds rails DB seed. Okay? Uh, yeah, I mean to say rails, DB migrate, and then TB seed rails console, user dot all you said the users have been created with our seeds and each user has a unique username. Okay, let's, uh, run, um, uh, yeah, let's save our changes scaffold users. Okay? Now we want to be able to search for users in our mentions controller. So we're going to make a fast to our users controller here. We're going to make a request in the JSON format, find the user by our request PERS and, uh, uh, return, uh, JSON of, uh, users. How are we going to do it? Uh, we're going to also, I think, install the GEM request just rails. That is good for making the, uh, requests from, uh, your stimulus controllers. So I will add this gem to our gem file run bundle. You see, I've added request G rails. Now according to the docs, I need to say bin rails request. GS install. You see run, install added this input rails request yes to our application gs. Okay? Uh, now going back to our mentions controller. Instead of defining the values like this, we're going to do something else. First of all, we will need to make, get request for the request gs. So I will say import, uh, get from and, uh, from rails slash request Gs. And here I will say, so values, uh, equals, we're going to make a, a sync, uh, requests. We'll have, uh, the text that we submit, uh, uh, in the search field, we have the server running. Let's run the server. So we'll have the text that we submit, uh, in the search field, and we have the results. Let's name it CB, whatever. Um, and I will save, uh, constant response equals a weight. We are going to make the get request with rails request. You see it, uh, becomes active. And I will get the slash users slash uh, uh, do json and we're going have a search query. So I will say at query equals, and they're going to have, uh, uh, dollar sign and text. So we're going to search, we're going to input this, uh, text into our search pres when we, uh, search for users do js o let's, uh, say if, uh, response do, okay, then we'll have, uh, constant users equals, uh, evade the response. Do JS o and let's, uh, log our users. Yeah, I'm forgetting the JavaScript syntax. So if response, okay, and then console log users. Okay, let's, uh, see if something happens. I will refresh, I will say at Y. And you see, I always get a list of all the users. Okay, that's already not bad. I have a list of all the users. So let's try displaying all the users from our database. Now they're not displayed because we just have an array and we don't want just an array. We want to, uh, just the usernames. Mm-Hmm. And we will have keys and values as we have, uh, in, uh, in the GBGS example. So in our case, both, uh, keys and values are going to be the username. So, uh, let's go back and here will have, uh, uh, c, b, so kind of the results users map. And, uh, in map, I will have, uh, braces. Key equals user, user, user. Yeah, we need to have, uh, users map, uh, braces. Let me try one. Scan users map. He will have user, and the user is going to have, uh, uh, key user username and value user username. I need another close. Okay? Uh, this should be much better. So let's try the search for users. And you see, we always get a list of all the users that they have an database, d, HH, okay, at ov, okay? But now we always have a list of all the users in our database. And if we don't want the list of all the users, we want to have by default only the list of users that match the search query. So we're going to go to our users controller and, um, do some manipulations here. We're going to display not all the users, but only the users that measure the search theory. So, uh, let's try making, uh, putting it a binding B, I just now run the rail survey instead of bin dev to hit the binding. And I will type something. Let's, let's type something. You see, I hit the binding. So it makes the request to users, the js O, and let's say pres in the PREMs, we have the query equals s perri. And now we're going to search the users. So I will say, um, respond bond to do format in format HT MLI will return all the users and, uh, in format that, uh, JSOI will perform the search. So let's say users equals if ifs present, then we'll have user dot, uh, what is it like, uh, there. And we'll have username, uh, I like so matches. Um, and here we'll have, uh, dollar signs inside dollar signs rams, uh, query. Uh, yeah, I forgot the hashtag sign here. Okay. Uh, and, uh, yeah, I forgot to close it. Okay, so we're going to search for users. Uh, and if, uh, the is not present, we will have user none. So an empty active record collection of users. Um, okay, so we have, uh, the search. Let's see if the search would work. I will try to search and I have the users that measure this, uh, uh, query that have the letter S in their, uh, username. Let's search for, uh, SHM. And there is just one user that has SHM in the his username. So, looks cool. Continue. I will remove the binding and let's refresh the page. So let's search again. So hello at, okay, failed because internal server error. Okay, I have a syntax error somewhere. Let's see. Uh, maybe it is because I need to say, uh, render Jason o at users. Let's see. It might be at, we, okay, still we have, uh, an internal server error, unexpected input line eight two. Okay, we might have made a typo somewhere here. Let's see. Yeah, I think I just forgot an, and here. I don't know why my code edit didn't give me this error. Okay, let's try once scan. Okay, and search works. So at a, uh, or C, okay, search, uh, seems to be working well. So now the search our database for users, that looks good. So, um, find users from database. Looks good. Okay, now let's, uh, actually create mentioned records. So when a user has been mentioned, we want to create a, a mentioned, uh, where we have the user and the, uh, message. And this value user would be able to see a list of, uh, all the messages that he was mentioned. Uh, to do this, we are going to create a new model that is going to be called mention. So let's say rails generate scaffold, mention user references, and uh, message references, rails. They be migrate bin dev, let's say scaffold mentions. Okay? So how we are going to create the mentions. Now, whenever a message was created, they're going to pass the message for the users that were mentioned, and we are going to, uh, create the records for mentions. So to do this, I will go to our, uh, message. rb, I will say has many mentions in mention, we already have belongs to user and belongs to the message. And I will say, uh, after grade commit, um, uh, generate mentions. Now we'll have private and private, I will say dev generate mentions. And we're going to pass the message body for mentions. So for text, that starts with the at, uh, symbol. Uh, let's say, uh, mentioned usernames equals uh, content scan. And we're going to scan, scan, uh, starting with the, uh, at, and we'll have, uh, uh, votes, uh, like Let's try scanning the some kind of mention or message. Let's go to our console. Let's say message equals message, lost message. Okay, we have the body. Let's try to scan. Now it's going to be not content, but it's going to be body. So message, body scan, we have this, uh, area of arrays. We're gonna say do flatten to just have an array of Latin. And we have these mentioned usernames. Looks good. And for these mentioned usernames, we're going to find the, uh, users. So here we have mentioned usernames. We don't have body, we have, uh, yeah, we have message body, okay? And we'll say, uh, users equals user dot their username matches, uh, mentioned usernames, okay? We found the users, users dot count. We found two users, okay? And we're going to create, uh, uh, a mentioned for each of the users. So I'll say users each do user. Now we'll have kind of self, so message mentions, create, and they're going to find user will be this, uh, user. Now an end statement, and this should be good, let's, um, go and try creating a new message. Okay? Uh, so let's destroy this one. Let's destroy this message. Let's go to mentions you. We don't have any mentions. Now I will create a new message, uh, no, not a new mention, a new message. So hello vice OV and Marco. Great message. I go to mentions and you see both, uh, users have, uh, mentions. So I generated two mentions. Looks good. Now let's try deleting the message. Okay? We get an error. We need to say dependent destroy. Now, once again, I go to delete, the message was deleted and the dimensions for deleted loose call. Let's go and create another message. Uh, hello at Adrian and, uh, Mets. Okay. I created this message and I have this two usernames here, but they are not clickable, and we want to be able to click on the username and go to the user page. So to this, we're going to, uh, add another scam. So I'm going to scan, uh, and display this, uh, as a link. I'll go to the message helper, or before I do it, I'm going to save out changes. So generate mentions. Okay, yeah. Now I'm going to highlight the, the, uh, mentioned usernames as links to open the user's page. So I will say dev, uh, with mentions text. And instead of just having body scan, I'm going to, uh, replace the usernames with a link to the username. So I will say text gs o, uh, this. And, uh, for each of them I will say do mention, and I'm going to have, uh, uh, a link. So link to, uh, mention, and it's going to be user path at mention. And going to the partial message, I will have with mentions message body. You see I have the HTML that was generated. Uh, looks, uh, okay, I'm going to say simple format. And the HTML has been turned into normal links. Looks good. Let's try clicking on one of them. You see we get an error and we have this at agent dev, so we don't want to have the at in our links. Let's, uh, open the inspector here. We have this at, we want to have no at in the path. So I will say username equals mention, uh, one. So everything after the AT and however will have the link to username. Okay? Uh, so we have the username, we don't have the at, but we can't find the user by username. We can only find the user by id. So going to user's controller, instead of searching just by username, I will search by, uh, ID or username, find by ID or user. The username matches this id. So refreshing. And if you can find the user by the, uh, username, or we can find the user by an id. So looks, uh, good. And, uh, that's basically it. Let's also add a few navigation links to our application, uh, application, application, html, er BI will add a few navigation links, link, uh, to messages, link to users, and link to mentions. Okay, here we have a few links. So messages, users, and mentions. Let's, uh, again, for a final demo, delete all the messages. We have a list of, uh, users in our application. Each user has a username. We have, uh, no mentions yet. We have, uh, an empty list of messages. Let's try, type in a message. I would say hello at ov, uh, from, at the, uh, Mets and the Adrian. Great message. Uh, these are actually links. Let's, uh, yeah, highlight these links. Let's go to message Helper. Let's say glass text Blue 500. Okay? There's actual links I can open the users and I have also generated mentions in the database. So in theory 10, a user goes to his profile, he would be able to see all the mentions for himself, and that's basically it. So I really like this, uh, G Js plugin for Creon mentions. Thanks Fortune, and see you in the next one. --- # Social Share button (very important for PWA & Turbo Native apps) | SupeRails #185 - URL: https://superails.com/posts/social-share-button-very-important-for-pwa-turbo-native-apps-superails-185 - Published: 2024-08-22 - Duration: 07:09 - Access: free - Topics: PWA, stimulusjs If you open an app in a browser, you can easily copy the URL or click the native Share button. But if you are running the app as a PWA or Turbo Native app, there is no native browser sharing functionality. Thus, you will likely want to add a native share button. Many apps like Instagram & WhatsApp do this. Let's implement a native Share button using the Navigator.share browser API. Based on this blogpost: https://blog.corsego.com/stimulus-share-button ## Transcript Hello, France. Here's a very interesting problem I've recently encountered. So here, let's say I'm navigating the local host of Seat rails, uh, and I want to share a URL. Well, as I'm running it in a browser, I can just go with the URL and share it with whomever I want, or I can click on this native share button that would open a popup with different options of how I can share this URL. If I'm running it on a mobile app here I have, uh, uh, safari opened. I run s in a mobile app, and I have the URL. I can cope with the URL or I can, uh, click on the native share button. That'll open a model with different sharing options I can share in an app or whatever. But what if I'm running RELs as a, for example, PWA? Well, uh, here I've previously already clicked on, uh, add the app to home screen. So now I have, uh, RELs available as a PWA here. And you see there is no native share button like this one. And, uh, there is no, uh, UL bar I cannot copy with the URL to page. Yes, of course, I have, uh, previously manually added a link to copy with the UL to the clipboard so I can share it to somebody. But I want to have this kind of, uh, native, uh, popup model, uh, on mobile. And I want to have, uh, this kind of button on desktop. So how can I add a native share button to my application? Well, for this, I can use the, uh, browsers navigator share method, and we're going to implement it. And, uh, even if you are on, uh, PWA, you would be able to have a share button and it would open up, uh, this kind of model so that you can share, uh, URL to a page in your PWA, uh, with anybody you want. So, uh, let's, uh, go to our application and I'm going to create a new stimulus controller. I will create rails, generate stimulus, I will name it, share. Now, going back to the application, I will create open the share controller, and here we're going to have an action named, uh, share. And we're going to run navigator share. So, uh, share and we'll have navigator share. And we're going to share a URL. So URL will be, we need to pass in A URL, so we'll have a stimulus target, uh, no actually stimulus value. So it'll be this, uh, URL value. Uh, we don't need anything except of the URL. Actually, there are a few different pres that you can pass into this, uh, navigated the chair, but, uh, only the URL is supported across all the browsers, or at least should be supported. A few different other types of data that you can share are not supported in a few browsers. So, uh, we'll have this navigated share. And we need to add this, uh, uh, value. So static values, URL, that is a string. And now let's add a button and we can make this button available everywhere around our application. Let's, let's do it in application html, ERB, and above everything, we can say something like equals button two share. Uh, we not pass any URL, so nil. And we'll pass data controller will be share. Then, uh, action will be, uh, when we click on share by default, uh, the button for the action for button is click. So I will just say, um, share controller, share stimulus controller, and share action. Uh, now to make it a bit less confusing, let's make it click. So share controller, click action. So, uh, then click on this button. We're going to activate the click action of, uh, this share controller. And finally, we need, uh, URL value. So we'll have, uh, uh, share URL value equals request dot u rl. Now closing the data controller, and let's see if we display it, uh, here. Okay, let's turn the server bin dev. Okay, I have the share button. I will click the share button. And you see it does pop up this, uh, native, uh, share menu, but you see it still tries to load the, the page each time 'cause it's, uh, using the default button, uh, clear behavior. So we are going to prevent that by adding the prevent default e dot prevent, uh, default. Now I will refresh the page to reit, initialize the stimulus controller. And now the share button does not refresh the page. It only gives us this, uh, popup menu. Okay, let's, uh, try running this in, uh, mobile. I'm going to refresh the page here. I have the share button and it opens the, uh, native share model. Let's try run it in the local host, PWA. So I will refresh, oh, it doesn't work like this. Okay, let's try navigating somewhere. Okay, you have the, the share button. And you see even that, uh, we don't have the browser controls visible all around. We have this, uh, native share button, so it works well. Uh, let's try running this application in another browser. Here I have, uh, Google Chrome. So I will refer the page here, have the share button. And you see click cl the share button does nothing. So apparently this, uh, navigated the share. The share is not available in the this Chrome browser. So let's hide this button if the browser does not support this behavior. So I will say if, uh, not navigator share, uh, this, uh, element, let's say hit them equals true. Now I will refresh it here, and you see the share button doesn't pop up. If the browser does not support the share button. But, uh, on Safari, I will refresh again or try and never get into another page. You see the share button is visible. So that's basically it. I think it is, uh, important for you if you're building a TUR native mobile app. If you are building a PWA to still have, uh, such, uh, basic functionality as, uh, share, if you go to Instagram or any other Messenger app, uh, it is highly likely that you'll be able to share information from there. And the same way you can share different pages in your web application. So, thanks and see you in the next one. --- # Getting Started with Turbo Native in Ruby on Rails | SupeRails #184 - URL: https://superails.com/posts/ruby-on-rails-184-turbo-native-in-15-minutes-getting-started - Published: 2024-08-15 - Duration: 15:34 - Access: free - Topics: apple, hotwire, iOS, outdated - Playlists: Hotwire Native Let's learn to build mobile apps with Hotwire using Turbo Native! 0:00 Run and explore the Turbo Native Demo app 5:25 What is Strada? 7:22 Run Turbo Native on any URL 8:44 Create a new Turbo Native app 12:30 The current best way to create a new app Turbo Native iOS: https://github.com/hotwired/turbo-ios Turbo Navigator branch: https://github.com/hotwired/turbo-ios/pull/158 Shoutout to @joemasilotti - he has great examples on YouTube, his blog, and GitHub for building Turbo Native apps 🙏 I learned a lot from him. ## Chapters - 0:00 Run and explore the Turbo Native Demo app - 5:25 What is Strada? - 7:22 Run Turbo Native on any URL - 8:44 Create a new Turbo Native app - 12:30 The current best way to create a new app ## Transcript Hello friends. Let's learn to build mobile applications with Rubion Rails. So if you go to the Hot Fire do Dev website and scroll down to, uh, learn more about Turbo, go to the handbook, open the chapter six, go native on iOS and Android. You will see this term Turbo native. Basically, it is a tool that lets you wrap your Ribbon Rails web application into a native iOS or Android wrapper and serve it as a real application. So basically, termin native is two main libraries to native iOS and to native Android. You can see them in GitHub to Native iOS is written in Swift and to native Android is written in Kotlin. In this episode, they're going to focus on the iOS. So we have have this, uh, repository. It's basically a library to create the iOS applications that, uh, uh, can serve your web application. And you need to have a hot volume to have Tobo Drive enabled, uh, in your application to make it work in your Rails application. So how do we start with, uh, tur native? We have this repository and we have a demo application here. So you want to run this demo application, uh, locally and see how it works. Now, to make it vo you would first of all need to clone this hot file slash turbo iOS repository locally. This I have already done. I have it locally, and you would need to install X code. So this is, uh, a program to build the iOS applications. So now pause the video, install Xcode. Okay, now I assume you have already installed Xcode. Let's continue. So, um, I'm inside this Turbo iOS repository, and here I see this demo folder. I will, uh, go to the demo folder and I will type open and I'm going to open demo, do Xcode project. So Open is by default going to try to open something in Xcode, and, uh, we need to open the Xcode project file. I click okay. It is, uh, launch and Xcode. And uh, here we have this, uh, application. Here is the file structure. Uh, we have this demo file and we see the URL, uh, to the web application that is, is going to try to serve. Is this one? Uh, let's try opening this. URL. Okay, I've got an H before HTPS. Okay. So this just, uh, a web app that is optimized to be used, uh, with Termin native, and, uh, well, it works just as any regular web app, uh, on the web. But uh, on the Termin native, it'll have some special behaviors. So here, for example, you have, uh, a menu. You click the menu, it opens as a dialogue. Uh, you have the form, it's just a regular form that you can submit and, uh, redirect somewhere. But we're going to try to run this, uh, web app, this URL, um, inside this, uh, iOS terminated app. You see, we also have this Turbo iOS package installed. We have, uh, out of code written to uh, make, uh, this, uh, turbo iOS package, VE as an iOS app. So we have, uh, like this, I would say the main file, it has, uh, uh, turbo imported. So it is by os it has Strada imported. It is this one. So let's try running this application. I'm going to, uh, click on this strong button, or you can type command R. And it's going to try to run this application. It is opening an emulator, and you see the emulator here is set to iPhone 15 Pro, but you can try setting any other supported device. So anyway, it is launch in, uh, the application for the first time. It can take quite, uh, about launch here. It's going to appear, okay. And here the application is, uh, running. So basically we're just, uh, serving this URL, uh, inside our, uh, ative iOS application. We can try navigating, let's try some different behaviors. So advance to another page. And you see this, uh, navigation has been stacked. So I click once and twice to go back to the home screen. If I have the turbo visit replace, so that screen just gets replaced for the this one. So I would still need to click not advise, but once to get back to the home screen. Um, hidden an error. This looks more like a native error page. Uh, open a slow loading page. You see we have this, uh, native, uh, an indicator trying to load a webpage model. You see, it's like a popup. We can go up and down. We can click down to cancel or click submit to have a redirect. The page should be after the submission. We can try following an external link. And by default it is going to open the native browser, but we can figure it, uh, we can configure it so that it opens an in-app browser. That's actually quite easy. Uh, we can, uh, see how Strada works. This is actually interesting. So here we have a form and you see we don't have the submit button on the bottom here. For example, in the uh, web app, we have, uh, type in first name, last name, and a submit button on the bottom here. Using Strada, we hide the submit button in the bottom and instead display the submit button here on top. So this way the user has more of a feeling that he's using, uh, native application, but Not just just a web application, uh, in his, uh, browser. Then full, uh, modal example. So, uh, let me go back in the web app here in the menu, I click open and it opens, uh, just an HTML dialogue with a few different options. And here I click open menu, and you see it opens the native iOS, uh, menu element and I can submit it. Let's then have a look at the final Strada example, overflow plus menu. Here again, it opens just, uh, the dialogue, but, uh, here it would, you see it doesn't show the uh, button on the bottom. Instead it shows this three dots. Uh, on the top right you click and it opens, uh, the options. So this could be nice if you have like a record in the Urban Rails application, like you have a post, you would click it, you would be able to see the edit or delete the or cancel buttons. This looks neat. And this, uh, business strada can make your application feel more native. So you don't really need Strada to build, uh, native applications. You need terminate, but Strada is just a cherry on the top, termin native to, uh, add additional, uh, uh, behaviors to your application that would, uh, look different on, uh, the web and on mobile. So, okay, we've tried running this, uh, ative demo application. Let's try running, uh, a different URL with the same, uh, uh, ative, uh, app. We can try running super rails.com. So here I have, uh, super rails.com. It, uh, looks quite, uh, responsive. Let's try run this. Uh, URLI will just change it to super rails.com. I will, uh, run once again to replace everything. So each, it is through launch in the application. Okay. And here we basically have the application running. Now you see it is, uh, not really optimized. Uh, we have the content going, uh, above the nbo, so this could be improved. Then, uh, the navigation, you see it gets stacked, so that's interesting. Then the log in the socials buttons, uh, don't work outta the box. So there's quite some work that would need to be done, but basically you can try to serve, uh, any web URL with, uh, this, uh, uh, demo application. Okay, so we have, uh, had a look at the demo application. Now let's try, uh, serving the initial URL, uh, with a new, uh, termin native application. So let's try creating a TUR native application from uh, zero. And to do this, you would want to go to the Turbo iOS docs and have, have a quick start guide. So, uh, let's write a new turbo native application. I'm going to go to file. Yeah, let me actually stop this one. Yeah, it's not, I go to file and new, new project app. Uh, the name will be, uh, whatever, no, uh, super ails organization. identifi would be the reverse of your domain name. Uh, language Swift. Don't change anything else. Everything here is kind of by default and I will store it, uh, yeah in my iOS folder. So here I have a new application, a new iOS application. Let me try running this application and see what it looks like. You see, unlike the uh, demo application, there are just a couple of files and they are all more or less, uh, empty. So here I have this app running and actually it's just a blank sheet at the moment. So let's try installing a turbo native. So here we have this fixed out guide and I have this scene delegate file. I'm going to copy all this content and, uh, I'm going to replace all the content in the scene delegate with this new content. Save. And you see we have this no such module turbo. So we need to install Turbo iOS Library to do this, I click on the project and then here again, I click on the project package dependencies, add a new dependency. Now I already have it, uh, in recently used dependencies, but if you don't yet, then you need to uh, put in the whole URL. So, uh, here, search and package URL, you put in the URL, but if also going to use the main branch at package and, uh, it is, uh, downloaded and it's stolen turbo. Ensure that the target is set not to non, but to this project you have just created ADD package. Now we can go back to Syn delegate and uh, you see the error has disappeared. Now let's try running the project once, uh, again. And you see in the syn delegate by default in this, uh, uh, quick start guide, we have the URL set to this, uh, demo application that we're playing around. So let's see what, uh, the demo application, how it's going to render here. It has already relaunched with just this in delegate and Turbo installed. Let's try navigation. So navigation works well. Let's try, uh, uh, hidden an error. And you see it's just a blank screen. Because it hasn't been configured in our application, we would need to possibly configure it manually loading a webpage mod. Uh, you see it hasn't been loaded Mo. So this is the basic code you need to just render, uh, web application in the Termin native wrap. But auto behaviors are not configured, uh, yet. Again, Strada is not configured yet. It's just, uh, web navigation. So we would need to configure a lot of behaviors, uh, on our own. Uh, luckily there is a pull request, uh, that is going to help us a lot at the moment. Uh, if you go to the Turbo iOS, uh, repository and uh, go to the pull requests, you have this update upstream to Navigator branch by Joe Otti. So initially he created a Turbine Navigator package that could be installed, uh, as separate dependency in your application, but, uh, hopefully soon it's going to be merged into Turbo iOS itself. And, uh, creating to native applications is going to be easier. But for now, I think, uh, if you're building a TUR native application, uh, using this branch instead of uh, uh, the main branch is the best approach. So, uh, we're going to use this branch for our application. I'm going to go with the name of the branch and I'm going to go to the application, have the package dependencies. Instead of up to next major version, I'm going to, uh, say brunch. And I'm going to put in this name of the branch. I clicked enter. It should, uh, load now and I'm going to replace the code in Syn delegate. Uh, I'm going to replace this getting started code with the one from this upstream tool navigator. So, uh, have, have this quick start guide. I'm going to open it and, uh, I need just this, uh, code. So I replace all this code with this code. Again, we have the URL to the demo application. Let's relaunch the application. It's relaunching with this specific tool navigator branch, and with this getting started code and, uh, let's try some, uh, behavior. So logged at web page modally, uh, doesn't work yet. Uh, try hidden an HTP error. You see this, uh, works out of the box. Uh, slow loading. The webpage works out of the box Box. So some behaviors are going to be, uh, available outta the box that were not available previously. Now, uh, talking about loading the VA pages, modally talking about Strada, we're going to do it in the next episodes, but this is the basic way you would be setting up and your Terminate application. Uh, uh, today. I think this must be the more, less best approach. Uh, use the Turbo Navigator branch and use the Get Getting Started guide from the specific branch, and you have a good start application for building your own termin application. Thanks for watching and see you in the next one. --- # #183 Rails 7.2 Rate limiting for Devise - Guard your app from spam and bots! - URL: https://superails.com/posts/rails-7-2-rate-limiting-for-devise-guard-your-app-from-spam-and-bots - Published: 2024-08-11 - Duration: 07:12 - Access: free - Topics: security, devise 🔒 Secure your app from password-guessing attacks, spam, and abuse by rate-limiting your sign-up and sign-in pages. I always rely on the wonderful rack-attack gem for this, capping requests per IP within a set timeframe. Now Rails 7.2 adds native rate limiting out of the box! Let's add rate limiting to your Devise Sign Up and Sign In pages & secure your app! 🛡️ Episode source code: https://github.com/corsego/183-rails-native-rate-limiting/commit/54b962dfa0a7e6407f99e4b3251e61edd5df964e Based on this blogpost: https://blog.corsego.com/rails-rate-limiting ## Transcript Hello friends, here's an old application that I built and here is the list of users. And as you can judge by these email addresses, they all look fake. This could be real people trying to sign into the application with fake data and try to have a look around All this could be actual bots that was spam in the registration page, trying to just create a lot of, uh, necessary data in the application. Usually when you have email and password log in into your application, you would want to guard from bots, uh, who are trying to spam your application with creating lots of fake users and especially should guard the log in page from bots that are making thousands of requests, trying to guess an email and possible combination and trying to log into somebody's account. Historically, I always use the gem rec attack to guard from, uh, bots trying to spam the login and uh, sign up page in the application. But as Rail 7.2 rails has its own native rate limiting feature, so you can add rate limiting without having, uh, any dependencies like rag attack. And, uh, re 7.2 has been released just a couple of days ago. So you can use this rate limiting out of the box and you see it is added on a controller action level. So here in the sessions, basically in the login page, it says that you can have maximum, maximum 10, uh, login, uh, requests within three minutes from the same AP address. If you have more, then you're going to get, uh, an error and you will not be able to, uh, submit the request, uh, for the first time. Actually saw this been used in the, the rails native authentication generator. Here is the code, then you generate that sessions controller. It adds rate limiting line by default. So let's try to add this kind of, uh, native rails seven uh, rate limiting into an application that uses device. And here is a basic application that has a home page, a dashboard that require authentication, log in and uh, sign up pages. And we're going to try to add the rate limiting to login and register so that there cannot be too many requests within a period of time from the same ip. Otherwise there should be an error. So, uh, looking at the example here, it is added on a controller level and uh, we don't actually have, uh, device controllers by default when we create a device, uh, uh, generated in the application. So we'll need to add the device controllers for sessions and registrations. So I'm gonna say rails generate device, uh, um, controllers, users, so for users and the controllers are, uh, registrations and sessions. Okay? So it created the, uh, two controllers and uh, I will go to my roots and uh, I will say device for users, controllers, sessions and registrations. Okay, so opening this controllers, I'm going to add this rate limit to let's say 10 within three minutes only on create to the sessions. So to the login page, let's add rate limiting and let's start the server. And you see going to the login page, you get this no method error and define method rate limit five. It. So because going to the jump file, I'm actually not trying rail 7.2, I'm run rail 7.1. So I will say rail 7.2 0.0 and run bundle. Okay, I will start the rail seven once again and I will say for the sake of example, I will limit it to two requests within, uh, three minutes. So let me open the console also, log in, log in, log in, and you see, uh, I don't seem to be getting any errors by, so because uh, it you it should be using the rails cache, but the rails cache is disabled by default in development. So we need to run the command rails, dev cache to enable caching in the development environment. Um, let me run it again. So it is no longer been cached. Now it is been cached. Okay, let's run the rails server. Now I will click login, login, log in again. And you see now I get this error two many requests. So if I refresh the page, I click log in and you see there is no request submitted, no errors have been rendered. And in the console I see this completed with two many requests. So I'm not actually submitting the form. So it's really nice. Uh, the form isn't been submitted because we are defending it with, uh, the straight limiting, but uh, we want to maybe give the user some input about, uh, what is happening. So we would want to like redirect the user and say that, uh, try again later as it happens in this example, uh, in the rail sessions controller generator. So let's go to this line and we will say limit 10 within three minutes. Only create and redirect the tool in device. We don't have this, uh, new session, URL, we have the new user session, session path. Okay, let's uh, try once again, I will click on login a few times and you see now I get a try again later flash message so it works quite file. And we can add the same way in our registrations controller. So here I will add the rate limit to 10 within three minutes as uh, it is done in the rail sessions. Gen generator. Maybe it is like a good, uh, combination of, uh, uh, attempts per interval of time. So let's add it to our registrations. And uh, I'm going to click send up multiple times. And you see I get this try again later, but again, I was redirected to the login page. So in the registrations control, I want to be redirected back to the register page. So here I will say in new user registrations path. And that is more or less it having a look at the docs of the straight limiting. You can uh, set different stores, you can store it in Redis, you can store it in the rail cache, and by default it responds with head too many requests and it responds based on the remote IP address. So on the IP address of the person who is trying to make the request, uh, we don't have to specify the code, uh, here. So in this scenario, like this is, uh, what would happen by default, but we don't need to specify it. So yeah, that's basically it. This is how you can add the rails native rate limiting to device, to the sign in page and to the sign up pages. Thanks for watching and see you in the next one. --- # #182 Build an OpenGraph image generator - URL: https://superails.com/posts/182-build-an-opengraph-image-generator - Published: 2024-08-09 - Duration: 21:57 - Access: free - Topics: ferrum, image generation - Playlists: Build an OpenGraph automation tool This app will automatically take screenshots of URLs and turn them into custom opengraph-friendly images. Episode source code: https://github.com/corsego/182-opengraph-image-generator/commit/29f7991c1c3a464aa2de0fee39160c72d9ecab04 Text guide: https://blog.corsego.com/opengraph-image-generator ## Transcript Hello friends. So recently I saw this very interesting question on Reddit. What do you use for generating open graph images automatically? So among the answers, that was the GR magic that lets you, uh, draw images using Ruby. And actually I made a real nice episode number 91 on this topic where you can, uh, programmatically overlay images, overlay text in different positions and generate an image. And the final result looked more or less like this. This was actually used as an open graph image for, uh, sports games. Uh, another answer was using the Jim Grove. That is, uh, headless Chrome, API that uses, uh, puppet and node in the background. And my suggestion was, uh, to use the Ferrum that is just like Grover, but it doesn't have the additional dependencies of Node and Puppet, it is just a Ruby headless Chrome, API. They've also different, uh, paid, uh, options recommended as thumb smith.com and actually generating OpenGraph images automatically is, uh, interesting business niche. There are quite lot of businesses around this. So for example, there is tiny app where you can, uh, put in A URL and it'll take a screenshot of this URL and put it, uh, above a nice background. There is HTML, uh, to css image.com where you can, uh, use, uh, HTML to draw images and then, uh, it'll generate the images and uh, it actually has like quite a few different price and tiers. So they pay you, you pay for how many images you want to generate. There is also, uh, thumb smith.com, og image, uh, dot org where you can, uh, again, here's a nice example. You would, uh, share a link to do hunt.com and, uh, if it doesn't have an open graph image, it would look just like this so it doesn't look really clickable, doesn't look nice. But if it does have an open graph image that it look really nice. And what we're going to do, we're going to build our own open graph image generator. Because we're developers, we don't want to pay for an external tool to generate images automatically. So here in this example application, we are going to have a list of Ruby conferences where there is a name allocation start date, and A URL. And based on just this data, we're going to do generate this kind of images where we're going to have a nice background, um, screenshot of the website, the name of the conference, the location, the date, and this is going to be an image. And they're going to attach this image to each event and displayed as an open draft image so that it is visible, uh, when sharing this link, uh, a link to this, uh, event online. And, uh, let's start doing it. So first of all, we're going to just have a basic application where we have, uh, an events model with name, location, study, and URLI actually seeded the events using this script. You will be able to see it in the seed file of, uh, the repository. You should have access to the repository at cogo slash episode 182. Okay. And, uh, here is our basic application. So let's start by adding the gem ome that they're going to use to, uh, take screenshots of, uh, these URLs and, uh, store them in each event. Object. So, um, I will say, uh, bundle add ome and it's going to install, uh, this Gem Rum and Firm has, uh, a screenshots, uh, API. So we are going to visit a page and take a screenshot of the page. So, uh, let's try doing this. I'm going to actually generate a job for this. So I'll say rails, generate a job, uh, uh, event, uh, URL to, uh, image, they're going to open this job event URL to image job. We're going to pass the event object. We will, uh, have your L equals event url. They're going to start, uh, uh, rum. So we'll say rum browser new. Now let's go to this. Uh, go to, we'll be going to the URL. They're going to take the screenshot and we'll ensure that they create their browser. And, uh, they're going to store the screenshot, uh, somewhere. Let's store it. For example, in our assets, uh, at the beginning, I will store it, uh, in app slash assets images slash uh, uh, u rl parameterize, uh, jpg. So JPGs are more lightweight than PNGs, and, uh, JPGs are enough for our use case. They're going to also say quality will be 40%, but voltage it is 75. And we will also mention the format will be, uh, JPG. Okay, let's, uh, try taking a screenshot. So I will open it in the, uh, console. Run this job in the console, uh, event to image shop, uh, dot, perform. Now, event. First it is supposed to, uh, take the event URL, open the firm browser, visit the URL, uh, take a screenshot and store it in our assets. So let's go to our assets. And you see, uh, this image has been, uh, generated. Looks, uh, quite okay. Uh, we can also, uh, try this in the URL and see if, uh, it looks the same. Let's, uh, visit this URL. Yes. And, uh, it looks more or less, uh, the same, same. But you see this, uh, ruby gem was not, uh, uh, generated in our image. Maybe it needs some more time to load. So, um, we will add a command web. We'll, before taking the screenshot, we'll say browser.network, wait until idle and additionally sleep, uh, tool, uh, to ensure that, uh, all the images, everything is uh, loaded. So I will load and run the job once again. Uh, yeah, the autocomplete was not, uh, correct, wait for idle. Okay, let's reload. This time, it'll take a bit longer. Let's have a look at the image. And you see now it generated the image already, uh, with this, uh, ruby jam. So the image generation worked, uh, well, okay, we have generated an image and stored it in our assets, but we want to attach this image to our event. So, uh, instead of, uh, this approach, we're going to attach the image to our event. I will say, uh, um, TMP equals temp file. New, uh, we're going to take the screenshot, um, to this path. So path will be, uh, tmp do path, and now we'll say event. Do, uh, image do attach, uh, this file, the tmp, uh, path. Okay. And, uh, to be able to attach an image to an event, we need to add active storage. So in our event, we don't have, uh, possible to, to attach images yet. So I will install active storage, uh, storage bin rails, active storage install. Let's run the migration rails to be migrate, uh, has one attached image. Okay, so now this job should attach an image. So a screenshot to the event, uh, let's see if it works. I'll run the console event equals event first, uh, event to image, job dot, perform now event. And this time it's not, uh, store the image in assets. It should attach the image to the event. So let's say event image attached. Yes, it is attached. So we can try display in the image, uh, in our local host. Uh, I will go to the event partial. And somewhere here I will say, uh, um, image tag. Event image cannot, uh, resolve image into UL. Uh, maybe some events don't have image attached. So I will say if, uh, event image attached. Okay. And, uh, I will additionally say if event image dot representable, it's additional nice check to have to be sure that, uh, this, uh, element can be viewed. And I will additionally say with, uh, 500, for example. Okay. And now we have taken a screenshot of, uh, this webpage and, uh, we displayed, uh, uh, on the web event. So it looks really nice. Uh, we can do the same with all the other events. We can, uh, add, run this job automatically when the event is created. So for example, in the event model, you could have an, uh, uh, yeah, a callback to run this job or to complete this, helping me a lot with this. So it would look more like this. You would, uh, generate a screenshot of the webpage of the event whenever an event is, uh, created. Let's go further and, uh, not just generate a screenshot, but also make it look, uh, nice. Uh, add some, uh, text around it, add a background. So this is going to be a bit harder. Instead of just taking a screenshot, we are going to also generate our own h tm l around it and take a screenshot. Uh, well actually, yeah, we're going to use, not use the browser to screenshot just like this of a UL. We're going to input our own HTML into the browser and take a screenshot of our own HTML. So let's generate our own H TM L. Let's, uh, try this an an event. Let's take this event and I will generate my own HTML in the slash og. So let's create our own, uh, uh, template. Inside events, uh, og html and here will have equals event title, for example. Now we need a route for this. So I will go to our roots and say, events two, member two get og. And okay, the route exists. The title is undefined. So going to events controller, I will set event for the open graph for also. And, uh, yeah, event doesn't have title, event has name. Okay, so this is going to be our starting template based on which we're going to make something nice looking like this. I'm actually not going to build the cs s out, uh, on my own. I'm going to copy it. Just I will also use not our application H tml, uh, template. I'm going to create a separate, uh, layout template. So let's name it minimal html dot erb. I will remove, uh, everything I don't need. And, uh, this og HTML ERB will, uh, render the template. Uh, that is minimal dev OG render, uh, layout minimal. I think like this. Let's, uh, double check with folks. I will put something in this layout. Yes, I'm rendering this layout. And now I'm going to style this layout. Now I'm just going to copy some styles. So here is the minimal layout styling where we have a background, we center everything in the center, and we rotate all the content a bit so that it looks, uh, rotated by like a few degrees like this. Let's, uh, save and refresh. And you see we have this background and we have the text, uh, that is slightly rotated. And we're also going to update our OGH, TM L with, uh, some CSS. Let's, uh, see how it looks. Okay, so it can look more, less like this. You see, we have the title that we under here, uh, the event name. We have the event location, the event, uh, start date, and an image. Now you see I render not just the image tag event image, but I have all this, uh, hack stuff around. You will see why. Let's first try rendering without all the second stuff, just event image. And you see it folks, as before. Now, we are going to try to take a screenshot not only of uh, this, uh, URL, but a screenshot of this page that we have, uh, uh, generated and, uh, wrapped on our own. So step one is, uh, taking a screenshot of, uh, the URL. Step two is going to be take a screenshot of this page that we created on our own. So, uh, let's create another job. Uh, rails generate, uh, job, let's name it. Uh, let's see how the previous job was named event URL to image. So, uh, event open graph event, OG image. Let's open event OG image job. And it's going to be quite similar to the previous job. Let's copy everything. But there are going to be a few slide differences. So we're not going to go to A URL, but instead we're going to input our own HTML that we're going to have. Uh, here we're going to input this HTML. So, uh, instead of go to URL, we will, uh, say, uh, frame equals browser frames first frame do, uh, content equals uh, HTML and H. TM L is going to be some h tml that we render in line here. So HTML will be application controller, do render, and here it'll be template events, OG layout, minimal. And uh, do we have event here? Yeah, we would need to have at event. So let's set, uh, at event equals, uh, event. Okay, so we are going to render this H TM L that we have created on our own. Uh, we are going to put it inside the browser. We're going to wait for all the images to load. And then we're going to take a screenshot and store it in the event OG image, not just the event image. So let's add an OG image, uh, to the event. So first we generate an image of the URL, and then they generate an open graph image based on this additional sty into which we wrapped our screenshot. Uh, let's see. Let's try run this command and see if it works. Event equals event. First, let's have a look at the event. It is the same van event image attached. Yes, we have an image. So let's run the event OG image, uh, job A form now on the event. Okay, and define the name for nil class. Um, so event is not defined. Maybe we need to pass a science event equals event. Let's reload. Okay, let's try viewing the event, uh, OG image. So just as we have the image, we'll have OG image. Now let's go to the list of events. And yes, here we have the screenshot and we have the open graph image. And you see the image was not, uh, rendered. It wasn't rendered because, uh, I, uh, was rendered in plain HTML in our job and it didn't find the correct path to the event image. So to actually make it fo uh, using rum programmatically, I would need to have the base URL, uh, rails, plot path, event image. This way it would, uh, work. Let's drive. Once again, I will reload and run the job once again. Now I will refresh. And, uh, you see, uh, the image has been loaded well this time. So, uh, we managed to take a screenshot and we managed to take, uh, a screenshot of the H TM l uh, that we have generated on our own. Looks quite nice. Now, open graph images have a specific size. I think it is 1,200 pixels wide and 630 pixels high as recommended. So we are going to specify it here. I'm going to say the, uh, browser dot resize with and height here and here. And now I'm going to run this, uh, jobs for all the other URLs, or at least for a couple of them. I'm going to actually run this job after this job. So whenever I attach the image, uh, I'm going to run the open craft image generator performed later for the event. So one job is going to trigger another job. Let's try running this for another conference. Let's take, uh, Euro for example. It's event number four. Uh, event equals event find four. We are going to run this event UL to image shop for now event. So it is going to take a screenshot of, uh, the URL, and then it's going to take, uh, it's going to render the screenshot inside our own layout here, the one that we have styled, and take a screenshot of it and attach to the event. Let's run this. It can take a bit longer. I'm, uh, putting sleep in, uh, both cases and in both cases I'm waiting for, uh, the network to load everything so that all the images are loaded nicely. And let's refresh this event. Okay, it has an, uh, attached screenshot, but it looks like it did not, uh, manage to run out the job yet. It seems to be still running. So let's wait a bit. Yes, and you see it has generated the second image. Now we want to display this images, uh, in the open graph. So if we go to inspect, we should have a meta tag that is open graph image, and uh, this image. So I'm going to go to our application layout and here I will say equals yield head. And inside events slash show, I will, uh, put, uh, a block to yield the meta tags. So, um, what is the content for? I will have content for head do, and here I will have, uh, equals tag dot meta, and I will have, uh, property named the OG image, and the content will be a URL to this, uh, second, uh, image that I have generated. So, uh, what is the path to this image? Uh, let's see. I think we can use Rails blob, not path, but URL. This is important. It has to been absolute URL event, OG image, um, and to close the braces. And, let's see, once again, I'm going to refresh. I'm going to go to the head. And here I have meta property, OG image. Let's open this URL. And here I have this, uh, open graph image. So fantastic, just from having a few, uh, lines like name of event, location, start date, and your l It took a screenshot, uh, of the URL. We wrapped it inside the, our own template and, uh, generated an OpenGraph image for this event. We can do it the same for all the other events. And, uh, each event when visiting it, uh, and sharing the in, uh, app like Twitter or LinkedIn will have a nice open graph image attached to it. So that's basically it. This is, uh, I think a really nice use case for how you can use the ome headless browser. See you in the next one. --- # #181 Search and Autocomplete French Company Information - URL: https://superails.com/posts/181-search-and-autocomplete-french-company-information - Published: 2024-08-03 - Duration: 24:00 - Access: free - Topics: hotwire, stimulusjs, 🇫🇷 France - Playlists: International company database, Just enough Hotwire for Rails developers Let's solve a common business problem! Allow users to find a company in a public company registry via API and autocomplete company info. While building this feature you will also learn how to use and combine Rails frontend tools like RequestJS + Turbo Streams + StimulusJS in an elegant way! In this example we will search French open company data. To make it work with other countries, you will need just a few tweaks like updating the search URL. 🇫🇷 web search: https://annuaire-entreprises.data.gouv.fr 🇫🇷 API description: https://annuaire-entreprises.data.gouv.fr/donnees/api-entreprises 🇫🇷 API docs: https://recherche-entreprises.api.gouv.fr/docs/#section/Bienvenue-sur-la-documentation-interactive-d'API-Recherche-d'entreprises-!/Infolettre Episode source code: https://github.com/corsego/181-fr-company-search/commit/0dc5cc487b9287474fb091e317c79eb0d88fdb80 Text version: https://blog.corsego.com/french-company-database-search ## Transcript Hello, friends. So here I have an application where I have a list of companies and then I go to create a company. I would want, uh, the user to be able to input information about his company. And usually an official registered company has a name, uh, tax ID and official address and other official information that is registered in the, uh, governmental database. And, uh, what if you don't want the users to manually input this information and possibly input it incorrectly or input false information about a company that doesn't exist? What if you want to let them only input the real information about, uh, their company? So you could add a search where they could search for their company, and, uh, based on the search, you would, uh, just auto complete this information. For example, here I will search for a company bearer. I found this company, I found a list of companies that could also fit the search. I will click on the company that matches the company that I want to input and follow. I have, uh, autocomplete this information. I will click on Create Company. And I have created this company with, uh, this, uh, information that I could not input manually. I just found the company from the list. So how can you do it? Well, in this example I'm going to do it, uh, based on French open, uh, data. Uh, but you can, uh, apply similar, uh, approaches to other countries data. Uh, I've played with France, so you can, uh, I'm gonna do it with France. Uh, so here is an official, uh, company search, uh, website of the French government. I can try searching for this company. I will copy in the text, ID click on search. And here I found this, uh, company. I can try searching by just a name. And here will have a list of companies that manage the query the most. Uh, but, uh, this cannot be added to our application. We would need to access some kind of API. So again, going to the website there is this API portal. Uh, so API here. And, uh, we see that there is, uh, a free API that, uh, also has some rate limited, but yeah, it's free and you can use it without paying anything. So that's really cool. And they have this open API documentation. So let's go to this link and, uh, what do we have here? So we can search companies using the, uh, CURL. Let's, uh, cope this and, uh, put it into our terminal. So get we have this, uh, uh, URL. There is, uh, a search lab post is, uh, a name of a famous, uh, French postal service. The search, uh, won display the first page of the results and only display one result per page. Let's, uh, search. And here we have all this, uh, publicly accessible information about this, uh, company. We actually get, uh, an array of results, but there's going to be only one result in this array. Okay? So we could do it with a CRL, it Means we can also do it with, uh, uh, Ruby. Uh, how can we do it with Ruby? Well, first of all, I'm going to go to my, uh, application. I will, um, check out a comment where I haven't implemented this feature yet. So here I have just coupled the, a list of companies, nothing else. Um, I don't need this. Okay, let's try again. So here I just have, uh, an application where I have, uh, companies, I don't have the search field implemented yet. We're going to implement it, uh, together now. So, uh, I'm going to try to, uh, make this kind of, uh, accessible in the rails, uh, app. To do this, I would, uh, install the GM IDE to make a request bundle add ide. And, uh, let's run the rails console. So I will say require ide. Now, uh, this is the URL and the search for cfu. And we can put something inside the cfu. So let's say base URL equals, uh, this, um, then would have, uh, something like page one per page. Uh, let it be 25. I think that 25 is the limit that you can have, uh, uh, in this French API search. Uh, then we will, uh, have ri equals bear little bit the name of the company that we're searching for. Um, and, uh, would make a request with, so would say response equals Faraday do get. And the URL is going to be base URL, uh, concatenated with, uh, query, uh, concatenated with, uh, this. So page, uh, what page and per page. Okay, let's run the, uh, query and we'll see a response here. Have the response. And, uh, let's, uh, try getting the response. Body. Body here. We have the response body, and we can say JO pass response body. Okay, so it looks like, uh, we've got the results. The up to 25 companies, uh, let's say XX equals this. So here have all the results. Here we can say x, uh, results dot count. Okay, we have 25 results. Uh, uh, we can say results first and we can get the, uh, full name of the first results. So noncompliant. Okay. And we have the name of the company. So we made, uh, request with, uh, Ferdi and, uh, got, uh, a list of, uh, companies, and we got the name of the first company. Looks quite good. How can we make this spoke in our actual rails, uh, app, not just with Ruby? Uh, so here in the, the new company form, I'm going to add the, a search field. So, uh, I will go to the form and I can edit just like an any other field. I will have form dot text field com. Uh, search, just be search. Okay, here I have a search field, and whenever I type something, I want to make a request to this, uh, API with a clear parameters that I send. So I would actually do it with stimulus, um, I would say rail generate stimulus, uh, company search. And from inside the stimulus controller, I'm going to send the request somewhere. Anyway, let's, uh, first open the stimulus controller, company search controller. And uh, we'll have, uh, on search, we will display the event, uh, uh, what was it? Target dot, uh, value console log event, target value. And here have, uh, data controller will be company search and action on input. We will go to the company search controller and the search action. So whenever I input something into this field, I should, uh, log this content in the console. Let's see if it works. I will refresh the page so that we reit initialize the stimulus controller. I will go to the console and I start typing something. And you see all the results of what I'm typing are being displayed, uh, here in the console. So it looks good. Now, um, to put some rate limit, I'm going to display the information in the console only if I have more than, uh, uh, two symbols stepped in. So if, uh, yeah, it's as if, yeah, it's amazing. It's like reading in my mind. If the value length is, uh, more than, uh, more or equals than three, then we are going to log it in the console. Let's try on scan. Okay, so only as of three values, we log in the console. That's how we're not going to perform search when there are not enough characters. Okay? And, uh, now from here we're going to make a request, but, uh, we're not going to make the request to the API. They're going to make the request to, uh, to our app itself, uh, to a rails control. That is going to make the request to the API so that we can respond with the to both streams. Um, so I will import, uh, request G rails so that we can make external requests easily from our stimulus controllers. I will, uh, install it. I will, uh, add this to our GEM file bundle. Now I need to install request Gs, run this command rails, request GS installed. Okay? And now I will, uh, go to our search controller and say, uh, import get, uh, from at rails slash uh, what was it? Let's look at the docks from Rails slash js. Okay. And here we're going to make the ga, so, uh, gat, yeah, thank you. Auto complete company search. Uh, it'll be slash company slash search. Uh, mark theory equals, and we're going to also add the tub stream headers so that it is not an HTML request, but a tub stream, uh, uh, request. So, uh, it'll be something like, uh, headers, uh, accept, but not, uh, application json. It's going to be, uh, text slash VD to stream html. Okay, let's see if this works. It should make a request to slash company slash search, but this, your l shouldn't exist, so we should get an error in our console. Let's, uh, see our console and, uh, okay, couldn't find company with ID search, uh, to go to slash company slash search and query. So we need to add the roots. Let's go to our roots resource companies, do collection, do get search and, and go to our controller. I will create dev search and put a binding here. Okay, now I will search again. Yeah, I already hit the binding. Let's just try once again. I will, uh, search again. Okay, we hit the binding. Let's have a look at the request format. You see the request format is to stream. So we'll be able to return a tobo stream instead of HTML. And we can see, uh, pers. And in the perms theory we have, uh, this, uh, value JEF. So based on this, we can make the request to the open, uh, data and, uh, FA list of companies. So, uh, here I will have theory equals PERS theory. Uh, I will say base URL equals what was the base? URL here, let's go with this. Once again, it's going to be this. Uh, then, uh, full UL equals, um, base UL and this. And, uh, then they're going to make the request. So, uh, response equals faraday dot get full QRL. Let's try run all these commands response. Okay. Uh, then, uh, we can have something like, uh, uh, results equals JO pass response body. Here we have the results and we don't need the, the pagination for now. We don't need the, the top pages. We are going to just display the results on the first page. So, uh, let's just also put the results. So we have just a list of companies. Results dot count is, uh, yeah, we have one because we have one page. Page one, they'll have 25. Okay, this looks, uh, fine. We should, uh, be able to have that list of results. And now let's display them in our view. So I will create another div div with ID results. And, uh, here we are going to respond to this, uh, search controller action with, uh, Tobo Stream template. So I will go here and create search turbo, stream EB, and here will have equals turbo stream do update. We're going to update the content of, uh, this empty div. Uh, so the div's name is Results, and they're going to update it with, uh, the list of companies that we have in AT Results. At Results. Okay, this should work. Let me disable the binding. I will click continue. I will refresh the app and I will search. And here you see I have, uh, companies popping up. So whenever I search, uh, I search, uh, the companies, let's try searching just by, uh, company id. And, uh, yeah, it is unique. So we have only one result. Let's display not all this rubbish information. We don't need most of it. Let's display just the text id, the full name and the address of the company. So, uh, instead of having, uh, results, we'll have results. Uh, CRAN is the company text id, the full name. Then, uh, we will have the address that is, uh, nested inside the Sea team. So we'll have, uh, CGE and Inside Sea we have, uh, address. Okay, let's, uh, try searching once again. So I will search for this company name. Uh, okay, let me refresh it. Uh, nothing's been displayed. Okay, yeah, because I don't have, uh, a collection here, I should have, uh, a collection results dot, uh, each do result. And this is going to be inside the result. So I don't need the add here. Okay, let's try on scan. Okay, I have the company id, the name and the address, but they're all mixed up. Let's just put it, uh, put each of them into a separate div. Let's search again. Okay. And here I have, uh, each company in a separate line. Now, I will also remove this equals, so that they don't have this, uh, ugly array on the bottom. Okay, and have just one line. This fund company looks good. Let's search again for multiple companies. Looks fine. And what if, uh, now by clicking on one of the companies, we want to auto complete this info of the company. Let's make this field actually not use editable. Uh, let's make it only automatically completion. So, uh, here on this field, I will, uh, add the read only true to ensure that the user cannot type in it. Now refresh. You see, I cannot type in it. I can type, but I cannot type in this field, but I can still type here and search. And when clicking on this, I want to auto complete this one. So I'm going to create another stimulus controller for this. I will name it, uh, uh, company autofill or auto Complete Rails, uh, generate stimulus company auto complete and open the company auto complete controller. Here I'm going to have, uh, two targets the target that I'm completing. So this field and the collection. This one I'm going to, uh, close the collection, uh, when I click on one of them and auto complete. So, um, static targets will be, uh, paste. I'm going to paste here. And here I will have a results list and results. And here I will have an action paste. Um, let's have an event here. And, uh, I will put this company auto complete around all the forms. So I will say data controller, company auto complete, and here it'll be data company Auto. Complete Target will be Paste. So I'm going to paste here. And, uh, inside each of, uh, the results, I'm going to invoke the paste action when clicking on, uh, a result. So, uh, here I will have data action, uh, click company portal, complete, uh, based action. This based action. Okay, let's, uh, see if I can access this, uh, based action. I will say log event target value. Yeah, let's try this. I will, uh, so the server one scan, okay, let's open the console. Okay. Uh, now when I click on one of them, I should, uh, have something pop up in the console, but nothing pops up. So, uh, instead of popping up the target value, maybe the target doesn't have value, it would be text content, text, uh, content. Let's try again. Yes, you see the text content pops up. And, uh, I'm going to, when clicking on this, I'm going to update this, uh, element with this text content. So, uh, we have, uh, this dot based target value equals event target text content. Let's try again. I'm gonna search for a company and, uh, missing target element based company auto Complete Target. Yeah, I made it typo. Okay. And now you see it has been updated. There is enough, uh, place, but you see whenever I click on one of these, this info field gets updated. So it looks quite, uh, good. Let me just, uh, close the collection whenever I make a selection. So I will, uh, remove the content of these results and that's where I need the results target. And here I will say data company auto complete, uh, target equals results. And uh, whenever I click on paste, I'm going to, uh, remove everything from inside this results target. So this dot results target in H TM L is empty. Let's refresh and try again. I'm going to search for a company. I'm going to input its information, and whenever I select the company, the search, uh, is cleared. I will search again. I will select this one. The company information has been filled and the search has been closed. And that's basically it. You can add this, uh, kind of functionality for any Country. You just need to have, uh, different, uh, search queries in your, um, controller. And, uh, yeah, that's it. So, um, thanks for watching and see you in the next one. Uh. --- # #180 EU eInvoicing. PDF to XML to JSON. FaxturX, ZUGFeRD - URL: https://superails.com/posts/180-eu-einvoicing-pdf-to-xml-to-json-faxturx-zugferd - Published: 2024-07-30 - Duration: 13:00 - Access: free - Topics: eInvoice, 🇫🇷 France I will walk you through the codebase of a Ruby app that lets you upload PDF documents and extract XML metadata, if the PDF document is eInvoice-compliant according to the EU eInvoice regulation. Next, we will parse the XML and extract useful information like invoice name, date, amount, sender & receiver names & VAT numbers. Subscribe to SupeRails PRO to have watch the whole episode and access the source code! Episode source code: https://github.com/corsego/facturx-reader Based on this blogpost: https://blog.corsego.com/factur-x-einvoice ## Transcript Hello. Issuing invoices is really important for business. That's how you get paid for the work that you have done. Usually an invoice contains an invoice name, the invoice date, the due date for the payment, the information about the receiver, the sender, the information about each line item for the invoice, the totals, and the information of how the payment should be done. And a problem with digital invoices is that they usually send in PDF format and it'll be hard to read the, the relevant information, uh, from the PDF format of invoices, especially when you have a lot of invoices that, uh, your company needs to pay. And usually there is a lot of tools that would help you read the, this information from invoices and solve a problem. For example, there is mind D where you can, uh, upload your PDF documents to them and they would, uh, extract the relevant information most often for invoices. That's their main business, uh, but they charge quite a lot. So it can be like 10 cents per page. And if you have a lot of, uh, small invoices to pay, they can get really expensive. There are also tools like, uh, AI tools like this invoice, uh, AI scanner or you can use, uh, JGPT like tools where you can upload an, uh, invoice and you've get the, some kind of, uh, j with the invoice information. There are also different tri tools like PDF reader that would basically just copy paste all the, uh, all the texts from an invoice into a string. But you would still need to extract the data into an object, uh, in a meaningful way where you can, the integration of the line items where you can get the information about the send the BAT send a name and so on. So this tool will not help you that much. Luckily, EU with its regulations want to solve this, uh, problem of, uh, written, uh, digital invoices. They have this e-invoicing, uh, project where they want to each PDF document that, uh, is an invoice to have an attached XML with all the, uh, relevant invoice data structured in, uh, standardized format. So your software will not need to read the PF document at all. The PDF will be just for the humans, and the software should read the attached XML that is present in the metadata. And the XML should have the, uh, information, uh, in the, a very structured format. So it should be the same for all, uh, invoices. And, uh, this has already been implemented, uh, in some countries, especially in France and Germany. So in France, uh, it has been implemented as as of 2026 for big companies, 2027 for small companies. So if you are a company that is, uh, issuing invoices to the French government, you will need to invoice, uh, to issue invoices that, uh, have this attached, uh, XML file with all the invoice metadata. So if you are building a invoicing software for the French or for the German market and you want to stay competitive in 2006, you will need to generate all your invoices that, uh, are valid in this factor x uh, uh, format. Talking about factor X format, that is how it, uh, like this invoices are called in France. In Germany they used to be called tuk third, but now Germany has uh, also started referring to them as factor X. And on this, uh, wonderful website, uh, you can see the logos of, uh, factor x, uh, compliant, uh, documents and you can download the technical specifications. So I downloaded the technical specifications and uh, I got, um, this folder. So first of all, I have the technical appendix. It's kind of a, uh, data schema of, uh, what the attached XML document can look like. Then, uh, there is, um, an Excel file with the example invoices and with the different types of data that can be present in an invoice. So yeah, like all of specifications, this is useful for you if you're building invoicing software. And, uh, here is, uh, actual great document that is, uh, quite readable that I read about, uh, what, uh, is the point of uh, having this e invoices, how they should work and so on. This is a good document. I recommend reading it if you are working on invoicing software. And it also has some examples of different, uh, PDF documents that are valid, uh, in this factor X format. So each of these documents will have an attached XML file, uh, on which you can train your software to read this, uh, factor X or invoice e invoice, uh, compliant PDFs. And that's kind of a tool that I built, uh, here. So I built a small tool, they can upload PDF documents and uh, if they contain, uh, this, uh, embedded XML in the right format, then uh, it'll, uh, extract the data. Let's have a look how it works here. I have some kind of invoice. I will, uh, upload this invoice into my app. And here I have, uh, an invoice object created. I have an the PDF invoice attached. Here it is. And uh, I have, uh, the information that it is valid in this factor X format and I read some invoice information. Let's compare this information. We have the invoice number. Here it is, we have the date, 11th, uh oh, 16th of November. Here it is. We have the you payable amount minus 233. Here it is. We have, uh, the sender name and VAT and the client name and, uh, VAT. Here it is. So my tool did not need to read the information, uh, like the text information in the PDF at all. It just found the attached uh, XML file and read this information directly from this XML file. And Here I will open the invoice. I extracted that the attached XML file. Let's have a look at how I do it in my Rails app itself. So, uh, let's start from the beginning. First of all, I have the invoices controller where I have this, uh, import action where I upload the, uh, file or multiple files. And from these files I create invoices with the attached, uh, PDF documents here and going to invoice.org beam, I have attached PDF documents. So an invoice will have attached PDF. And after creating the invoice object, I run this extract XML job and the extract XML job, they'll try to find an attached XML in this PDF. So let's have a look at the job itself. Here is the job. So, uh, as I read in the thing specification, uh, in this file, uh, there can be two valid file formats for the XML, either factor X or extract maybe for legacy Germany documents. And uh, I will, uh, basically take the PDF document content. I will open it with the gem HX PDFI installed this gem in my GEM file. The gel helps me extract the, the metadata from the PDF. So, uh, I run a few different manipulations to find the right document. And uh, if the document has the file name that is one of these, then I'm going to save the XML in a text field in my invoice. So in the schema invoice has, uh, text field XML document. And here is this XML document, uh, field. Here it is. I have extracted the XML. And you see in my app I have all these fields that get the information directly from the XML. I don't have each of them present, uh, in my invoice, uh, object. I just have the XML document from which I extract the invoice number, invoice date, and uh, so on. And, um, let's have a quick look at how this uh, works. So, uh, uh, let's say I'll go to the Rails console. I have the Rails console and I will try to use this, uh, gem hexa PDF to extract the XML from A PDF. So here, for example, have a PDF document in my fixtures, I will go by the file path. I will say file path equals this. And, uh, PDF equals hex, uh, pdf, DF, uh, re I think open. Let's have a look again. Uh, hexa pdf, DF document, document dot, open file path. So we have this PDF object and catalog equals PDF catalog. So catalog has, uh, names inside. It has embedded files, it has do value inside has names inside. It has, uh, this array with, uh, the name of the file and the file reference itself. Names do, uh, last or second, last, let's say. So it is, uh, reference object equals this rough. And we will say P, D, F. So PD, F, uh, object for this reference. And here we find this object attached to the PDF. And, uh, from this we'll find the, uh, let's say EF F, it's not so easy actually initially use the, uh, I think JIT or copilot to find this right reference to dig and find the right uh, x ml content itself. And uh, I will have to say stream, do stream. And here finally I have the PDF content itself. So you see XML version 1.0 and so on. So here's the PDF itself. So I needed do quite a lot of manipulations to get, uh, the PDF out of, uh, of X ml out of the PDF, uh, in the console. And I do it all in this PDF log to ML job. Also, I wrote a few tests. So, uh, test PDF blow to XML job. So I create an invoice, I attach, uh, A PDF, I find the, the attached XML or find the, uh, different fields in the xml. But another interesting thing, okay, we have, uh, attached X ml to the invoice. How do we read the invoice number, invoice date, and so on. There are actually a couple of ways. Here is one example of factor XXML concern. So I use X no XML to pass, uh, the XML field, the x ml document inside the invoice for different information like this would be how I would find the IB, this would be how I would find the, an account name and so on. But the better way, one that I like more is, uh, uh, searching for inside JSO. So here I use Knowledge Jam named Nori, and uh, I passed the XML document with Nori, um, and uh, it kind of turns it into JS O. And already with this Jason, I make some digging to find different fields. So I find the invoice date, invoice number, and so on, so on. And as this a concern I just added to the invoice model. And now invoice, uh, uh, invoice date would be passing in the attached XML file. So let's say the date will be, not this date and pause, but hello, let's refresh have we have this Hello instead. So yeah, this is basically how the app works. I've written some, uh, tests for the jobs for the invoices themselves, and the solution seems to be quite stable. So feel free to use it. It'll be present at Cogo slash factor X reader. And if you are a subscriber to res.com, then you will have access to this, uh, repository. And feel free to use this. And, uh, uh, yeah, it can be a seven stone for you to save some time and, uh, uh, pause invoices trying to find then attached XML and get the information from the XML instead of using different OCR like tools. So thank you for watching and see you in the next one. --- # #179 NEW Rails 8 Authentication Generator: Build Registrations feature - URL: https://superails.com/posts/179-new-rails-8-authentication-generator-build-registrations-feature - Published: 2024-07-21 - Duration: 10:49 - Access: free - Topics: authentication - Playlists: Rails Authentication generator Rails will now have a default authentication generator out of the box! In the previous episode we ran the generator, allowing existing users to sign in. Now let's add a "registrations" feature, that will enable sign ups (creation of new users). I hope this feature gets merged into Rails too! Yet, to make this authentication solution viable in production, you will still need to add AT LEAST "password reset" and "email confirmation" functionality. I highly recommend to use Devise for authentication, rather than reinvent the wheel. Episode source code: https://github.com/corsego/178-rails-8-authentication-generator/commit/b94b002dc7c4c685fb86956cab1fc3e3d9233473 Based on this blogpost: https://blog.corsego.com/rails-8-authentication-registration ## Transcript Hello. So at the moment, rails is working on a new authentication generator, and so far they have added a possibility for users to create a generator to create the users and sessions and allow users to log in via an email and password. And in the previous episode, we implemented this solution based on this pull request in a new Rails application. So it looks like this. I have a homepage that is accessible to anybody. I have a dashboard page that is accessible to only logged in users, and I can log in as an existing user. So I log in, and here I'm logged in. I can visit the, the dashboard page and I can sign out. I cannot visit the dashboard page anymore. And, uh, again, this request does not, uh, give us, uh, a possibility to actually create a user. So to register, it only allows already registered users that have an email and password to send in if the email and password combination is, uh, a valid and let's, uh, add, uh, registrations, uh, to this application to make the solution actually more, uh, viable in a real application. So at the moment, we have, uh, our sessions controller that was created by the generator. And let's create a, a registrations controller to allow you just to actually sign in. So I will cope with the sessions controller. I will name it the registrations, controller registrations, uh, in the roots, I will, uh, create the resource registration. And I'll actually say only, uh, they're going to have only a new and the great action. And for the resource session, we will have, uh, uh, at the moment we have like all the actions, we'll have only, uh, new, great and destroy. This way will not have, uh, root for, uh, control actions that do not actually exist. Okay. So in the new, we will, uh, allow, uh, unauthenticated access to new and create, same as in sessions controller. Um, this looks good in the create action instead of user authenticated by, they're going to use, uh, user.new. Uh, so we'll have, let's say user equals user new perhaps permit email address, and, uh, password. And we'll have, if user save, start, new session, redirect to send signed up. Uh, else redirect to new session URL, uh, with an alert. Uh, uh, yeah, let this be the alert message for now. Let's, uh, see if this works. Let's now go and, uh, create a new view, uh, to let users register a new form. So, uh, we have our sessions new, and in similar fashion, we're going to create registrations, new registrations. And here in the new view, the only difference is going to be they're going to submit the form, not to the session URL, but to the registration URL, email password, sign up. And let's see if this, uh, works. So, uh, I have the server running. I will, uh, go to not sessions new, but Reggie registrations you. Yeah, it should be registration, not plural. Okay. And here I have this form, email and password. Let's try to create a user Sign up. And, uh, I seem to have sent up successfully. Okay, it, uh, seems to be working. Let's try, uh, adding the links to sign in and sign up in the NF bar. So I will go to our application, HTML here. I will, uh, add a link to sign in, uh, new session path, sign up, new registration path. Okay. We have the links to sign in and sign up. Let's, uh, try sign in, uh, in as the user that we have recently created it. Folks, I can visit the dashboard page. I will click on sign in and sign up. And you see these pages are also accessible. And this is, uh, not very nice. Uh, we are actually authenticated, but we still can visit the sign in and send up pages. And if you have a look at our sessions controller or our registrations controller, it happens because, uh, we have allow unauthenticated access for the new action. And this unauthenticated is not defined. It is new. So to make the sign in and send up, uh, actually unaccessible and for, to make this line work in the, uh, for sent in users, we would need to add the another, uh, before action. Uh, before action. That would be let's open authentication dot RB. So we have this required authentication that we are skipping with the saying, allow authenticated access. Uh, but we need to actually say resume session so that we try to find a user if there is found for their new action. So I will say before action, uh, require what was name, uh, resume session, resume session, and only for, Uh, for the new action. So in registrations controller. And the same in, uh, sessions controller. So now I will refresh and you see I'm redirected to the route page. So no more the sending and send up pages. Uh, they're not accessible to send in users anymore. Let's, uh, hide them in the views. So if authenticated, we will not show this, I will put them behind an else, uh, statement. Okay? Authenticated users do not have the, the visibility of the sign in and sign up links. Let's, uh, try to sign out, uh, sign up as the use that we already have created the, in the application. And we have this escalate unique constraint, failed, uh, message. So we are validating the email uniqueness in the database. Uh, here it is, we validating the email uniqueness, but we're not validating it in the, uh, model. That's why we get this error. So I will go to the user model and uh, I will add some validations. I will say validates email. Um, address uniqueness. True, uh, presence. True. Okay, let's try once again, I will try to input this user details. And we get this. Uh, yeah, it isn't try. Okay, let's try once again, I missed something. So signing up as a user that already exists. Yeah, I get, try another email or password error because in registrations, control of it did not, did not customize our errors. So let's say user errors, full messages, two sentence. Let's try once again. Okay. It will sign in. Let's, uh, try sign up as a user that, uh, already exists and we've got this error. Email address has been taken. So errors are rendered correctly. And just in case, uh, for the backend, uh, validation, we should maybe also validate the format of the email. So it can say something like format. And in the format it'll have with the, and then there, there is this helper. Let's try opening it. It would be URI, uh, mail tool, email Rex. So we're going to validate it with this email Rex. This way we can be more sure that the email address provided is, uh, good. Let's again try creating a new user. Uh, yeah, I don't see whether they're in the same, in, in or send up. Let's maybe add this in the views. I will go to new registration here. I will say H one, uh, create an account. Uh, in new session, I will have another H one sign in. Okay, so here, create an account. Create an account, it works. I'm signed up. I will, uh, visit dashboard. It works. I will sign out. I will go to sign in as this account. It works now again, as the same account. I will try to create an account with the same email, email and password. And I get this email address has already been taken error. So everything looks, uh, good and uh, this way have kind of implemented the, uh, both, uh, sign in and sign up functionality into our application. Now, one lasting I don't like is the flash messages that scattered all around. I will remove them from their views and put them, uh, in the shared layout. So here I will have the alert and notice color green for notice. Red for alert. Not putting a lot of mind into this, just a quick solution. Okay? Now we don't have the alerts, uh, anymore in our views. We have them in a shared location. So one final time. Uh, yeah, this, you see if we don't, we have like flash rendered into places. If we will have it just in one place. Let refresh, sign, uh, up. Okay, flash is trending nicely, so everything seems to be broken. Okay? Thanks for being with me and see you in the next one. --- # #178 NEW Rails 8 Authentication Generator - URL: https://superails.com/posts/178-new-rails-8-authentication-generator - Published: 2024-07-19 - Duration: 16:18 - Access: free - Topics: authentication - Playlists: Rails Authentication generator Recently DHH merged a PR that adds an authentication generator to Rails. Currently the generator allows email-password log in for existing users. It does not tackle registrations. Let’s try it out! Episode source code: https://github.com/corsego/178-rails-8-authentication-generator/commits/main/ Based on this blogpost: https://blog.corsego.com/rails-8-authentication ## Transcript Hello, France. For many years, there has been a debate in the Ruben Rails community whether there should be a generator for authentication by default in Rails. And as of now, there is going to be one. So let's see how it works in the European Rails application. Uh, at, at the moment, uh, the code is not yet, uh, available in the latest, uh, stable release of, uh, ribbon Rails, but it's available on the main branch as it has been merged to Main. So I'm going to create a new repository using the main branch. And to do so, I will say rails new, then the name of the repository, let be author and dash dash main to indicate that I want to use the latest, uh, main branch of Rails, but not, uh, the latest stable release. So, um, now I'm generating the application and, uh, let's open it. So cd o code, and if I go to the gym file, I see that I'm using the latest version of, uh, rail. So the branch is, uh, main, uh, I will say gi, the at all GI com. Main message, create app. And now I'm going to generate this, uh, a scaffold, uh, to have one page that requires authentication and one page that does not require authentication. So I will say rails generate actually not discovered, but a controller, let's say controller, uh, it'll be like home controller and there will be an index action. And the dashboard action bo, or, yeah. So index will not require authentication and dashboard will require authentication. Let's create this, uh, uh, controller. Let's, uh, go to our application, go to our roots. The route will lead to home index, and I will get the dashboard to, uh, home dashboard. So this phase when I start the rail server and go to local host, I have home index open by default, and I also have slash dashboard available. Let's add these route to our layout. So I will go to application html dot, er b, and above the body somewhere, I will say link to home route path and link to dashboard, dashboard path. Okay, so here I have this to links and both pages are accessible to anybody. Let's save our changes. For now, I will say, uh, GI at all, gi commit message, uh, scuff, oh no, uh, home controller. Okay. And now let's, uh, actually add this, uh, authentication. So if we go to the usage docs, we see bin rail generate sessions, and uh, let's run this generator and see what it gives us. So it, uh, creates couple of migrations. Let's go and have a look. In DP Migrate, we create a user model that has an email address and the password, uh, session. A user can have multiple sessions, and in the session, the, the IP address and user agent. So we can know that the user is logged in, uh, on a Mac, uh, computer from, uh, a specific browser in a specific location. Uh, and the user can have multiple sessions. That's cool. Then, uh, in our models, we create a current, uh, model where we'll have current session and current user available. We create a session so user can have multiple sessions, and we also create, uh, we update our route. We add resources, sessions and resources. Sessions is linked to the sessions, uh, controller. Here it is, and in the sessions controller. So the new actions should be available to all unauthenticated users. Then, uh, the create action to sign in the destroy action to sign out. And we also have the straight limiting added by, added by default. This is also a new rails, uh, feature. And, uh, allow of unauthenticated access means that, uh, users that are not logged should be able to access the sessions controller. And this is defined. Let's see where this is defined in the authentication concern. So in our application controller, we include this authentication concern. And this is kind of the heart of, uh, the code around authentication added by this, uh, pull request. So, uh, we have a helper to check whether the user is authenticated or not. We can, uh, require authentication. We can, uh, skip, uh, requiring authentication. We can, uh, uh, sign in and sign out. All this, uh, code or the hot of this code is in this authentication concern. So here we have the create method, we have start new user session. It is also defined in this authentication concern. And uh, interestingly, we store the information about the current session in, uh, permanent cookies. So this is a good approach. It's better to store information about the, uh, currently sent end user in the, uh, cookies, but not in the session. Okay, let's try running our application and see how it works Out of the box, let's say rail server, we need to run the migrations. Okay? And yeah, I'm related to sessions new. So at the moment, any page within any page requires, uh, uh, being sent in by it. So, because in this authentication concern, we have this before action require authentication. So it's the same as adding it inside our application controller, 'cause it includes this authentication. So as of now, by default, uh, within any patient, our application requires authentication. Let's skip requiring authentication for our home controller. Uh, yeah, before doing anything else, that's just save our changes. I'll say, uh, Git, add all Git, commit main, uh, generate authentication. Okay, yeah. Now let's make, uh, the homepage accessible for not logged in users. Uh, to do this, I will go to this authentication concern and we, uh, have this, uh, helper allow unauthenticated access. So I will add this to our home controller. So allow authenticated access only index. Let's, uh, refresh. And you see home index is accessible for unauthenticated users. And if I go to the dashboard page, I see the sign in info on the screen. Okay, this is, uh, uh, good, let's try signing in. Let me try, uh, full@bar.com. And, uh, it says to another email passport. Well, I don't have any users, uh, available in the app yet. And actually this, uh, uh, basic sessions generated, it doesn't handle, uh, registrations. It only session handles authentication for existing users. So at the moment, I cannot, uh, send in if I don't have an user. So I will create a user in the console. I will say user dot, create email address will be this, and password will be the same. Okay, now let me try signing in. Will it work? Okay, yeah. I don't have the server running, So I'm signing in and, uh, it must have worked because now I have the home dashboard page accessible. Let's display the information about the current user and sign out link. So I will go back to our application html, uh, B and, uh, say, uh, if authenticated, again, authenticated is, uh, a helper defined in this authentication module. So if we are authenticated, I will display current session. Let's, uh, try this. So we have the current session. We can display the current user email address, and we can also display a link to sign out. So actually it's going to be not a link, but a button button to, uh, sign out, session off and method delete. I think it's delete. Okay, it says undefined method, destroy for nil class. This, uh, happens because, uh, there seems to be a bug in this, uh, pull request, uh, by, uh, the hh, uh, that destroy methods should be accessible only for signed end users. But now, uh, we don't have a user defined and we're trying to access the destroy methods. So I will say, uh, allow only think dedicated access only to the new and to the create actions. So destroy actions should be authenticated to access. Let's try, uh, sign in again. Okay, yeah, I seem to be signed in. I click on sign out and go into the dashboard page. I need to put my login, uh, information once again to be able to sign in. So I did manage to sign out. Let's, uh, try and scan. Signing out seems to work. Let's add some, uh, error messages. So I will go back to this authentication concern. I will go to, uh, request authentication and say, uh, alert, uh, sign in to continue. So now when visiting our dashboard page, I get this error signed into continue, and then signing out. Let's also display a success message that the user has signed out. So, uh, notice signed out, let's, uh, try once again Signed out. And yeah, I don't have the notice visible because I don't have, uh, flash, uh, in our application. H ml B, so I will say equals notice and equals alert. So again, let's try to sign in. Uh, I don't have the messages visible. So, uh, let's try again. Now I'm going to try to send in, I will also add the, some sort of flash message to the successful send in. So, uh, notice send in, yeah, let's, uh, put the notices on another line, the flash messages. Okay, so I'm going to dashboard. I'm signing out. I see the send out message. I'm going to sign in. I see the send in message. Okay, this looks good. We've added some, uh, flash messages for our sessions controller. Now let's, uh, try going to our homepage. And you see if we go to the homepage, we actually don't see the information about the current user. And this is, uh, a bit strange. Why is it? So? Because, um, the home controller, we allow unauthenticated access for index and, uh, allow unauthenticated access. Skips require authentication and require authentication. Has this resume session team. So we are not setting the current session at all for our home controller index action at the moment. And that's not very nice because we want to know on this page that the, there is a signed end user and, uh, let him sign out. We don't want to make the end user visible on only on the pages where he is sent in. So, uh, I'm going to set resume session in the home controller for this user. So, uh, I will say before action resume session only index. And now when I am on the homepage as, uh, sending user, I still see that I'm sent in, I will sign out, I'm going to the homepage. I still have the homepage accessible. Okay, this looks, uh, fine. And, uh, yeah, I think that's, uh, mostly it, uh, on top I would, uh, also recommend adding the, or updating your tests to work well, uh, with this authentication methods. So let's try running our test rails test And let's open our home controller test. So, uh, this is going to be Route U RL response success, and this would be dashboard URL. Okay. And, uh, well, I tried to get the, uh, dashboard path, but I was redirected to sessions new. So this is, uh, uh, good. Uh, I wasn't let to access the dashboard URL, but uh, I need to update the test to make it a success. So let's say, uh, assert redirected to new session URL. This is good. Now let's, uh, post, uh, session UL uh, email password. Uh, let's create a user for this. So I would say user dot create email address would be full@bar.com. Password would be, yeah, let's say password. Now I'm going to say post, uh, session url, email address full@bar.com. Password, password assert to response. Uh, success. Let's see if this works. Okay, uh, it was a direct, so let's say assert, redirected to dashboard, URL. And, uh, let's, uh, yeah, let's say follow, redirect And assert response success. This is good. Now let's also test signing out. So let's say, uh, delete session, URL. Uh, and again, let's get the dashboard URL, and it should, uh, direct us to the signin path. Uh, yeah, I made a small typo, but yes, this is basically it. So, uh, we have tested the unauthenticated access. We have tested the authenticated access and, uh, signing out, uh, uh, for this dashboard path. And that's, uh, basically it. I think, uh, this, uh, addition is going to work really nice for new ribbon rails applications. And, uh, I'm sure this code will be improved and the some bugs will be fixed and it'll be more mature by the time Ribbon Rails eight is uh, released. So I'm super excited about this new, uh, authentication generated in ribbon rails. See you in the next one. --- # Tailwind on Rails #177 Responsive Collapsible Sidebar - URL: https://superails.com/posts/tailwind-on-rails-177-responsive-collapsible-sidebar - Published: 2024-06-30 - Duration: 09:58 - Access: free - Topics: tailwindcss, css, stimulusjs - Playlists: Tailwind on Rails Let's add a button to minimise (collapse) the sidebar. When the sidebar is minimised, only link icons (not labels) are visible. We will also save the sidebar state across requests. Based on this blogpost: https://blog.corsego.com/tailwind-collapsible-sidebar Episode source code: https://github.com/corsego/138-tailwind-layout/commit/280d649360359de1c346741d130d7cd49c9745d2 ## Transcript Hello friends. This is going to be another video in the VIN on Rails series. And recently I received an email request from Aruba Friend to create a responsive sidebar that you can minimize. Uh, and, uh, if it is minimized, you would see only icons. If it is not minimized, you would also see, uh, text. And if you refresh the page, you, the sidebar keeps its minimized state, uh, uh, based on cookie, on something else. Actually, I have previously implemented the similar functionality as, uh, drawn here in a real application. Here it is. So, uh, I have this, uh, toggle button. Uh, here it is on the bottom of the sidebar. If I toggle it and refresh the page, it'll, uh, stay toggled. If I, uh, navigate anyway, it'll still stay toggled. If I un toggle, it'll also save the state. And you see, when it is un toggled, I see not only the icons, but the text. And when it's toggled, I see only the icons. And here is, uh, more as the final result that they're going to have today. So here's the application that we've been building in this Vin Angel series. Here we have a sidebar and I have a couple of links and a toggle button. If I click on the toggle, I don't see the text of the links. The sidebar is minimized and I see just the icons. If I click un toggle, you'll also see the texts. So let's build this kind of application. First of all, I'm going to navigate to a branch where I still do not have this, um, implemented. And, uh, here is the initial state of the application after these seven episodes. Okay, so we have a sidebar. Let's open our sidebar, uh, application. H tml. We have layout slash sidebar. Here it is. And let's first of all do the CSS. So first of all, I will have a data attribute. Data expanded equals true. And now we're going to have some conditional CSS. So let's say if data expanded equals through, let's change the background of this to green. So I will, uh, say, uh, uh, class equals, and now we're going to do some, uh, CSS, uh, magic. So, uh, we'll have data expanded equals true under score and, and will have background green, uh, 400. So if data expanded equals true, they will have a green background. Let's see if it works. So now it is green. I will set the data expanded to false, and now it is not green. And this is the principle based on which we're going to do all the CSS changes. Now we need a way to, uh, change the data expanded from true to false and vice versa. So we'll need to add a button. We're going to do this with stimulus js. Let's create a controller rails, generate ti sidebar. Let's run the server again, bin dev. And I will open sidebar controller. And uh, here I will, uh, uh, have A toggle action toggle. We'll have an event. And let's add the A button for this. I'll add it in the bottom. I will have equals button to, uh, Neil, I want to redirect anywhere, uh, data, uh, action, uh, stimulus controller name sidebar. And the action will be toggle, uh, do, and here I will have, uh, that text toggle. And next to it I will have an icon to minimize. Okay, let's see how it looks. So here I have this toggle button, and then I click the toggle button. I have a full page of direct each, uh, time. And I should be also hitting the sidebar toggle. So I don't want to have the full page of direct. I will say, uh, prevent default. So event prevent default. And now when I click on toggle, I don't have a full page. Uh, I shouldn't have a full page direct. Something is, uh, uh, not right. Sidebar, toggle. Okay, refresh. Let's see. Actually it is a rookie mistake. Uh, it does not, uh, listen to prevent default because I did not, uh, set data controller sidebar. So I'm going to set it here. I will say, um, data controller equals sidebar. Now let's, uh, refresh the page. And here, now I click on toggle and I don't have a full page refresh. Okay. And now I'm going to change the state of, uh, data expanded on the data controller element on this place where it is defined. So I will have, uh, uh, constant new state equals this element dataset do, uh, expanded. And, uh, if expanded equals through, they will set it to false. If it is, does not equal true, they'll set it true. And I will say document hook key equals side by expanded new state, I don't need to specify the path. Uh, yeah, this should, uh, work. Uh, and we can log it in the console, console log, uh, document cookie or better, just in new state. New state. Okay, let's click on toggle. And you see it changes from no, it only sets to true, uh, via that. Well, we actually want to also update, uh, the data expanded to be the new state. We should not only update the cookie. So I will say this element data set expanded equals new state. Let's try again. I will click on toggle. And now, yeah, it toggles from true to false, so looks good. And you see each time I toggle the color of the side, but topo changes. So looks good. I will refresh the page. And after the page refresh, you see it still goes back to the initial state that was, uh, set to true here. So instead of having this hot coded, I will have, uh, cookies. And in the cookies we have our sidebar expanded attribute. So we'll have this one or true. So it'll be either true false set from the cookies or true by default. Uh, let's clean this up to be multiline so that it's more readable. Okay, let's try once again. So now I will click on toggle. I refresh the page and it stays screen. I will click on toggle. Once again, I refresh the page and it stays, uh, uh, without a color. Okay. And based, uh, on the this, uh, based on conditional setting, the data expanded, uh, and updating the CSS based on this, we can, uh, hide or show elements. So let's, uh, start with the toggle button. If, um, uh, it is toggled, we will show only the arrow. If it is not toggled, we'll also show the text. So let's say, uh, uh, I put the text in span and I will add class. And let's cope with this. So if, uh, data expensive, expanded equals false, we will add the hidden class. Let's try again. So I have it as false. Now I have it as through now. And you see it's visible. Let's also have this, uh, icon conditional. So, uh, we'll have, uh, the icon and, uh, yeah, another icon for the un toggled state. Let's, uh, okay here, this fault. And, and here it should be true. So, uh, yeah, now it is not toggled and now it is toggled. And you see also the icon changes. Uh, let's, uh, clean it up. We don't need decide what bottom text. We don't want to set the fixed width on the side, but we want to actually make it smaller. Then there is less content. So I will remove this, uh, fixed width. And you see now this, uh, the biggest sidebar, I will, um, make it smaller. It doesn't become smaller because we have the sidebar top text. Let's, uh, hide it or we can add the, another link here. Now let's try to hide it. So yeah, here we have just the sidebar toggle button. I toggle, I see only the button. I un toggle. I see the button and the text. And when I refresh the page, the state is perceived. So looks good. And in the similar way, you can do it for all kinds of other links. For example, link to homepage, let's say the link to route, uh, path. We don't need any data attributes. I will have, uh, a house here And, uh, home here. Okay. Uh, I will remove this, uh, conditional coloring. Um, and yeah, I will have a home visible in both uh, cases, but here will also have the text. So here we have, uh, a house and home. I will click on toggle and I have only the house. And you see the sidebar takes as much space as it needs in both cases. But, so I'm hot code in the width, but, uh, I, uh, have text in the un toggle version and no text in the toggle version. So that's about it. Basically, we added a stimulus control that, uh, changes the data set, expanded from true to false. We tried it to the cookies. We have conditional CSS that based on the data expanded attribute, uh, shows or hides different elements. That's it. Thanks for being with me and see you in the next one. --- # #176 Sign in with Apple. Omniauth. Social log in with Devise - URL: https://superails.com/posts/176-sign-in-with-apple-omniauth-social-log-in-with-devise - Published: 2024-06-30 - Duration: 14:31 - Access: free - Topics: omniauth, apple Add a "Sign in with Apple" button to your app! Step 1: Create an apple developer account ($99) Step 2: Generate the API keys Step 3: Fix gem omniauth-apple The video covers omniauth with Devise. If you are not using devise, read this blogpost: https://blog.corsego.com/apple-oauth Episode source code: https://github.com/corsego/176-omniauth-apple/commit/2de259bb42b5161f6766aa567120fccb62541a2b Create apple identifiers here: https://developer.apple.com/account/resources/identifiers/list Gem omniauth-apple: https://github.com/nhosoya/omniauth-apple ## Transcript Hello friends. Previously I have created multiple videos about social login or auth, and now we are going to add auth with, uh, apple. So here I have already added this, uh, auth with Apple in superl.com. Let's see how it works. I will click on the sign in with the Apple button, and as I'm using uh, Mac, it will automatically show the native, uh, apple ID to authenticate and sign in. So, uh, here I have passed the authentication and, uh, voila, I have signed in my via my Apple account. Okay, looks good. Let's see how you can do it in your application. First of all, the hottest thing you need to get the Apple API keys to do. So you need to enroll to the Apple Developer Program. So you go to this page and you click on start your Enrollment. Next, you have to create an Apple ID and pay $100. Actually, it costs $100 per year to keep your Apple developer ID active. So when you have created the id, you can go to your account page right here and you will go to certificates, IDs, and profiles. So you will click on, let's say certificates. It'll take you to this page and you'll need to go to Identifiers. First of all, you will create, uh, an app id, let's go to continue app. And, um, so description, uh, devise, uh, uh, demo out. And bundle ID would be, uh, usually the reverse of your domain and the app name. So it's say if I using super.com, I would have, uh, uh, com Super Rails, uh, device Al. And here in the, uh, capabilities, I would need to go down and add the sign in with Apple. You don't need to click on edit here. You can do it, uh, later. You don't need to edit here. Okay, you just check this checkbox and you click on continue. Now, just to confirm, you click on register. Okay, so we have created an identifier. This is an app id, and now we need to go and create a service ID. Here, I don't have, uh, this service id. I have an old one that I created previously. I will go to identify us, create a service Id continue, uh, description, uh, device, uh, al demo service, id, identify again com, super rails, uh, do device, uh, uh, alpha Author service, continue and register. Okay, so we have created this, uh, service id. Let's click on it. And we need to add the send in with Apple and click on configure. So domains and sub domains. You can add your rule production domain, but uh, if you want to make it work in local host, you can just use local host. You would need to use something like, uh, nro. Uh, I've created a video about NRO previously so that you will get a virtual, uh, URL that points to your local host here in my example app that I have already just installed. Device I am running, uh, nro, and this is my URL. So, um, let me, uh, add the CRL and the return URL is going to be slash users slash al slash apple slash callback. Uh, next, okay, this has to be without HHPS and here it has to been actual URL. Okay, let's click on, uh, next. And, uh, yeah, before clicking, uh, done, how can we verify that this is the right, uh, URL? Let's go to our roots of our application. Yeah, actually I have add basic device applications run in. So I have like just a simple app where I can, uh, let's say, uh, log in. So I'm not logged in. Now I can just visit the homepage. To visit the dashboard, I need to log in and have the normal device, email and password flow. I don't have any authentication yet. So let's have a look at the route here. Let's say rails roots grab, uh, apple, we don't have an Apple route yet, but if we go to user RB and say Omni Apple and, uh, omni providers will have Apple and run the rails roots grab Apple. Once again, it's going to give us an error. Uh, why? Because we didn't add the Apple, uh, authentication gem yet. So let's add the Apple out gem. It is this omni alt Apple gem. Let's, uh, add the gem to our gem file. Now we run, uh, bundle. Let's check the route once again. So it has added the route, uh, slash users out Apple callback. So let's, uh, verify it here. Yes, it's the right, uh, URL. Let's click on Don. Okay, and continue. Now we have, uh, this app identifier and we have checked the, the sign in the Apple. Okay, save. Okay, and now we need to add the keys. So we'll go to Keys and create the key. So device, uh, uh, demo out. Uh, I will click on sign in with Apple Configure. We will select the app that we have just created, save and uh, continue register. And we have the key ID and we have the security key. We can download the secret key. Let's, uh, show it in finder and we can actually drag and drop it into our editor and see our private key. Okay, so, uh, this is good. Let's click on Don and let's go and, uh, add the rail spot. So we have already added all the, uh, authentications we need in the Apple pod. We've added the, uh, app id, the service id, the keys, and now we can add the gem itself. So we've added the gem, Omni Apple, and inside our, uh, device.org, we can find omni auth and uh, uh, providers. Let's see. Uh, yeah, so here I'm going to go with these, uh, keys and I'm going to say contact Omni Auth Apple, and the client ID is going to be the service id. So going to identifiers, service IDs, uh, we are going to call it this identifier. Yeah, this identifier. Okay. Then, uh, team ID is going to be this id, it's o at copy pasting. So, uh, this is the team id. Next thing is the key id. So I will go back to our keys. Uh, the key, this is the key ID And the PEM. So it is this one. Let's copy it and, uh, put it inside. Okay, we have the key. Let's, uh, see what's next. So, uh, we have added the omni health keys. We've added the callback. And let's, uh, go back to our user B if we have added the providers Apple. Okay, we have most of the setup done. Let's see if it works. I'm going to run bundle once again and start the server rails server Again, as a reminder, I'm running the on clock. Okay. And you see now we have the sign in with Apple button because we added the, uh, apple Omni provider inside our user rb. So let's click on this and, uh, if, uh, all the credentials are correct, it might, uh, work, but, uh, okay, could not authenticate from Apple because authenticity error. So to fix, uh, this error, I've only to install, uh, the gem that you need now for all the omni health providers, gem, uh, omni Health Rails, CSFR protection. So this gem should help us with the fixing the authenticity error. Let's start the rail server once again and click on sign in with Apple. And here we have this. Let's click on, uh, sign in, and we have this invalid authenticity, uh, token. So, uh, to fix this, uh, error, uh, what would we need to do? Uh, one thing we definitely need to do is, uh, so you see, uh, in the gem, uh, pull requests, we see there is, uh, this, uh, error. Uh, I mean this fix to a problem that error is having. And basically the gem doesn't work correctly without, uh, this, uh, change. So we would need to set the nos to local. So I'm going to use not the gem itself, but I'm going to use, uh, the GEMS version from B fogal. So, uh, I will say, uh, uh, GitHub b fogal, uh, omni out apple and brunch. It'll be, uh, fix apple session handling. Okay, uh, let's run bundle once again. Uh, okay, it's not found. Yeah, it should be slash So now we run bundle. Okay. And inside our device, a b we'll need to set the nons to local and, uh, raw, wider, ignore state to true. Okay, to true. Uh, let's, uh, start the server once again and let's, uh, go and sign in and see if it works. Okay, let's continue. Uh, And, uh, great. So, uh, we have fixed all the onal problems, and now we try to hit the device Onal Callbox controller, and, uh, it says the action Apple could not be found, so it actually does the authentication part, uh, but we need to actually, uh, add the part inside our app, uh, to handle the Omni Out request. So I'm not going to go through the Hulu process now, I've already done in previous videos, but basically I'm going to add the, the Omni ath callbox controller inside our application. So I will go to, uh, slash controller slash users. Here. I will create omni ath Callbox controller. I will, uh, copy the controller here. I will rename it from Azure to Apple. I don't need this provider and your ID. So I have, uh, the Callbox controller that should, uh, handle the request and, uh, find the user from the, uh, uh, information received from the Apple, uh, payload and find the user or create the user and log the user in and direct to the application. Uh, this is fine. Now we need to go to our roots and say that we are using this controller. So controllers, onar, callbacks slash users, Onar callbacks. Okay, let's, uh, start the server once again and, uh, try to sign in with Apple. Okay, we get this invalid authenticity to token. And, uh, for Apple specifically, we would need to skip, uh, before action verify authenticity token. Uh, other omni health providers usually send, get requests, but Apple also sends post requests. That's why we need the, to skip this before action. Let's, uh, try once again. So log in And we have signed into our account via Apple. So Luke's file. So, uh, to summarize, initially, we need to, uh, sign up to the Apple developer program, get all the API keys afterwards. We need to add the, uh, right versions of the gems, uh, that work. We need to add our keys to device RB inside user RB via add Omni Providers Apple. Uh, in our route we say that we're using Omni Callbox controller and the Skip Before Action Token and Sign Infinite Apple. So that's about it, and, uh, thanks for being with me and see you in the next one. --- # #175 Sell access to a Github Repository. Sell your code! - URL: https://superails.com/posts/175-sell-access-to-a-github-repository-sell-your-code - Published: 2024-06-24 - Duration: 22:11 - Access: free - Topics: stripe, GitHub Allow users to pay for your product via Stripe Payment Link. You can place the payment link on any website. When a user pays, a webhook will be sent to the Rails app. The webhook will grant the user access to a private GitHub repository. Start selling your code now! Based on this blogpost: https://blog.corsego.com/github-api-collaborators Episode source code: https://github.com/corsego/175-github-api/commit/f739888032d4e2af64f56b2efa49eeb05ff79e80 Stripe gem: https://github.com/stripe/stripe-ruby Github API gem: https://github.com/octokit/octokit.rb ## Transcript Hey, as a developer, you might be thinking, why should I build a product and sell it if I can just sell access to my code? So in this episode, we are going to build an application where a user can make a payment via Stripe, and he'll automatically be granted access to a private GitHub repository. Here are a few examples of how this can work. For example, jumpstart, you can pay a subscription or pay once and after paying, you will get access, uh, to a private GitHub repository. Similar with Sidekick, there is a free version of the G and then there is a premium gem that you can pay a subscription or just a one time payment for. Similar with the, you can, uh, access the free, but there is also a premium option with the additional features. Also in Super Rails, if you are a paid subscriber, you will also get access to the Super rails.com source code. How does it work? Basically, you uh, click on subscribe, and after the payment is, uh, processed by Stripe, you will, uh, be granted access to a private GitHub repository. Here is a very interesting example from the next j js and file that actually inspired me for this video. Uh, this here is just a one pager. It is just like a landing page with a pay now button. If you click on the Pay Now button, you will be redirected to a Stripe uh, payment page. And after processing the payment, you will be crunched access to a private GitHub repository. That's it. And just with this small product, this, uh, guy seems to be doing quite well in terms of sales by just selling access to a private GitHub repository. So let's see how we can do this. And they're going to do it on an example of, uh, basically any, uh, private GitHub repository here. I have a private GitHub repository, and, uh, I will get, give access to any user that, uh, pays, uh, via Stripe. Why use Stripe? Why not use something like Gumroad? Uh, because Gumroad is an additional middleman that is just going to provide you this landing page, and they're going to take 10% commission on top of the Stripe Commission. So look, Gumroad is taken 10% flat, but if you go lower, what are the fees? Let's go to what are the fees? There is the Stripe fees. So, uh, you'll be paying not only for Stripe, but also for Gumroad. So in this, uh, episode, they're going to build the basic, uh, page. Uh, we're not going to do a lot of style, but it'll look more or less like a page with a pay now button. And, uh, when the user, uh, checks out and the payment is completed, then we are going to use the GitHub, API to give the user access to a private GitHub repository. So we are going to start with a fresh, uh, Stripe organization. I've just created the Sell Fast Organization, and, uh, I'm going to start with a Fresh Rails app. So I have just created this, uh, repository. I named it Cell Fast. And, uh, let's run the app. It's basically an, uh, empty rails application at the moment. Uh, let's, uh, create a basic landing page. I will say rails generate controller, uh, home index. So it'll be a controller named home with an index action. I will go to our roots and drag the route to home controller, index action rail server and have, have a, a basic landing page that we can tell however we want. Now, I'm going to go to Stripe and I'm going to create a product. So, uh, let's say we're going to sell access to our private repository for 1000, uh, dollars. Let's make it 999. So, uh, super ails access, uh, code access. It'll be a one off payment. It'll be in dollars. Let's make it 999 add product. So I have created a product, and now I'm going to go to payment links. This is actually the easiest way to add the external checkout, uh, in, uh, use use using Stripe. I'm going to select this product that I have just created. So I'm creating the checkout link for this product. And, uh, advanced options, this is important. I'm going to add a custom field, and, uh, I will name it GitHub, uh, username. Let's, uh, yeah, that's GitHub username, so that it looks nice here. So the user should input his email, his GitHub username, his card details. Let's create the link. And here is the URL. I will go with this link and put it on my, uh, home index page. I will add the equals link to by now. I will add the link and the target blank so that it opens in a new tab, not the same tab. Okay, let's go back to our application. I have this Buy now button. When I click the button, it opens the stripe checkout. I can input the email. Let's be like full, uh, m.com, whatever. Uh, a valid demo code. So 42 42 always works. Let's click on pay. It is going to process the payment, uh, in this test version. It'll be successful with this card number. If it go back to our Stripe customers, I see that the customer has just, uh, been created. I think this might be the one, uh, but we did not give access to the source code. Uh, we need to add and creation to the GitHub, uh, API. And, uh, first of all, before doing this, we need to let our application listen to the Stripe back webhooks. So it's going to work. So the user has a link to pay on Stripe. He pays, Stripe sends our application information about the payment, and based on this successful payment information and the username from GitHub, they're going to grant access to GitHub repository. So step number two is going to be, um, processing stripe web hooks. So for this, I'm going to install the gem stripe, gem stripe, uh, bundle. Now I'm going to find the gem stripe on GitHub, and we need to add an initializer. And, uh, in the initializer we're going to add the stripe, API key. So going to Initializers, inside config, I will create stripe rb. And here I will add the stripe secret key. So to get the secret key, I'm going to go to the a sentence, API keys, reveal the secret key and copay paste it here. Uh, now let's start the Rails console and see if we can access the stripe. A PII will say stripe price dot list. And we do see the price that we have just created. So $999 zero 0 cents. And we said that these are dollars, uh, because the currency is USD. Okay, uh, so we can access the stripe, uh, API in our console. Now let's listen to Stripe web hooks. To do this, I'm going to go back to the developers, open the web hooks, and, uh, yeah, add a local listener. So first of all, I need to install the Stripe. CLI, I have already installed it, so I'm going to go to Stripe login. Okay, I'm going to log into this organization. Looks good, allow access. Uh, next I'm going to listen to webhooks. Uh, I'm going to listen to webhooks on Port 3000. And I need to add the webhook route to listen to. So, uh, I'm going to go to our roots and say Post, uh, webhook, because here I'm listening to slash webhook, uh, tool webhook grade. That's called webhooks grade. So let's create the webhooks controller. I'm going to go to controllers, a webhooks controller, RB plus web hooks controller that inherits from, uh, application controller. And, and they're going to have a great action. And inside this great action, I'm actually going to go with a Sinatra code. Let's, uh, go with this example and paste it here, and let's clean it up so it folks in inside our rails controller. First of all, I will delete all these extra lines. We don't need to require a stripe 'cause we have already installed it as a gem. The stripe, API key is in present in the stripe initializer. The endpoint secret will be inside the dev crate. It's fine. We don't need to set the port, we don't need to set this post. Um, and we not don't need to return the status. So, uh, this should more or less, uh, work. We have a webhook endpoint, and let's try sending something to it. I'm going to start the rail survey of the window rail server. So here I've got the listener here, I've got the rail server, and in a third tab, I'm going to try to send a web hook. So I will go with this web hook. Let's trigger a web hook. So here we see we had a post request. The response is four to two, and it is some error inval authenticity token. So for this webhooks controller, I'm going to, uh, skip verification of authenticity token so that we can, uh, accept, uh, both requests, uh, from, uh, outside our application. Let's try once again. Okay, here it was a successful, uh, request, and here it was, uh, also a successful request. Let's try putting, uh, binding inside, uh, the payment intent succeeded. So let's say binding B, let's sell, send another request and we can, uh, explore the event object. We can explore the event data object that is the payment intent itself. So it works well. We can, uh, listen to Stripe webhooks. Next point is going to be to, uh, find the GitHub username that was submitted. Let's, uh, click fit and start the server on scan. And let's try, uh, doing a real, uh, checkout. So now, uh, again, I'm going to click on the buy now button. Let's input an email, a GitHub username, let's say yaro one to three card information. I will, uh, before clicking pay, I will, uh, uh, listen not to payment intent succeeded, but to the checkout, uh, session completed. So then checkout session completed, because when that checkout session is completed here in the payload, we'll have the GitHub username. We'll not have it inside the payment intended succeeded. So I'll put the band in here instead. Let's, uh, click on pay. And let's look at our console. So, uh, here we are inside checkout session completed, and we are in the binding. Let's get the event. Uh, let's try finding the GitHub username. And we actually have it here. Uh, we see there is a value Y 1, 2 3. So let's try exiting this, uh, value. We have, uh, event data object, uh, custom fields, and it can be an area of custom fields. So we're going to find the one that has the, uh, key GitHub username. So I'm gonna say find, uh, inside find. I will have, uh, uh, like object. We're going to run the one, but object key equals GitHub. Username, I'm doing not just, uh, custom fields dot first in case there can be multiple custom fields. So let's find that this one, if it is present, and it gives, uh, just this, uh, element outside of the array, they're going to say dot, uh, text and dot value. So this is how we can get this, uh, custom value of the GitHub username. So let's say GitHub username equals this big line. Okay? So, uh, we approaching the strive webhooks, and if there was a successful payment, we, uh, find the GitHub username that we want to give access to. So the next step is going to be to actually integrate the GitHub API and, uh, give the user access to our code base. Uh, let's, uh, install, uh, the Octo Kit GEM OK kit, uh, Ruby Gem. So this is a strange name of a gem, but it is actually the gem for, uh, working with the GitHub, API. So, uh, I'm going to add the Gem Okta kit to our gem file and run bundle. And now let's try, uh, playing with this gem a bit. So, uh, let's, uh, open the auto kit client, but we need an access token. So let's, uh, see how we can get a GitHub access token. Let's open GitHub. I will go to settings here, develop a settings, personal access token, find trained tokens. I will generate a new token. As you see here, I already have a token for super rails to, uh, grant access, uh, read and write access to other repositories. So now we are going to add a new token. Let's say sell fast. And description is, uh, give access to invoice app. Uh, only select repositories. Invoice app, and account permissions are going to be, uh, no, actually not account, but repository permissions are going to be administration. So read and write. So I'm going to letter this API token, uh, give access and take away access, uh, from GitHub repositories. Let's generate a token. Have, have the token. Let's scope it somewhere. Um, okay, now let's try interacting with the API, uh, in, uh, our console, the GitHub, API. So access token equals this token client, uh, client user dot login. Okay, so we get the information about the user looks good. Let's, uh, try get information about, uh, the repository, uh, collaborators of the repository that want to give access to, let's say client dot, uh, collaborators. And we're going to need a repository path. The repository path is going to be, um, OV slash invoice app. And here I see that, uh, two GitHub accounts have access to this repository. Let's, uh, have a look at it. Uh, once scan, I will go to settings, collaborators. And you see there is me and there is also Adrian who has access to this repository. Let's try, I programmatically give access to another user to this repository. So, um, I will say, uh, client dot add collaborator. The re Oh, no, let's try and scan client, add collaborator, uh, OV slash invoice app. And the username would be something like, let's say secret Prey. Let's give this user access. So I will click enter. It gives me true, let us refresh the page. And you see, this user has been invited to access the repository. Okay, so we managed to programmatically, uh, using the GitHub, API give access, uh, to a GitHub repository to a specific username. Let's try, uh, once again, I will delete this use access, and let's try integrating this into our Stripe checkout flow. So, uh, uh, here I'm going to start the GitHub client. I will say, uh, GitHub client equals, uh, auto Client New Access Token equals, uh, this Access token. And, uh, now I will say GitHub client dot add collaborator. Uh, the repository name is going to be, let's put it somewhere else, like repo path equals slash invoice app. So they're going to add the access to this repository path to this, uh, GitHub username that is provided in the checkout. And that's, uh, that's it. Let's see if it works. So, uh, once again, I'm going to start the rail server Rails s basically the Rails app is needed here only to process the web hooks. That's, uh, it, so, uh, let's go to buy now. Let's just double check. Only me and Adrian have access to this repository. I will input an email, uh, GitHub user name, uh, called information, uh, And I expect to receive, uh, a web hook from Stripe and, uh, make a request to GitHub to give access to the repository. So, uh, if I just refresh the page, you see Alexander has actually been added as a collaborator to the repository. And, uh, that's basically it. In our case, we need the Rails application only to process the web hooks. We only need the web hooks controller, uh, the route. And that's it. You can, uh, put a pin now, link anywhere on any website, and, uh, uh, just this way, let's, uh, just demonstrate it once again. One last time. Uh, let's find some, uh, GitHub user like Marco Roth, for example. Let's try giving him, uh, access. So I'm going to create a new checkout. I'm going to go to buy now. So I put in this GitHub username. I click Pay Stripe will send the web to our application that there has been a successful payment and that we should give, uh, access to the username. And if I go back, uh, to the repository, you see the user has been invited to the application. And just like this, you can, uh, uh, use Stripe, uh, payment links and GitHub API to sell your code. That's it. --- # #174 i18n. Test translations, refactor, and launch into production! 🚀 - URL: https://superails.com/posts/rails-174-i18n-test-translations-refactor-and-launch-into-production - Published: 2024-06-16 - Duration: 06:52 - Access: free - Topics: i18n, translation, internationalization - Playlists: i18n (internationalization) The feature is ready, but the code is not production-grade yet. Let's refactor the code and write some tests for translations. This way we can go from "good" to "great"! 💪 ## Transcript Hello friends, and this is the final episode in our translations miniseries. So in the previous episodes, we let our guests and users like their language in the nav bar and we also added translations into Spanish using the open AI and the gem item and tasks. And now as a final step, let's turn our code on translations from good to great and make it ready to be merged into production. So first of all, let's have a look at our application here. Something I see visually, hey, some translations are missing daytime distance in words X days. Now this is a rail specific translation of the method distance of time in inverse or time ago inver. And to fix this, I would need to use the gem rails 18 M Before I add this gem, I will, uh, think, look, I see this error with my eyes, but uh, if I run my tests rails test, I will not see this, uh, error. Why? Because, uh, I need to enable error detection in the development for the translations. So I'm going to say config it and, and raise on missing translations. Now I've restart the server because I have changed something in and initializer, let's run again. And you see I get this error missing translation data. I will also get it if I run my tests. Uh, so to fix this, I'm going to just add the gem rails ITNM for my rub rails version. I will add it, uh, globally and run bundle. Now run bin dev. Once again, refresh and application works. So all the real specific translations have been added and we don't have to manually add them in our translation files. Okay, this is very good. Now let's have a look at the, our application controller. You see this is, uh, translation logic and uh, we don't want to clutter our application controller over this. We can move it into a concern just as we did with our authentication. So let's create another concern. I will, uh, call it, uh, translation, uh, the same pattern as authentication. I would have named it I 18 N, but I will not because 18 n is reserved. So it would give us some crushes. So in two translation, I will, uh, cope with this concern. Name it, uh, translation here again, uh, the name of the module is translation. And let's, uh, clean it up. So this is an, uh, empty concern with the name translation and I'm just going to cope with the code from application controller. So included include the before action. And we are going to add this to private. So everything should work just as before. But now our application controller is much cleaner and we separate the authentication logic where set current user and the logic for translations into separate concerns. Okay, this is good. Let's, uh, just, uh, run our, yeah, have some kind of jobs running. I guess. Lemme stop this bin Dev. Okay, the application is running as before I will change the language and it seems to be working. Let's run our tests rails test and talking of tests we have not written and need test for our translations. So let's do this. Now. A good place would be our maybe controller test. So test controller, but we don't have an actual controller for translations. We keep it in application controller. So let's do it not as a controller test, but as integration test. I will name it translation test. And I can actually control, cope with something from a controller test because the logic is going to be same as in a controller test. So here I will have class, uh, translation test. Let's see. Our test should run successfully previously, and I will say test guest changes locale. So I will get root path locale. I prefer it to be a symbol. Uh, I don't want to assert success or something like this. I will actually assert that we find the, the specific text. So getting the root path in English, I want to see this text. And in Spanish, I want to see this text, but I don't want to hardcode, uh, these, uh, in my test I will actually try to find the, the line in the translation file. So looking at our landing page, it is static landing page title. So I will say, yeah, and this is, uh, I think head of one. Yeah, H one. So I will say, uh, a third select H one, uh, i 18 n dot translate static landing page title and locale will be EM. And I'm going to do the same for Spanish. Let's see if it works. Let's just run only this file. So it'll be foster Rails test, just this file. Okay, success. Let's make it fail. I will put em here. Then actually we have set Es. Okay, so failure works. Let's also test the user changes locale. I will say that we'll sign in user, yeah, this way it'll be faster than you GPT or uh, compiled t. But it's good to have, uh, both, uh, tests when a guest and when a user sign in because in our translation, we have both cases when we have a user and when we don't have a user. And talking of this, uh, currently we set the locale in our session, but uh, if we are doing a PWA or a native app, it would be better to store it in the cookies. So cookies locale, and cookies locale. Let's run our tests once again and everything is working great. So that's basically it. Now our pull request is ready to be pushed into production. Thanks for being with me and see you in the next one. --- # #173 automate translations with AI - URL: https://superails.com/posts/rails-173-ai-automated-translations - Published: 2024-06-16 - Duration: 11:21 - Access: free - Topics: i18n, translation, internationalization - Playlists: i18n (internationalization) Previously we enabled language selection for guests and users. Now let's actually translate the YML files from English to other languages. There are many tools to manage translations. Some can be crazy expensive! Here's how AI saved us EUR 9000/year on translations. Text version: https://blog.corsego.com/i18n-translation-tools Source code: https://github.com/yshmarov/superails/pull/251/commits/e91f8c01a4854b36928bcfcd2eabaec9489a5d53 Gem i18n-tasks: https://github.com/glebm/i18n-tasks ## Transcript Hello guys. In the previous couple of episodes, we learned how to translate our application. So let use a select their preferred language. In this case, I have selected English now, so I see everything in English. I select Spanish, and I see puzzle application in Spanish, but not everything by, because here you see, I have the playlist text and posts, and if I go to enl, I have the N bar translated. But if I go to esl, I don't have these translations. I have just a few keys translated into Spanish. So only day I've been displayed in Spanish and everything else defaults to my English translation. So how can I, uh, translate everything into Spanish nicely? Now, there are actually different approaches to translate in our applications, and there are lots of paid tools for this. Also. For example, there is, it is, uh, kind of a JavaScript plugin that, uh, translates, uh, your application text before render. So before it is rendered, it translates into the selected language. And actually, I tried using in one of my, the companies I used to work for, and it was just awful. It is not compatible with Turbo. It doesn't work well without Hot Page refreshers or if it's not, uh, another JavaScript technology for refreshing the pages. Unlike Turbo, I joined the Slack. I tried to talk to them. It did not work. It has face time and money. Also, at a recent company we used Phrase, and actually I've seen people on Twitter recommend phrase, and I don't like it at all. It's also bad. First of all, you need to initially add the keys and the translations in phrase, and then you pull the translations from phrase into your application. And the worst thing in the bottom is their pricing. So here is a list of, uh, bills that we used to pay Phrase, we're paying around 200 euros and then spontaneously without any changes in our usage. They just increase the price to a fixed 750 euros. It is more than a 350%, uh, increase. It's just crazy. So this way they will be just paying phrase to store our, uh, ven files, uh, 9,000 euros per year. This is just crazy. So as started thinking, what are other ways to translate our application and how can we use AI to do this? First of all, I found this Gem Instant I 18 N by ob. And this is a guy in the Rubic community that talks a lot about AI and um, some AI tools. So how does this gem work? Uh, you add the gem, you add your open, uh, ai, API key, and it works more like this. So, uh, if I go to my landing page here, I have this, uh, T static landing page. And this gem would be it n it not just T, but it, uh, the string. And here I would say something like, uh, EN or ES or I 18 n locale, the call that the current user has selected. And it'll make a call to, Uh, open AI and uh, come back with a translated stream and later on it would catch the string to make your calls to the API. And this might be a quick and okay approach. Yeah, again, I would use not this, I would use the actual title. So, uh, um, hello, uh, world tava. So this way it'll be translated, but I wanted something more conventional. Uh, but if I want to actually allow you, uh, have more control of the translation, not it this way for the jam, it is just a black box over translations. What if I want to, uh, have control with the translations as I do in my VML files? I just want to, uh, have my Ian VML file and generate the translations for all other languages based on this, uh, file. And, uh, let use override something manually if the, uh, AI is hallucinating. So, um, what do I do? There's a better jam. There's a Gem I 18 n tasks that lets you manage these V ml files. It'll help you link this files, clean up the, uh, unused keys and, uh, syn keys from one translation to another and make calls to AI or another translation service to do the actual translations. So let's see how this gem works. And I'm really happy with this gem. It's like one of the best gems I found this year. So let's try adding this gem. I will go to my gem file and add it in my group development. We don't need this gem anywhere else. Now I run bundle. Now there are a few files I need to copy, so I'm going to copy file number one. Now I don't need to cope with the aspect. I'm going to cope with the mini test file and to, okay, so what have I coped? I've copied with this, uh, item task, VML file where we beside the default local and we can, uh, update different ways. Uh, the item task gem, uh, uh, process our ML files. And I have imported that test file. So if you test that, uh, the gem runs on our translations. Let's try running these tests. I will say rails test. It gives me through failures. It says that I have some unused keys. Let's have a look at the unused keys. And uh, here you see these keys are not being, uh, used anywhere. Uh, why isn't this key used anywhere? Because we just, uh, removed it here in the landing page. Let's bring it back. So I will run the unused command again. And you see the key is being used. So how can we remove these unused keys? There must be a command, uh, unused, remove unused, okay, it remove these two keys. Yes, let's have a look at our mobile file and scan. And it actually not only remove the two keys, but linted everything. Basically removed the unused lines and, uh, sorted everything in the ML file alphabetically. Okay, uh, next, let's run the tests once again. Now instead of three fillers, we have two fillers. Let's have a look. So, uh, we have item tasks normalize, and it'll, uh, yeah, I forgot the, that was the symbol, the apostrophe on the beginning on my, okay. Uh, I normalize the Spanish translation now and the English translation. So everything is alphabetical here. Now let's run the test once again. And I have one failure and it says, uh, it and tasks missing. Let's run this command. And you see it says, uh, all locals are missing these lines. The Spanish local is missing these lines. And here's how it looks in the English translation. So how can we fix, uh, this? Here's the actual problem. Uh, in the English translation, I have, uh, uh, how many like 118 lines in the Spanish translation, I have 10 lines. I want to copy all the translations. So, uh, there is recommend a missing. And what did it do? Going back to the E-N-V-M-L, I have 121 line, the ES VML file. I have, uh, also 121 line. But you see in the ES VML file, I actually did not do the rule translations. I just, uh, copy the keys and, uh, put them in the human readable case. So this looks awful. This is not something I want to do. I want to actually do the translations in the ESML file based on the EN VML file. How can I do it? First of all, I will delete the ESML file changes. Let's leave it as it was, uh, on the very beginning, what we had here. Now, uh, again, I will run the normalized command and I want to, to use AI to do the translations. Now, if I go to the IT tasks VML config file, I see there is this translation services bot and I can use different translation services. In this case, I will use open ai. So I will use the open AI API key, and yeah, let build this model. So I need to add my open AI API key. Let's go here. I have already created an account in the platform.open ai.com. I have added the a credit card. Uh, I have some balance. Now I'm going to go to my dashboard, API keys, uh, create a new API key create key. Okay? Please don't steal my key. I will copy it and paste it here. Okay, I have the open A API key, and now I want to do the translations. So, uh, in the description, in the rhythm, we have this translate mission keys. So I can run 18 m translate mission, and I will say backend equals, uh, open ai. So let's run this command. I will cope with this. And, uh, backend equals open ai. Now, before running this, let's have a look again at our ES by ml. Okay, let's run this command. Now it's actually make an API cost to open AI based on our API key. Let's fade a bit. And here are the translations that four generated. Let's have a look at our ES ml file. Now again, we have 121 lines, exactly as many as in the English translation, but everything is translated with ai and that's basically it. So, uh, the gym item and tasks lets you sanitize, normalize your translation files, remove missing keys, and use, uh, a translation service or AI to actually generate that translations. And this way the application will look nice in different languages. Now let's have a look at the application itself. I will refresh the page. So you see now the header is also translated, uh, into Spanish, and so are all the other pages. And, uh, that's basically it. Thanks for being with me and see you in the next one. And most importantly, this is how we saved 9,000 euros per per year using this wonderful gem. Goodbye. --- # #172 i18n. Set language preferences in session or in User model - URL: https://superails.com/posts/ruby-on-rails-172-i18n-set-language-preferences-in-session-or-in-user-model - Published: 2024-06-11 - Duration: 08:31 - Access: free - Topics: i18n, translation, internationalization - Playlists: i18n (internationalization) 🚫 The official Rails Guides do not recommend to store locale in session/cookie. Let's break the rules! 😈 In this episode: - not logged in users store language preferences in the Session. - logged in users store language preferences in User model. This way our routes will stay clean 🙌 ## Transcript Hello friends. So in the previous episode, we added translations to Ruben Res application. Here you see the application, uh, has this, uh, title and subtitle in Spanish because I have the, uh, application slash local Es. And, uh, if I don't it, uh, to English. You see here I have the application in English. I can go to any UL and I will have slash ian or slash uh, uh, es if I select Spanish as a language. And this way, whenever you share a page in your application, everybody will see, uh, the application in the same language that you shared it in. So this is setting the, uh, local from the URL pers. And what if you don't want to have, uh, the, the language in the UL pars? What if you want to have it stored in session or in cookies if the user is not logged in, and if the user is logged in, in the user model? Well, let's do it. Another approach. Let's do it to close this setting the locale from session or cookies. So the official guides say that it is not a good approach because, uh, what if you view the application in Spanish, you send a URL to another user and he sees the application in the English because this is the default language. So, uh, in some cases it's fine. So let's try to do it. So we are going to make it so that users that are not logged in see the application, uh, in English, and they can save their preferences in cookies. If they change the language or in session and logged in, users can update it in their user model. So let's do this. Um, I'm going to do it based on the previous poll, uh, on the previous com comet. Uh, here you can see the changes. So, um, first of all, I'm going to undo the scope. I'm going to use the scopes, we'll need them. Uh, so going back, this should not work, but uh, removing slash es it should work. Okay, so now I can set language, uh, like this. Let's, uh, try to save it works. So it still works, uh, because I have the locale in the URL. Uh, now I'm going to also remove the code going to application controller. I'm going to remove this default URL options. And we are going to set the locale in a different manner. So now, uh, I refresh, we still see this, uh, translate to Spanish because we set the locale from perms locale, but I'm going to remove this. And this way they're going to set the locale to be the default locale. So the default locale is English, therefore it is always in English. Okay? So let's, uh, make it so that the users can actually save it changes in the, let's say session. So I select Spanish and I want the application to be in the Spanish. So, uh, I will say, uh, I think local Equals, or let's actually do it another way. Let's say if PERS locale present, then we'll say, uh, uh, session locale equals pers local two sim, or I don't think we need for the session. Uh, okay. And uh, we will say it locale equals the session locale, or they're going to use the default locale. So this way we are going to store the language in the session. Let's, uh, set it to Spanish. Now, uh, I navigate around the application and I have Spanish set. I click on English and I say the application in the English. So it seems to be broken. Okay, this was, uh, easy. So this way the language is stored in the session. Let's try again. I will, uh, click on English. I will, uh, visit around. Now you see Spanish is highlighted. Once again, why? So let's go to the N bar. Uh, it's highlighted because, uh, yeah, let's display the it end local. Maybe we have an error. Let's say. Yeah, it is Spanish. Let's go here. I will set it to English. I will go back to local host. Okay, it is set to English. So again, switch to Spanish. It is set to Spanish. Yeah, it's spoken. Yeah, just there was some thin cash. That's why it, uh, blinked for a moment. Okay, so it's English, and now it is Spanish vo. Uh, so, uh, this favor said the locale in the session was our application controller. And what if there is a user who is logged in and uh, uh, we want to allow the user to save, uh, the language in his, uh, model. Let's do this. I will say rails generate, uh, uh, migration. Add the locale to user locale, uh, stream. Let's go to add locale to user. Let's, uh, say default, uh, will be n but let, we don't even need to do it because let's make it more dynamic. Let's just use the default language. So, um, yeah, but we can also set it to no false, no, let's just leave it like this. Okay, real to be migrate. Okay, so now the user has, uh, local and uh, let's say if uh, there is a user who is logged in, they're going to use, uh, his, uh, locale. How can we do this? Um, so this is used for sufficient locale. Let's also update it in the user model. If, uh, there is current user, let's say current user dot update, local, prime, local, if user send in, uh, and they're going to use, uh, the current user dot locale if there is a current user or they're going to use the session locale or the default locale. So, uh, let's see if this works. I will start the server once again. Uh, refreshing. Uh, I'm not logged in. Uh, I have it in Spanish. Let me, uh, yeah, let me log in. I have the application in English. Now let me set it to Spanish and, uh, let me visit some page. It is still in Spanish. And let's, uh, run the rails console user dot first. And uh, the local is Spanish. So we managed to update the local of the user looks. Uh, fine. Uh, let's update it to English. Let's go back to the console. And the local is English, so looks good. Let's log out now. And the local is still English. Again, let's try changing to Spanish. Log in, uh, log out. And it is, uh, set again to English because this is the default locale. Again, I will set to Spanish, not been logged in. It'll assist as been Spanish, so looks fine. And, uh, this is basically how you can set the locale, uh, for users that are not logged in in the session, and for users that are logged in based, uh, on the locale that they stored in their user model. So this is, uh, about it. Thanks for win with me and see you in the next one. --- # #171 Internationalisation (i18n) basics. Translate your app! - URL: https://superails.com/posts/ruby-on-rails-171-internationalisation-i18n-basics-translate-your-app - Published: 2024-06-10 - Duration: 15:10 - Access: free - Topics: i18n, translation, internationalization - Playlists: i18n (internationalization) In this episode we make our app available in different languages. There are different strategies to set the current language. Setting the language in the URL is an easy and RESTful approach. Let's implement it! Episode source code (premium subscribers only): https://github.com/yshmarov/superails/pull/251/commits/da896aa42667b11b044e2899b2cbe69a3c2f5023 Based on this blogpost: https://blog.corsego.com/i18n-select-locale ## Transcript Hello, friends. In this episode, we are going to add translations to Ruben Rails application. Now, the approach of adding translations into an application is called internationalization or I 18 n because there are 18 letters between INN and, uh, we are going to actually do it on super rail.com. So I'm going to add support for Spanish language, and it's going to look more or less like this. So here I go to the root URL, and it is English by default. And, uh, I have different flags. I can click on the Spanish flag and I will send the application in Spanish. And you see I also have slash es in the URL, so I'm sending the language in the URL and if I navigate to posts or, uh, yeah, playlists or to tags, uh, we still have this, uh, slash es or slash uh, en N in the URL. So we're going to set the language based on the URL programs and why not, uh, on current user, why not in session? Why not in cookies? Actually, the rails, uh, guides have nice answer for this. So, um, should be transparent as a part of the URL. You will not break people's, uh, assumption if you send it to a friend and they should see the same content as you in the same language as you. So, um, we are going to try to, uh, do translations this way, but usually, most often if an application requires, uh, a user to log into use, a user would, uh, set his language on his own. But for the sake of this example, we're going to set the language in the URL. Let's see how we can do it. So, uh, first of all, I will undo all these, uh, changes. I don't need them. And, uh, let's, uh, go back to our application. I will also restart the server. So yeah, here's the normal un translated untouched version of the application. And, uh, first of all, I'm going to see, uh, what is the default language of the application. I will start the rails server. Let me make the screen bigger. Rail c it n locale. It is EN default locale is also EN. And available locales is a list of, uh, all, uh, languages that we can have. So how can they add translations? First of all, I have this London page. I have this, uh, title and this subtitle. Let's navigate to this, uh, page. Let's open London page. It is, uh, inside the static controller. Here it is the landing page. And I have a title and subtitle, so let's slate them. I, uh, have already an en end ML file where I have lots of translations, but I did not add translations for, uh, these two lines. So I'm going to add them. Uh, I see I'm in the study controller and landing page action. So in the Ian VML file, I'm going to find a shared controller. Uh, not this one, the controllers would be, oh, yeah, it's not shared. It's static. The name of the controller, it's, uh, the root, uh, controls are in the root after the, uh, language namespace. So we have the static controller, and I will add the action landing page. Inside the landing page action. I'm going to add a title. I will copy with this, uh, text, and I will add a subtitle. And in the subtitle, I will copay this text. Let's save the changes. And yeah, now it is empty because, uh, well, it, I've just made empty strengths here. So let's find these, uh, translations. I will, uh, do I 18 NT, uh, static landing page title. Let's save it, folks. Yes, it works. And it'll also work if we drop the 18 N and just lift T. And it'll also work if we just leave dot title. So this is called Lazy Translation Loading because it, uh, by default tries to find inside the static controller landing page, uh, name. So the first level is static, second level is landing page. And third level that they're is dot title. And we can do the same with subtitle. Uh, let me cope with this. So here we'll have, uh, dot subtitle. Let's just make it, uh, for the sake of example different. So here we have title and subtitle. And now let's translate this to Spanish. So we are going to add a new local, uh, e ml. I'm, uh, not going to copy everything. I will just copy with this. I will say Es and here will have static title. Let's translate the title. Let's look at Google Translate Now. Uh, same with subtitle. Okay. And, uh, let's go back to our application. So what if we want to see the application in the, uh, Spanish language? What if you want to see this translations? To do so, we can go to our application controller and set the default locale. So before action, uh, set locale. daf set locale. Let's make it private. Uh, it n locale equals es. Let's refresh the page. Uh, we got this translation missing, uh, because in our development, rb, I set config item and raise on missing translations. So, uh, I'm going to disable this ol because, uh, I've just added a new translation. I didn't add all the keys for it, and I will restart the server. Okay? Uh, and here, actually you see, I see the application in Spanish because, uh, uh, in application controller, I set locale equals ES. Again, I will make it EN. And, uh, I see the English version of this two strings. So looks good. But, uh, what you want to let the user, uh, slide the language and what you want to slide the language based on the URL. So, um, actually the rails guides have, uh, a nice example of this. Let's find it. So first here it is setting in the locale from your L para. So first of all, I'm going to add this to the application controller default, URL options, and they're going to append the locale equals at and n locale to all our URLs. Let's, uh, refresh the page. Let's visit the playlists, for example. And you see we have, uh, question mark locale equals em. So we have the, uh, current, current locale appended to all the URLs. Uh, let's make it ESL refresh. I'll go to another URL, and, uh, it should be local equals es, but it doesn't look good. We don't want to, uh, set it in, uh, this format in the query programs. I want to have it, uh, something like, uh, uh, slash n or slash es, uh, slash post. So to do this, I'm going to go, uh, back here and we will add scopes to our roots. So the scope is going to be locale. Let's go to our roots and final. Now let's translate, uh, just the playlists and tags and, uh, yeah, and landing page. I will also add here. So scope locale, uh, yeah, let's say EN and the ES landing page playlists and text. Let's, uh, uh, go back to our application and refresh the page. Um, it doesn't work for posts because we didn't add the post to this scope, but, uh, if you go to playlists, we see slash es slash playlists slash es slash tags and slashes. Just go into the landing page. If I make it slash uh, ENI still have the Spanish version. And why it? So, because, uh, we have hard coded our language into application controller. Uh, so let's, uh, make it dynamic. It'll have said locale, it n locale equals rams locale or it n default locale. So we're going to find the local from the PRMs from here or from the, uh, fewer PREMs like fashion local equals es. Uh, or they're going to use the default language that is English by default. So, uh, let's refresh, uh, go to the land page. We have slashes. Let's make it slash em. And you see, voila. We managed to translate the application by updating our URL programs. Let's go again to slashes. And we said the version of the application in Spanish looks good. Let's add a language picker here so that the user can slide the language without, uh, updating the URL. Um, let's go to our npo. And here I will have, uh, equals link to, uh, EN, uh, root path locale equals EM, and same with Spanish, e, s, and Es. Okay, here are two links. So I click on this one. I see the application in English. I navigate anywhere, and I still have it in the URL paras. Let's go back here. And I click here. I have the application in Spanish. Let's, uh, also highlight the link to the current language. So to do this, I will go another, the class clause, so will be, uh, if locale or if, uh, EN equals it, n locale, uh, then we'll have, uh, border bottom. Uh, otherwise we are going to have no clause. So if the local is em, then we have it highlighted. And let's do something similar for this other Spanish local. Yeah, and here it should be Es. So let's click on Es. And everywhere we visit inside the application, we see that the Spanish language is selected. Now this code looks not very nice. Let's, uh, what if we have like 10 languages? We don't want to hot code this for each language. So they're going to, uh, do an enumeration and they're going to enumerate by the available locals. So let's go to application rb. And actually it is nice to set config 18 n default locale equals EN. And we'll also set, uh, uh, yeah, it's not 18, but 18 n. We'll also set available locales to be English and Spanish. Now, after this change, I need to restart the server. And let's have a look at our available locals now. So previously we had a list of all these languages when running 18 L available locals, and now, which have only English and Spanish looks good. So let's go back to our N bar. And here I will have item N available. Locales each do locale. And, and instead of this links, I will have the locale, uh, root path to locale equals locale, and if locale. So this way we don't have to type it for each language. Let's see if it works. Refreshing. And yeah, it works just as before. Now, instead of the language names, we can also display the flags. This would be more beautiful. So to do this, we can go to our application helper and add helper, something like that, locale to flag locale, let's say locales equals uh, uh, um, and we'll have the yeah, US flag. And the es, let's have the Spanish flag. And let's have locales local to sim. So this way they're going to find the, the flag based on the, uh, sim, E-N-O-E-S and let's display it here in the nal. So we'll have locale to flag and locale and vola. We pass the flags. Looks good. Now let's go to our playlist, for example, I will change the locale and it directs me to the root path. So this is not really nice to direct back to the previous page. Instead of root path, I will say UL four, and it's going to be the UL to the current page. So now I'm on playlists, I changed language to English and it works. I change to Spanish. And you see I'm still on the same page, but I changed the, your L pres from EN to E Es. So yeah, it works. And uh, again, here I'm on the playlist page. I don't have the title and subtitle in Spanish, but if I go to playlist index, I see I already have the key for title and subtitle. So going to es vml, I can say playlists, um, index, yeah, something like this. Let's just go with the flow. I refresh, and here I have the translations in Spanish or some kind of things that resemble the translations. So you see I have the playlist in English and in Spanish. And that's basically it. This is how you can, uh, set translations in, uh, your, uh, rules. So inside the roots. And the cool thing is that if you share the URL, the person will to whom you share will see the application is in the same language as you share the URL. So it kind of makes sense. Now again, uh, to use this approach effectively, you would want to, uh, namespace all the translatable parts of your application under this, uh, scope. But, Uh, this is the only thing you do. You just namespace everything inside the scope and everything will just work. So yeah, this is how you can do translations, uh, based on, uh, send the locale from your L brands. So thanks being with me and see you in the next one. --- # #170 Maintainable Buttons CSS - URL: https://superails.com/posts/tailwind-on-rails-170-maintainable-buttons-css - Published: 2024-06-04 - Duration: 16:08 - Access: free - Topics: tailwindcss, css - Playlists: Tailwind on Rails Please, don't abstract button CSS into a component! It leads to suffering. Don't do this: https://github.com/baoagency/polaris_view_components/blob/main/app/components/polaris/headless_button.rb Using inline styles for buttons with Tailwind is also a bad option, because it can be hard for you to update the component everywhere in your app at once. CSS abstractions with an API similar to Boostrap should be just enough: https://getbootstrap.com/docs/5.3/components/buttons/#sizes Episode source code: https://github.com/corsego/138-tailwind-layout/commit/2cd22a45bbed2c79ece73cb7726c4a181ddff5ba Based on this blogpost: https://blog.corsego.com/tailwindcss-buttons --- # Build a diff checker with database record version control #169 - URL: https://superails.com/posts/diff-app - Published: 2024-05-22 - Duration: 18:08 - Access: free - Track changes in `post.title` and `post.body` - Display version history - Display diff between versions (like github) Text version: https://blog.corsego.com/diff-table-column-changes Episode source code: https://github.com/corsego/169-audited-diffy/commit/10204bbb8c68333749980f1917b22b14afb6b51f Gem audited: https://github.com/collectiveidea/audited Gem diffy: https://github.com/samg/diffy ## Transcript Hello friends. Today we're going to build a text comparison tool where you're going to be able to have two different texts and compare the differences between them. Here is a simple example, so just a random online tool where I inputted one version of the text, inputted another version of the text. And by clicking check the difference, we see the difference between the two versions. This is especially useful when you have different versions of contracts that you are about to sign. Now, for me, as a programmer, the most, uh, common, uh, uh, tool for viewing diffs between different, uh, texts or different files is, uh, obviously gi. So here I'm looking at GitHub, just a random comment, and I see different files that have been changed, and I see the differences between the, the current version and the previous version. There is this unified view by default, and there is also that split, uh, view where I see side by side the changes in the previous file and in the new file. And in this specific application, they're going to have, uh, a list of posts. A post has a title and the body. And, uh, I will be able to try the changes in the title and the body. So they're going to, uh, track the changes of, uh, attributes and they're going to display the d between the, the previous version and the new version. So if I added the title, I will be able to see versions like the, the previous version of the title, the new version, and the diff. And same with body. Now, how can we do this? First of all, we're going to install the gem audited. That will let us, uh, track the diffs. So gem audited. Here it is. So, uh, I'm going to add to the gem file, or I can just, uh, run bundle, add audited. And next I'm going to run rails, generate audited install. It'll run a migration. Let's have a look at the migration. Install, audited. So it creates a new table where we have audits. So we can audit a post or a user or any kind of a model in our application. Then we can, uh, store the user that, uh, did the change. We can, uh, see the difference of the changes. This is the most important attribute. 'cause here we're going to store the old title and new title, the old body and the new body, the versions. So initially the version is zero. Then, uh, it increments by one and possibly additional comments, uh, and, uh, like AP address of the, uh, person that, uh, or of the machine that made the change. Okay, so here we have the migration. Let's run rails, the B migrate. And now I will go to the post model and I will add audited. Now, uh, if you look at the documentation of the audited gem, you can specify that you want to audit only specific, uh, columns, or you want to audit, uh, only when the, uh, there is a great action, but not when there is an update Action, for example. Or you can require comments, uh, comments, or you can, uh, store for example, only two or only five audits per like, only the latest, uh, few audits per, uh, post. For example, a post has like 10,000, uh, changes and you don't want to store all of them. Uh, in our case, let's just audit everything. They're going to audit both the title and the, the body changes of a post. Okay, uh, let's see if, uh, this works. I will, uh, start the rails, uh, uh, server. I will, uh, uh, open this post. I will edit this post. So let's change it like PWA to, uh, no, let's change rails. Eight to rails nine will, uh, maybe be a PWA or let's change the web to the internet update post. So I have saved the post, and let's have a look at the audits of this post. I will open the Rails console post first audits. So here we have an audit. Let's count the audit. There is one audit, uh, and in the audit we have, uh, so the polymorphic association with the post, we don't have a current user. So that's, uh, obvious that it is new. Uh, the action was update because we already had it created. And here are the audited changes. So the old title and the new title, the Old body and the, the New Body. And based on this, later on, we are going to display the diff. We also see the version is fun. There is no comment, and we see the information about the IP and the request ID of this change. Okay, looks good. Let's, uh, display the change history in the view. I will study rail server. So inside the post show, I will also display the audit history, the change history post slash show. So let's put a break, a header, uh, audits. And, uh, we will count the audits. I will say at post audits dot count. So there is just one change at the moment. Now let's, uh, display all the changes. So I will say post audits, do each do audit and here equals audit. So yeah, we have an audit at the moment. Let's, uh, put it in a separate, uh, div to make it more visually different. So div and, uh, we have audit. What attribute does the audit have? It has created at, it has version, it has action. So this is when it happened. The version was fun, and the action was an update. And, uh, we will also have audited changes. So here is the hash of audit that changes the title and the body. Let's, um, now, uh, uh, try creating a new post. Let's, uh, create a new post, new post with body create post. So there is just one audit. The action was create. Here's the title and the body. There is, uh, no hash here. Let's compare. So here we have, uh, yeah, he have, have an array of, uh, two different versions. And, uh, here we don't have an array. We just have a stream because there is no previous version. Okay, so, uh, now we already track, uh, the changes in both title and, uh, body. And we want to display the diff as does, uh, GitHub here, as does this, uh, random diff jacket tool. Uh, here. How can we do this? Actually, there exists a great Ruby gem, GEM dfi, and the gem is going to do all the heavy lifting of, uh, uh, creating the diff between the strings and, uh, uh, making our HTML markup look good. So let's install the gem dfi. I will, again go back here and say bundle add dfi, okay, rail server. And let's see how we can use this jam. So, uh, we can put, we can display the diff between, uh, two strings. So string number one and string number two. Let's, uh, try using this. So, uh, here have, uh, yeah, let's try with this one. But we have two different versions, uh, inside the array, version one and version two. So, um, inside the audit, the changes we will have, uh, uh, each do, uh, attribute. The attributes are body and title and changes. The changes are version one and version two, attribute and changes. Okay, let's display the attribute equals attribute and, uh, equals changes. Let's also put this into a separative. Okay, so we have the attribute and the two changes, the attribute and the, the array better. Two elements, the old version and the new version. So, uh, uh, to have string one and string two, we'll have changes, uh, element one in the array and, uh, changes element two in the array. So, um, let's have, uh, definitive new changes. The first element, so with index zero and changes one, let's not display these changes anymore. And let's close this, uh, here. B tag. Um, yeah, okay. So here we have some kind of H TM L generated, but it doesn't display as h TM L in our application. We need to say HTML save. Okay, so we have the old version and new version, old version and new version. But it looks, uh, quite ugly to make it look good. We will need to go back to the docs of, uh, uh, dfi and, uh, go with the DCSS. So we can, uh, uh, yeah, just copy the CSS and put it into our, uh, application. Do CSS file. Let's, uh, refresh and it'll really looks much better. So we have version one and version two, but the text, uh, changes themselves are not highlighted, just the changes in the rows. So instead of using this html simple, we'll just use html. Okay? So this looks much better. And we can also, uh, add some additional options as, uh, include, uh, plus, uh, and minus in, uh, H TM L two. Okay. Looks even better now. Um, mm-Hmm. And, uh, yeah, looks fine. So we see the difference in the changes of the title and, uh, the body. Uh, now let's see if it works for the other attribute. Oh, not for the other attribute, but in case of, uh, uh, not having an array. You see it doesn't work because, uh, have, you just have a string. You have have an array, two different areas that we compare and show the differences. And here, there is no error to compare it. So I'm going to say, um, if, uh, the changes is an error, then they're going to display this. So if, um, changes is, uh, array, they're going to display this diff else, they're going to compare with, uh, an an empty initial version. So here you see, uh, yeah, we don't need also this, uh, we're having the full string compared to nothing. So here we see that that was created the out of nothing, a title, new post, and, uh, a body with Body. Let's edit this. I will, uh, with a sex body, uh, new edited post. And here we have two different versions. So looks, uh, good have, have vision one and version two, but I imagine it would be more useful to have the latest version on top. Uh, so let's change the order. I will try saying order version, the send in. Okay, nothing changed. Let's make version ascend in. Nothing changed. Uh, maybe they'll have to use, not order, but uh, sought by version dot reverse. Okay, now we have the latest version, uh, and the changes from the latest version on top. So, okay, now we can try the changes of the title and the body. Let's say they change only the title without changing the body. So, uh, you see in the latest version, we don't even log the change of the body because nothing changed and we see a difference in the title. So, uh, looks, uh, quite good. Now, uh, let's also, uh, have a look at GitHub. Here we have this, uh, unified view that we already kind of implemented. Instead of having different files, we have different attributes of the post. And, uh, let's also implement this split view. And DFI can also help us with this. Div has the option of, uh, DFI split Diff, and they're going to have the left column and right column. So let's implement this. And we're going to also add a toggle between the split and unified views. So let's first add the toggle. I will say, uh, if Rams, uh, split to s equals true, I will have a link to, uh, uh, column view or unified view, uh, post path at post. Uh, otherwise I'm going to have the link to post path, but I'm going to say split equals true. So, uh, let's see if the toggle works. We have the column, okay, it doesn't work. Um, uh, why doesn't it work? So, uh, this will be split. Um, okay, something isn't f let's see. So if per split equals through, we'll have column view. Uh, otherwise we'll have split view. Uh, what is the URL here? It makes sense. Okay, yeah. Now it is broken. Strange, maybe I just didn't refresh the page correctly. So the ULS change and let's have, uh, the conditional difference here. So, uh, I will say, uh, if PERS split to S equals true, then they're going to have the split view. Uh, otherwise they're going to have the normal view, the column view, and let's, uh, add the split view. So I'm going to have this DFI left and diffy right equals dfi left and dfi right. And here will have changes. Zero and changes one. Let's see if this works. So, uh, I go to the split view and uh, it kind of displays something, um, but it doesn't look good. Let's add the h tml safe dot h tml safe here. And here it's still doesn't, uh, look very good. Mm. Let's see why. Maybe we need to also mention format. HTML. Okay, yes, this helped. So here and here and, uh, we have the new version and the old version, but we want to show them side by side as GitHub does here. So we are going to, uh, add the diviv, uh, style, uh, display, uh, flex. And now they are kind of displayed side by side. Uh, obviously you can make the width of each, uh, column. Uh, same. Let's have a look at how it looks on the longer text. So here I have the normal column view and the differences and the split view looks, uh, much, uh, better. I have the old version and the new version side by side, and as this text alone that they can update even, uh, with. So yes, that's basically it. We built an app where we track the changes, uh, to the post title and the post, uh, body whenever something changes. And, uh, We can see the change history of the body and of the, uh, title and we can view the changes, uh, as a diff in the column view and in the split view. So that's about it. Thanks for being me and seeing in the next one. --- # #168 Mobile footer navigation - URL: https://superails.com/posts/tailwind-on-rails-168-mobile-footer-navigation - Published: 2024-05-21 - Duration: 06:32 - Access: free - Topics: PWA - Playlists: Tailwind on Rails You use your thumb to click on your phone. That's why all mobile apps have footer navigation. A good web app should also have footer navigation on a mobile (small) screen! Episode source code: https://github.com/corsego/138-tailwind-layout/commit/05552c77d1bf6ec7a5f4a37bea6d96dc58c1e660 Based on this blogpost: https://blog.corsego.com/sticky-mobile-footer --- # #167 Build a PWA - URL: https://superails.com/posts/ruby-on-rails-167-build-a-pwa - Published: 2024-05-12 - Duration: 14:16 - Access: free - Topics: PWA - Playlists: Progressive Web Apps (PWA) Let's turn an existing app into a PWA by adding a valid Webmanifest and Service Worker. You can simply add the files to the /public folder, OR you can do it the Rails 8 way and add manifest.json and service-worker.js to ActionView! Episode source code: https://github.com/yshmarov/insta2blog.com/commit/6bdcd957865ecaed5dc7802b5086ad98f6851ab0 Based on this blogpost: https://blog.corsego.com/rails-8-pwa 0:00 PWA on MacOS 2:38 Webmanifest 7:56 Service Worker 10:05 The Rails 8 Way ## Chapters - 0:00 PWA on MacOS - 2:38 Webmanifest - 7:56 Service Worker - 10:05 The Rails 8 Way --- # #166 PWA (Progressive Web Apps) in Rails 8 - URL: https://superails.com/posts/166-pwa-progressive-web-apps-in-rails-8 - Published: 2024-05-11 - Duration: 07:09 - Access: free - Topics: PWA - Playlists: Progressive Web Apps (PWA) Make your Rails website behave like an installable app by turning it into a PWA. All new Rails apps will have a webmanifest by default, meaning your app will be a Progressive Web App out of the box! In this episode we will explore how PWAs work. Based on this blogpost: https://blog.corsego.com/rails-8-pwa --- # Friendly S2E8 Radoslav Stankov does not need Hotwire - URL: https://superails.com/posts/friendly-s2e8-radoslav-stankov-does-not-need-hotwire - Published: 2024-05-06 - Duration: 11:07 - Access: free - Playlists: Friendly Show Podcast One more cool cat from BalkanRuby! This time we talk to Rado ex-producthunt, building his own product - AngryBuilding. He is the only developer and has a team of non-devs doing business stuff. He kindly reminds me that you don't always need "reactivity", and full-page reloads are good. Enough for many B2B cases! You can learn a lot from Rado about building businesses with Rails. His new startup: https://angrybuilding.com Slides from his conference talks: https://rstankov.com/appearances P.S. Sorry for the sound quality on these - we are still experimenting with studio recording 🙃 --- # Friendly S2E7 Nick Sutterer - Trailblazer and ViewComponents daddy and heart of the party - URL: https://superails.com/posts/friendly-s2e7-nick-sutterer-trailblazer-and-viewcomponents-daddy-and-heart-of-the-party - Published: 2024-04-29 - Duration: 11:34 - Access: free - Playlists: Friendly Show Podcast Nick is one of the most fun exciting people to be around! He has a few superpowers: 1. Developing unique Ruby tools way ahead of their time He created Trailblazer as a better way to structure business logic in Rails apps than Services. Way ahead of DDD! https://github.com/trailblazer/trailblazer He created Cells that inspired ViewComponent https://github.com/trailblazer/cells 2. Presenting on stage. So far he did over 100 conference and meet-up appearances! He has a natural charm and can always make you laugh. 3. Heart of the party. Find him on a Rails conference and learn for yourself! His only social profile is https://github.com/apotonick --- # Stripe Embedded Checkout form - URL: https://superails.com/posts/stripe-embedded-checkout-form - Published: 2024-04-27 - Duration: 10:35 - Access: free - Topics: saas, rails starter kit, rails boilerplate, saas boilerplate, stripe, online payments - Playlists: Stripe SaaS with Rails 7 😖 I really dislike Stripe Elements: you have to build and style the checkout form yourself. 🤩 Instead, stripe-hosted full screen checkout is a much better approach where you outsource all the payment logic to Stripe. It looks great! 🥰🥰 But now you can use Embedded Stripe Checkout inside your app and keep your branding! Let's implement Embedded Stripe Checkout in our app! Text version: https://blog.corsego.com/embedded-stripe-checkout Episode source code: https://github.com/corsego/rails-7-stripe-subscriptions/commit/3bfd4a1ff03c1340c616d71398f23d9394edbce4 Series source code: https://github.com/corsego/rails-7-stripe-subscriptions Stripe embedded checkout API: https://docs.stripe.com/checkout/embedded/quickstart Stripe embedded checkout docs: https://docs.stripe.com/payments/accept-a-payment?platform=web&ui=embedded-form --- # Friendly S2E6 Meet Balkan Ruby organiser Genadi Samokovarov 🇧🇬 - URL: https://superails.com/posts/friendly-s2e6-meet-balkan-ruby-organiser-genadi-samokovarov - Published: 2024-04-27 - Duration: 11:07 - Access: free - Playlists: Friendly Show Podcast Since the first moment I met Genadi I felt “this guy is great to be around“! In this episode he talks about the 🇧🇬 ruby community, organising conferences, his current work and open source history. Find Genadi http://gsamokovarov.com/ Balkan Ruby https://balkanruby.com/ Join Sofia local ruby meetup https://rubybanitsa.com/ --- # Friendly Show S2E5 Balkan Ruby! 👽 Irina Nazarova. Making $ on Dev tools 🤑🤑🤑 - URL: https://superails.com/posts/friendly-show-s2e5-balkan-ruby-irina-nazarova-sustainable-open-source - Published: 2024-04-26 - Duration: 17:55 - Access: free - Playlists: Friendly Show Podcast This week we are visiting Balkan Ruby in Sofia, Bulgaria. We met a lot of people who impact our community. In this episode we discuss Sustainable Open Source with Irina Nazarova, CEO of very very @evil.martians https://balkanruby.com/ --- # #165 Manage active sessions. Security feature - URL: https://superails.com/posts/superails-165-manage-active-sessions-security-feature - Published: 2024-04-18 - Duration: 23:37 - Access: free - Topics: security, devise, authentication In some cases to enhance security of your application you will want to allow users to see all the devices/browsers they are logged in with. You would also provided a button to sign out of a device/browser. Here's how you can do it! Episode source code: https://github.com/corsego/165-security-manage-active-sessions/commit/028064768ecb033dc19d7f3179bb52d963a734f4 Based on this blogpost: https://blog.corsego.com/secutiry-manage-active-sessions ## Transcript Hey guys, let's talk a bit about security of our applications. So that exists, this open world application security project of us that defines different tenants and methodologies for securing your applications and thinking about the world of rails, I know Craig mna, who is an OSCP certified penetration tester. Now, what does this mean? It means, uh, uh, offensive security certified professional. So a person that you can hire to try to hack into your, uh, software. And, uh, if he, uh, successfully hacks into your software, he will provide a report of, uh, vote can be improved in, uh, your software. And if you did not manage to successfully hack, uh, your software, he will, uh, uh, well provide you with an uh, OSP certification. So he'll say that your application is kind of quite bulletproof, and this can, uh, uh, from hacks and this can help you sell to corporate clients. So the process of, uh, doing this kind of, uh, uh, hacking that, uh, you are hired to do is called penetration testing. And, uh, we are going to have a look at a few things that, uh, are commonly mentioned by penetration testers in, uh, uh, ribbon rail applications. So one thing is, uh, access control. So broken access control. So, uh, basically given I use a list of all devices that he's logged in with, and, uh, the possibility to log out of a device remotely. So, for example, Facebook has this feature. I go to my account center and I go to where you are logged in. I click on it, I see that I'm logged in from two devices. If I click on one of the devices, I will see where I'm logged in and I will have the possibility to log out of this device. Let's see how we can add this kind of feature in the Ruben Rails application here, I actually have already built, so here I'm logged in from this file browser here. I'm logged in from the edge browser. I go to the slash logins, and here I see that I'm logged in from a Safari browser. And from the current session that is from the Edge browser, I go to logins. Here, I see that, uh, I'm logged in from Safari. Now if I click on disconnect, I will refresh the other tab. And here you see, I have been remotely disconnected. So this way you can log out of any device that you, uh, possibly forgot to log out. Uh, from now I log in, once again, I refresh. You see I'm logged into two devices. Now I will log out on my own and I'm logged in with just one device. So this is, uh, sometimes considered that an important security feature that you should have, uh, in your application. And let's see how we can build this. Uh, I'm gonna start with an basic application where I have just device installed. I will run realty b roll back. Now, uh, I'll go to the main branch, and here I have, uh, this, uh, basic application. Lemme restart the server. Okay, so here is my basic application that has, uh, only device and couple of pages, the homepage and the admin page. And within the admin page requires a user to be sent in. So, uh, let's try adding this kind of, uh, logins or cations or devices, uh, manager to our application. So first of all, we are going to start with adding a login. Stable, let's say rail generate, uh, scaffold login. So a login is going to belong to a user. So user references, it's going to have a device id, uh, it is going to have, uh, uh, an IP address and user agent, okay, let's say rails to be my. Now I will go to the user model and say that a user has many logins. And, uh, now how are we going to, uh, create a login when, uh, a user sends in, remove a login when a user sends out and ensure that login exists. When the user is trying to navigate the application, let's go to our application controller and just write it down. They're going to need to have something like dev, uh, create login. Then, uh, dev, uh, destroy login. So this is going to happen when user signs in, Uh, this is when user signs out and they're going to have another thing to check if, uh, that exists a login. So, uh, uh, dev require login, okay? So, uh, require login is going to be a before action. So before action require login, if there is a current user and what is required login going to do, it's going to check if, uh, the user is logged in from this device. So, uh, let's, uh, let's start by creating this create login action. When a user signs in, we are going to create a login in the login database. Uh, they're going to say, uh, uh, current login equals current user logins dot create device ID is going to be something. Then they're going to have, uh, a IP address is going to be something and user agent, we are gonna study this. Now where can we get them? Let's, uh, see what we have in our request. Whenever we make a request, we are going to have the IP address, user agent, and we're going to generate a device. id. Let's, uh, put a debugger here and, uh, let's try to invoke this. Create login. Uh, uh, when they sign in. Now, the easiest way to invoke it can be using the device after sign in the Path four. So dev after sign in path for resource, it'll be, let's say route path, but they're also going to invoke great login. So, uh, let's go back and try to log in, uh, what accounts they have. Okay, let me, uh, register. So I'm clicking sign up and I hit the binding. Okay, I had the bin Dev running, not just rail server. So, uh, it's not going to work. I'm going to just try, have to try to restart running the rail server. Okay? Now, once again, okay, email has been taken. Let's log in with this email login. And, uh, yeah, I have this undefined method require login. I forgotten. I, let's try on scan. Login, okay, now I hit the bind in. So I'm inside this Create login. What do we have? I have IP address that I can get from request request remote ip, and I can get user agent from request, user agent. And let's generate a unique, uh, ID for the device. So let's say device ID can be something like, uh, digest, SHA, uh, 2, 5, 6, HX Digest. And we are going to generate it based on the, the, uh, request user agent and request remote ip. So let's say, uh, request dot user Agent concatenated with request dot remote ip. Now, I'm not doing it as a secure HX because a secure HX would always be different, but this is going to be always the same, uh, for the same, uh, user agent and remote ip. So this is going to be the device id. Let's uh, paste here. So we are going to, uh, create a login and we are gonna store the device ID in our session. We're going to say session device ID equals device id. Okay, now let's, uh, and start the server once again. So, uh, I'm going to log in, uh, and define method, IP address. Yeah, because I should say, uh, request remote IP and, uh, request user Agent. Okay, let's try again. Uh, login. I'm logged in and let's see if, uh, login has actually been created. I will go to slash logins and you see EL has been, uh, created. Okay, so I managed to create EL login and, uh, let's, uh, require Elgan to be present, to be logged in. Uh, let's, uh, uh, say we're going to find a login. So I will say, uh, uh, current login, uh, equals, uh, current user dot logins dot find, uh, uh, find by device, uh, id, the device ID that we have stored in the session. So there should be like a device ID stored in the session from, uh, our previous login. So it'll be session device id. And, uh, if there is a current login, everything is okay, but if current login is, uh, nil, then they're going to send the user out. So sign out that is a device method, uh, current user, and we'll say redirect, uh, to, uh, new user session path alert, uh, device, not, uh, recognized. Okay, let's, uh, try navigating our application a bit. Now, uh, let's say I will, uh, log in with this, uh, device. I click on login and you see I get this device not recognized. Why am I getting this? Because, uh, login happens before, after Sendin path is, uh, triggered. So, uh, I have no chance to actually, uh, create a login. I get logged out before this, after Sendin path is triggered. So one way to work around this would be adding a return here so that this, uh, require login that is not, uh, trigger on the, the sign in the action on the device sign in. So I will say return if, uh, controller path, uh, equals devise sessions. And uh, action name equals great. So, uh, when logging in, we're not going to require login. This kind of makes sense. Let's see if this works. Now I'm going to try to log in and I manage to log in. Let's see how many logins I have. I have two logins. I will, uh, try to log out of, uh, this, uh, safari device, uh, destroy login. I refresh. And you see I have been logged out, so it kind of works. Uh, yeah, looks nice. Uh, now, uh, let's try login in onesca. I'm logged in again, I have two logins. I will log out on my own and I still have to logins, so I need to manually destroy the login. Then I'm, uh, uh, signing out. So, uh, I'm going to say, uh, current user logins, uh, find by device ID session. Device id. Uh, if we find it, we're going to destroy it and we're going to, uh, set the, the session device ID to nil. So I'm going to say session delete, uh, device id. Now, how can I trigger this? Destroy login. I cannot do it in the after send out path because, uh, I will already not have a current user. Then I hit the after send out path for device. So I'm going to actually have to hijack the device sessions controller for this. And, uh, to do it, I'm going to generate the device sessions controller. I'm going to say, uh, rails generate, uh, device controllers. I'm going to name specific within users, and, uh, I'm going to generate only sessions. I'm going to update my route. So instead of just having device for users, I'm going to use our custom sessions controller. Let's open the custom sessions controller. And here I have, uh, the new create and destroy actions that I can override and inside destroy. Uh, before running the actual destroy actions, I'm going to, uh, run, destroy login like this. Let's, uh, see if it works. Now I'm going to log in. Once again, I get device not recognized. Why do I get this? Because, uh, now I'm not using device sessions. I'm using, uh, uh, users slash sessions. So, uh, instead of this, I'm going to have users slash sessions. Let's try once again. Okay, it failed. And uh, now I have to logins. I'm going to log out here. I refresh this one, I still have to logins. This doesn't look right. Let's, uh, let's try removing this one manually. I will again click on login. So I have to logs, I log out, I have one login. Okay, so now it, uh, worked. Looks good. And uh, also you don't have to have this great login inside the atan path. You can also move it to the sessions controller since we have created it. So we could do it somehow, like, uh, dev create. So we are going to say super dual resource, and we are going to run, uh, create login, uh, if resource persisted in our case resource is user. And now we don't need the after sending path anymore, or at least the create login that we put inside it. And let's see if, uh, everything works now. So, uh, just in case, I will restart the server. And, uh, here I am. I have a couple of tabs. I'm going to log in here. I have two sessions. I will, uh, show the login, uh, remove it. I have logged out. I log in here, I go to slash uh, logins, and lemme try to remove this, uh, edge, uh, browser, EDG. So show and destroy. And I have logged out of, uh, this other browser while staying logged into this one. So it kind of works. Uh, obviously you would want to clean up your, uh, uh, logins controller. So here you don't need most of these actions. You only need the index and, uh, destroy. And, uh, you are going to allow a user to access only his own, uh, data. So they're not going to have all logins. I will have current user logins, and here it's going to be current user logins. Find. Now we don't need to respond with different formats, so it just going to be a redirect. Here. We have cleaned up, uh, the controller. Let's also clean up the login views. We don't need, uh, most of this. Let's, uh, just leave the login, uh, index. And, uh, what we have, we have, uh, uh, we want to have a remove button straight on the index. Let's, uh, add it. We don't need all these. We are just going to add, uh, equals button to delete, uh, login path at login method, delete. Okay, so here have the delete button. And let's also, uh, not allow the user to log out from the current device that he is, uh, in. So we can log out of other devices. We can not log out from the current device. Let's go to slash logins and let's display the current device. How can we, uh, do this? Uh, let's say If login dot device ID equals session device id, then we're going to say that this is the current device else. They're going to have the button to delete. Let's see here. We have a delete button. Here we have the current device button. Okay, it looks quite good. And let's, uh, add some additional information about the specific device that we are logged in into. And we can do it based on our user agent. Let's know whether this is a tablet or a desktop or a mobile phone. We can do it with using the GEM device detector. So Gem device detector. So based on the user agent, it can give us different additional data. Let's install this GEM bundle. Add the device detector. I made typo. Let's see if the gem has been, uh, added to the GEM file. Okay, I must have typed it correctly this time. And, uh, let's get some additional information about, uh, our device. So, uh, uh, let's, uh, run the device detector somewhere. Um, somewhere here we can, uh, say equals uh, device, uh, details. Login, user agent, let's create this method. Device details in the helper, login, helper dev device details based on the login. Uh, yeah, based on the user agent. And let's put a debugger here. So, uh, I'm going to start the real server. And here I'm in the debugger and I have the information about the user agent. And, uh, we can draw the device equals device detector. Uh, new based on this user agent, and we have some additional information. Let's run device and we can get.name. So Safari OS name, uh, uh, device type. So we know that it is Safari Mug desktop. Some good additional metadata. Let's, uh, display this data also, uh, in, uh, our helper. I'm going to say we have device, uh, name, device or type and device. Uh, device type. We're going to display them. Uh, how did I invoke device detector? Uh, join, uh, with, uh, slash for example. Okay, let's skip it now. And now when I refresh, you see, uh, instead of just displaying this user agent information that is not very useful, I can display that this is Safari Mug Desktop, and this is Edge Mark desktop. Uh, so yeah, that looks much better. Uh, and just this way you can allow a user to see his, uh, logins or devices that a user is logged in with and look out of other devices Here, I see that, uh, yeah, let's just add some border because it's not truly visible and let's remove the great action. We can't really create it. Okay, here we go. So here we see information about our logged in devices. I see that the, this is the current device and I can log out from another device. I clicked on, log out here. I refresh, I'm logged out. Uh, if I log in again, the device has been added. If I log out, the device has been removed, so looks quite good. And just like this, you can allow a user to, uh, have a list of all the devices that, uh, he's logged in with, and remote log, log out from another device, just like Facebook does it here. And your application is going to be, uh, considered more secure. So thanks for being with me and see you in the next one. --- # #164 The illegal loophole to scaffold Tables in Rails 7 - URL: https://superails.com/posts/superails-164-scaffold-tables-in-rails-7-custom-scaffold-templates - Published: 2024-04-01 - Duration: 06:51 - Access: free - Topics: tailwindcss, scaffolds Since Rails 7 you can not scaffold tables in the index view any more. Instead you have a list of cards. Here's how you can bring back tables to your scaffolds. DHH hates this one simple trick! Episode source code: https://github.com/corsego/164-scaffold-erb-tables/commit/bbcd2d880824dfb838564b86024a8dd902028cf1 Based on this blogpost: https://blog.corsego.com/custom-scaffold-templates Resources: - https://www.reddit.com/r/rails/comments/1bb0ixo/why_did_rails_7_kill_scaffolding/ - https://www.reddit.com/r/rails/comments/pztx9g/scaffold_index_no_longer_generating_html_tables/ - https://discuss.rubyonrails.org/t/back-again-after-a-long-time-rails-7-scaffolds-table-view/80967 - https://stackoverflow.com/questions/71357307/no-table-view-after-crud-scaffolding - https://github.com/rails/rails/pull/41210 - https://github.com/rails/rails/blob/ecb2850a04adc6c6665f9a30a1d60ca73965ccfa/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt --- # #163 Instant page loads with Turbo 8 prefetch (aka InstantClick) - URL: https://superails.com/posts/superails-163-instant-page-loads-with-turbo-8-prefetch-aka-instantclick - Published: 2024-03-21 - Duration: 06:01 - Access: free - Topics: hotwire, turbo - Playlists: Just enough Hotwire for Rails developers Turbo 8 has been released! One of the core new features - hover-click preload acceleration (ala InstantClick). InstantClick makes an assumption about potential user behaviour: now whenever you hover on a link, it will fire a GET request to retrieve that page. So when you actually click on the link, it will load faster. Remarkable how effective it is for perceived performance. It can save hundreds of ms saved in many cases! Episode source code: https://github.com/corsego/163-turbo-8-instantclick-prefetch/commit/bf7e1b5971aa8aa31341c4ccec96212640514b41 Based on this blogpost: https://blog.corsego.com/turbo-8-instantclick-prefetch --- # Friendly Show S2E4 Ufuk says there is definitely NO consipracy around RailsConf 2024 in Detroit! - URL: https://superails.com/posts/friendly-show-s2e4-ufuk-says-there-is-definitely-no-consipracy-around-railsconf-2024-in-detroit - Published: 2024-03-20 - Duration: 45:40 - Access: free - Playlists: Friendly Show Podcast Audio version: https://www.friendly.show/2278525/14689994-why-detroit-meet-ufuk-the-secret-organizer-behind-rails-conf-2024 RailsConf is happening on 7-9 of May! And Ufuk is the lead organizer! He recently joined the Ruby Central board of directors. He is on the Shopify infrastructure team, working on making Ruby on Rails a better ecosystem 💪 We discuss: - What makes RailsConf 2024 special? - What is "RubyConf Hack Day"? - Where does the ticket price come from? - Why is RubyCentral so important to our community? --- # #162 Gem Ferrum - Generate PDF and PNG with Headless Chrome API. No Puppeteer, no NodeJS - URL: https://superails.com/posts/superails-162-gem-ferrum-generate-pdf-and-png-with-headless-chrome-api-no-puppeteer-no-nodejs - Published: 2024-03-11 - Duration: 24:16 - Access: free - Topics: ferrum, wickedpdf, grover, puppeteer, html2pdf, pdf - Playlists: Build an OpenGraph automation tool Ferrum is Headless Chrome Ruby API. Ferrum lets you open a headless chrome browser and perform different actions: - visit web page - HTTP authenticate - find element by css/id - click link - fill in input field - take screenshot of a page - save page as PDF In this episode we will: 1. open page in new tab in PDF format 2. save page as PDF 3. save page as PNG Unlike Grover, it does not require Puppeteer and NodeJS dependencies! PRO MODE: Convert URL to PNG/PDF with UrlBox: https://urlbox.com/screenshot-api?ref=sr Urlbox also helps to block Ads & Popups, Bypass Captchas, Auto-Accept Cookies Episode source code: https://github.com/corsego/158-gem-ferrum/commit/34cd066e06955a84b8326348aa8b99efdbfc7e51 Based on this blogpost: https://blog.corsego.com/gem-ferrum-generate-pdf 0:00 Thanks, URLBOX 2:01 why Ferrum 4:48 URL to PNG 6:10 URL to PDF 7:07 open page as PDF 10:28 CSS for media print 12:10 attach invoice as PDF with ActiveStorage 16:45 attach invoice as PNG 22:11 PendingConnectionsError ## Chapters - 0:00 Thanks, URLBOX - 2:01 why Ferrum - 4:48 URL to PNG - 6:10 URL to PDF - 7:07 open page as PDF - 10:28 CSS for media print - 12:10 attach invoice as PDF with ActiveStorage - 16:45 attach invoice as PNG - 22:11 PendingConnectionsError --- # #161 Import ICS Calendar events with Gem iCalendar - URL: https://superails.com/posts/rails-161-import-ics-calendar-events-with-gem-icalendar - Published: 2024-03-09 - Duration: 14:41 - Access: free - Topics: calendar I love building calendars! ICS is the default format for a calendar file. Today we'll learn to read an ICS file and import it into our app. Recently 37signals released "Import ICS events" as a new feature in Hey Calendar. You can do it too! Episode source code: https://github.com/corsego/161-import-ics-calendar-events/commit/0e3704fc5728af60df6bc24f38062f46abfda4ba Based on this blogpost: https://blog.corsego.com/import-ics-to-calendar 0:00 read the ICS format 2:00 iCalendar gem, parse ICS in rails console 4:24 Form to upload ICS file and save in database 10:00 Upload multiple ICS files 12:15 Write tests ## Chapters - 0:00 read the ICS format - 2:00 iCalendar gem, parse ICS in rails console - 4:24 Form to upload ICS file and save in database - 10:00 Upload multiple ICS files - 12:15 Write tests --- # Friendly Show S2E3 Meet SQLite Jesus, Stephen Margheim - URL: https://superails.com/posts/meet-sqlite-jesus-steven-memenheim - Published: 2024-03-03 - Duration: 01:02:56 - Access: free - Playlists: Friendly Show Podcast Stephen is on a mission to make SQLite good enough for production. We talked about: - SQLite and Litestack - contributing to open source - being "the guy" in a niche, creating your own luck --- # #160 Meta Tags. Open Graph, SEO, social sharing previews - URL: https://superails.com/posts/rails-160-meta-tags-open-graph-seo-social-sharing-previews - Published: 2024-02-26 - Duration: 24:06 - Access: free - Topics: meta tags, seo - Playlists: Build an OpenGraph automation tool Meta tags help to render your website nicely in the browser, in search engines, and in social media link previews. Favicon, site name, page title, page description, and page image are the most impactful meta tags. Learn to set meta tags without and with gem "meta-tags". Learn to preview meta tags by specialised crawler tools. Episode source code: https://github.com/corsego/160-meta-tags-og/commit/d29de3545657f0fcf5caefda8f705d8c616cb724 Based on this blogpost (it contains links to all the crawler tools): https://blog.corsego.com/meta-tags-without-a-gem 0:00 How do meta tags work 3:30 meta page title 5:09 meta favicon 9:14 meta description 7:10 meta tags abstraction 9:15 meta image 10:38 page-specific meta tags 12:28 install and use gem meta-tags ## Chapters - 0:00 How do meta tags work - 3:30 meta page title - 5:09 meta favicon - 9:14 meta description - 7:10 meta tags abstraction - 9:15 meta image - 10:38 page-specific meta tags - 12:28 install and use gem meta-tags --- # #159 Access localhost anywhere with Ngrok - URL: https://superails.com/posts/rails-159-access-localhost-anywhere-with-ngrok - Published: 2024-02-12 - Duration: 06:40 - Access: free - Topics: ngrok, development, localhost Ngrok gives a public URL to your localhost. This is useful for testing from other devices, working with external APIs that require a real URL. Based on this blogpost: https://blog.corsego.com/ngrok P.S.1. If you hit the limit, consider alternatives! P.S.2.. I am not affiliated with Ngrok. --- # Friendly Show S2E1 Andy Croll - CTO at CoverageBook, Brighton Ruby, RailsConf - URL: https://superails.com/posts/friendly-show-s2e1-andy-croll-cto-at-coveragebook-brighton-ruby-railsconf - Published: 2024-02-10 - Duration: 42:03 - Access: free - Topics: railsconf - Playlists: Friendly Show Podcast Join the conversation with the serial conference organizer Andy Croll, where we talk about how he started Brighton Ruby, why he's still doing it, how he's helping organize RailsConf this year, and about First Ruby Friend and why you should hire junior developers. https://brightonruby.com/ https://firstrubyfriend.org/ https://railsconf.org/ --- # #158 Build a Calendar from zero. No external dependencies! - URL: https://superails.com/posts/rails-158-build-a-calendar-from-zero-no-external-dependencies - Published: 2024-02-08 - Duration: 22:40 - Access: free - Topics: calendar, simple_calendar - Playlists: Build a Calendar A long time ago I thought it impossible to build a calendar on your own; I thought you need an external library. However building a calendar is much easier than you think! Let's build a monthly view calendar, and populate it with events! Episode source code: https://github.com/corsego/158-calendar-app/commit/1702b7f385b80064635a96b0038aaf451999659b Based on this blogpost: https://blog.corsego.com/build-a-calendar-from-zero 0:00 Existing solutions 2:27 Build a monthly calendar 13:24 Highlight Today's date 15:00 Display events 19:00 Reusable calendar wrapper ## Chapters - 0:00 Existing solutions - 2:27 Build a monthly calendar - 13:24 Highlight Today's date - 15:00 Display events - 19:00 Reusable calendar wrapper --- # Friendly Show S2E2 Amanda Perino. RailsWorld 2024. CFP open. How to become a speaker? - URL: https://superails.com/posts/friendly-show-s2e2-amanda-perino-railsworld-2024-cfp-open-how-to-become-a-speaker - Published: 2024-02-01 - Duration: 20:55 - Access: free - Topics: rails world, rails foundation - Playlists: Friendly Show Podcast Join the dialogue with Amanda Perino, CEO of the Rails Foundation, as we unpack the intricacies and excitement of Rails World 2024. Amanda brings her unique perspective to the table, revealing the behind-the-scenes of organizing a top-tier conference in Toronto and how they're shaping it to be an even more engaging and community-driven event. We reminisce about past encounters that birthed our podcast and hear updates on both the Rails community's beloved doggo - Milo, and the technical leaps expected with Rails 8. What's more, we take your feedback to heart, promising more networking woven into the fabric of this year's slightly streamlined talk schedule. Our conversation with Amanda Perino doesn't stop there—venture with us as she sheds light on the electrifying atmosphere of RailsVeld at the Evergreen Brickworks and teases at the global roadmap for future Rails events. Imagine the potential of Rails conferences in places as diverse as Colombia and Slovakia! Plus, we get a sneak peek into the Rails Foundation's 2024 agenda, focusing on enhancing documentation, education, and marketing for the community. --- # Is DocRaptor worth it? Puppeteer, DocRaptor, WeasyPrint - URL: https://superails.com/posts/is-docraptor-worth-it-puppeteer-docraptor-weasyprint - Published: 2024-01-30 - Duration: 16:07 - Access: free - Topics: docraptor, pdf, puppeteer, ferrum In E155 I talked about DocRaptor - a premium API for generating and storing PDF via the PrinceXML HMTL-to-PDF technology. Today I am joined by James Paden who explains the intricacies of PDF generation and talks about free alternatives (puppetteer, weasyprint). I have always considered "HMTL to PDF" is a fundamental technology that should be open source. My key takeaways: - To generate a one-page document, use a headless browser PDF generator. - To have full control of mulip-page document formatting, use Prince/Docraptor. Free HTML to PDF (python) tool recommended by James: https://weasyprint.org/ Disclaimer: This episode is sponsored by @docraptorhtmltopdfdocument8232 . Thanks guys! --- # #157 Calendar Pagination with gem Pagy - URL: https://superails.com/posts/ruby-on-rails-157-calendar-pagination-with-gem-pagy - Published: 2024-01-21 - Duration: 23:43 - Access: free - Topics: calendar, pagination, pagy - Playlists: Build a Calendar We are all used to pagination by clicking a prev or next page link. But what if we could paginate by year/month/day? Some time ago I asked Domizio Demichelis (@ddnexus), the creator of Pagy gem, this question. And it resulted in the creation of Pagy::Calendar! Let's explore how we can add calendar pagination to our app and make it useable. Episode source code: https://github.com/corsego/157-pagy-calendar/commit/008a13dee887e57a5d6b8c81240950677adf9020 Based on this blogpost: https://blog.corsego.com/pagy-calendar-pagination 0:00 Calendar pagination demo 02:20 Install Pagy Calendar 6:58 Tailwind styling 7:56 Paginate by year/month/week/day 11:08 Toggle calendar and normal pagination 12:58 Create an event for current date 17:04 Fix bug with max year being wrong 19:25 redirect back to page in calendar 22:11 link to Today ## Chapters - 0:00 Calendar pagination demo - 02:20 Install Pagy Calendar - 6:58 Tailwind styling - 7:56 Paginate by year/month/week/day - 11:08 Toggle calendar and normal pagination - 12:58 Create an event for current date - 17:04 Fix bug with max year being wrong - 19:25 redirect back to page in calendar - 22:11 link to Today --- # FriendlyShow S1E10 Finale. 2023 summary, 2024 plans. What's coming to Rails 8? - URL: https://superails.com/posts/friendlyshow-s1e10-finale-2023-summary-2024-plans-what-s-coming-to-rails-8 - Published: 2023-12-29 - Duration: 01:06:17 - Access: free - Topics: rails8 - Playlists: Friendly Show Podcast Season 1 finale! Today we talk about: - our reflections about 2023 and plans for 2024 - Rails 8 features Highlights: 👶 Being a young father ⛵️ Sailing ✈️ visited 5 Ruby events together 🍻 met new Ruby friends 🎙️ 10x FriendlyShow podcast episodes 🎬 68 videos & ✍55 blogposts 🥑 All-in on AVO 0:00 2023 summary, 2024 plans 37:25 discussing new Rails 8 features Will there be a Season 2? Let us know if you want one! ## Chapters - 0:00 2023 summary, 2024 plans - 37:25 discussing new Rails 8 features --- # #156 Turbo 8 Morphing in real life - URL: https://superails.com/posts/rails-156-turbo-8-morphing-in-real-life - Published: 2023-12-25 - Duration: 32:58 - Access: free - Topics: frontend, turbo8, morphing, hotwire - Playlists: Just enough Hotwire for Rails developers Turbo 8 adds a new technique of sending HTML over the wire: "refresh current page, preserve scroll". This new approach opens EASYER ways to render elements on current page without passing too much context to TurboStreams. 👮‍♂️ however there some tricky scenarios that you should be aware of! 🚨 Level 1: We will try using PageRefreshes on a new Rails app. Level 2: Use page refreshes to live-update nested comments in a real app. Get access to superails.com source code: 1. Sign in to superails.com with Github 2. Leave a comment under the video Episode source code: https://github.com/corsego/156-turbo8/commit/24277eb152f3f0a96f693c454b9244f36604f687 Based on this blogpost: https://blog.corsego.com/hotwire-turbo-8-morphing 0:00 Turbo 8 intro 2:44 Update Table with TurboStreams 6:10 Install Turbo8, enable morphing 10:16 preserve some elements after morph 11:46 live updates 16:18 Turbo Morphing in a real app ## Chapters - 0:00 Turbo 8 intro - 2:44 Update Table with TurboStreams - 6:10 Install Turbo8, enable morphing - 10:16 preserve some elements after morph - 11:46 live updates - 16:18 Turbo Morphing in a real app --- # Friendly S1E9 Mariusz Koziel - Ruby community in Poland, how to become CTO and CEO - URL: https://superails.com/posts/friendly-s1e9-mariusz-koziel-ruby-community-in-poland-how-to-become-cto-and-ceo - Published: 2023-12-12 - Duration: 46:26 - Access: free - Playlists: Friendly Show Podcast Mariusz is an old friend of ours. Over the last few years we've chilled 🍻 at many events around 🇪🇺 He works at an iconic ruby consulting shop in Warsaw, Poland (Visuality), where he was recently promoted from CTO to CEO. Mariusz gives back to the 💎 community by organizing local meetups (Warsaw, Lodz) and organizing conferences. In July 2023 he organized the first "Warsaw Community Conference". Just 6 months later, he is organising the second edition of Warsaw Community Conference on 02 Feb 2024. It can be a great opportunity to visit Warsaw and meet old friends! --- # #155 Generate, store, email PDF invoices. DocRaptor API, HTML to PDF, ActiveStorage, ActionMailer - URL: https://superails.com/posts/155-generate-store-email-pdf-invoices-docraptor-api-html-to-pdf-activestorage-actionmailer - Published: 2023-12-04 - Duration: 39:35 - Access: free - Topics: docraptor, api, pdf Let's solve a classic problem: generate an invoice and email it as PDF to a client. Historically I would do it with gem wicked_pdf. But the underlying technology wkhtmltopdf is now Archived! So, we need to find other solutions. DocRaptor is an affordable API wrapper around the expensive Prince technology, that gives you a lot of options for rendering and storing PDF. Episode source code: https://github.com/corsego/155-docraptor/commit/0cce2a5367ae4222fc5bd3bc8f70f1a75f75ebe1 Based on this blogpost: https://blog.corsego.com/docraptor-html-to-pdf Podcast talking about Docraptor: https://www.indierails.com/15 Docraptor gem: https://github.com/DocRaptor/docraptor-ruby Docraptor ruby docs: https://docraptor.com/documentation/ruby DocRaptor: https://docraptor.com P.S. In the episode I forgot to mention another powerful feature that I love: generating a PDF from an URL. P.S.2. Docraptor pricing is per document, not per page! 0:00 The PDF problem 4:50 Demo of Invoice app 6:15 Generate a PDF with DocRaptor 10:20 Generate an Invoice PDF 13:15 PDF layout file 14:51 Store PDF with ActiveStorage 21:37 Preview, download PDF 27:15 Email the PDF 30:47 DocRaptor document hosting 34:50 Final Demo 37:50 Final thoughts ## Chapters - 0:00 The PDF problem - 4:50 Demo of Invoice app - 6:15 Generate a PDF with DocRaptor - 10:20 Generate an Invoice PDF - 13:15 PDF layout file - 14:51 Store PDF with ActiveStorage - 21:37 Preview, download PDF - 27:15 Email the PDF - 30:47 DocRaptor document hosting - 34:50 Final Demo - 37:50 Final thoughts --- # #154 Avo 3 upgrade - URL: https://superails.com/posts/ruby-on-rails-154-avo-3-upgrade - Published: 2023-11-25 - Duration: 14:46 - Access: free - Topics: avo AVO 3 was released a few days ago. I use AVO to manage posts, playlists, tags, comments and users in Superails.com. Follow me as I upgrade Avo 2 to Avo 3! Avo3 announcement: https://avohq.io/blog/avo-3-launched Avo github: https://github.com/avo-hq/avo Avo3 upgrade guide: https://docs.avohq.io/3.0/avo-2-avo-3-upgrade.html#refactor_the_grid_view_api Episode source code: currently private, leave a comment if you want access! 0:00 bundle update avo3 2:10 gem avo_upgrade 3:54 fields 6:22 search 8:30 licence 8:58 actions 9:35 grid 11:20 filters 12:16 lambda resource 12:56 grid FIXED ## Chapters - 0:00 bundle update avo3 - 2:10 gem avo_upgrade - 3:54 fields - 6:22 search - 8:30 licence - 8:58 actions - 9:35 grid - 11:20 filters - 12:16 lambda resource - 12:56 grid FIXED --- # #153 Refactoring Hotwire Modals - URL: https://superails.com/posts/ruby-on-rails-153-refactoring-hotwire-modals - Published: 2023-11-21 - Duration: 10:03 - Access: free - Topics: modals - Playlists: Just enough Hotwire for Rails developers I AM SORRY for not providing the complete, best approach right away. In E151 we connected turbo frame "modals" with the html dialog element. However this approach was not optimal: - We were needlessly adding a stimulus controller on the BODY html tag; - dialog html was present in the layout file, even if we do not need it right now; Let's clean up the mess and make our modals even better! ✨ Special thanks to @ledockol for pushing me on this one! 💪 Episode source code: https://github.com/corsego/151-dialog-turbo-modals/commit/6f4869005c28aca372319cc4f0f9e7e1ddd8a0e1 Based on this blogpost: https://blog.corsego.com/turbo-modal-dialog#refactoring 0:00 old implementation 0:38 I AM SORRY 1:12 move modal dialog from body to new.html.erb 5:40 reusable dialog wrapper 7:59 restrict opening modals in new tabs ## Chapters - 0:00 old implementation - 0:38 I AM SORRY - 1:12 move modal dialog from body to new.html.erb - 5:40 reusable dialog wrapper - 7:59 restrict opening modals in new tabs --- # #152 Custom Turbo Streams. How to redirect from a form that is inside a turbo frame? - URL: https://superails.com/posts/ruby-on-rails-152-custom-turbo-streams-how-to-redirect-from-a-form-that-is-inside-a-turbo-frame - Published: 2023-11-19 - Duration: 20:37 - Access: free - Topics: modals, hotwire, turbo frames - Playlists: Just enough Hotwire for Rails developers How to redirect from a form that is inside a turbo frame? This is a very specific question that you will definitely have after working with Hotwire for some time. There are many possible solutions, some better that the others. Let's explore the problem and different strategies to solving it: 1. add and click a hidden link 2. new turbo stream redirect action 3. adding turbo stream helpers and params 0:00 Introduction 0:49 Full page redirect does not work? 4:00 Break out of turbo frame does not work? 4:55 turbo stream hidden link 10:50 do not cache hidden link 12:42 Create a New Turbo Stream Action (best redirect) 14:56 A Turbo Stream action with params (more complicated) 19:50 Final demo Episode source code: https://github.com/corsego/151-dialog-turbo-modals/commit/c2cecbae1da1e327f8ad91c403531a8ec738a3d1 Based on this blogpost: https://blog.corsego.com/custom-turbo-stream-actions ## Chapters - 0:00 Introduction - 0:49 Full page redirect does not work? - 4:00 Break out of turbo frame does not work? - 4:55 turbo stream hidden link - 10:50 do not cache hidden link - 12:42 Create a New Turbo Stream Action (best redirect) - 14:56 A Turbo Stream action with params (more complicated) - 19:50 Final demo --- # Friendly Show E8 Andrea Rocca, Hotwire Cookbook, Rails UI, and flying foxes - URL: https://superails.com/posts/friendly-show-e8-andrea-rocca-hotwire-cookbook-rails-ui-and-flying-foxes - Published: 2023-11-18 - Duration: 39:41 - Access: free - Topics: friendly show - Playlists: Friendly Show Podcast Surprise surprise: we invited our friend Andrea to the FRIENDLY show! We talked about: 💰 How Andrea switched to programming and became CTO 🙆‍♂️ Everybody needs a Paul in their life! 👨🏻‍🍳 who stole the Hotwire Cookbook? 💅 Rails UI libraries 🦘 Ruby Australia --- # #151 Advanced Hotwire modals - URL: https://superails.com/posts/ruby-on-rails-151-advanced-hotwire-modals - Published: 2023-11-15 - Duration: 21:29 - Access: free - Topics: hotwire, modals, turbo frames, html dialog - Playlists: Just enough Hotwire for Rails developers 63 + 142 = 151. Why? In Ep 63 we learnt to build "modals" with Hotwire. In Ep 142 we learnt to use the native HTML dialog element Now let's combine them to create the perfect modal! We will dive deep into navigating within a Turbo Frame and breaking out of it when needed. We will render updates without page refresh on the background with Turbo Streams. We will add a tiny bit of StimulusJS to control the modal. We get to play with the whole Hotwire stack for this advanced behaviour! I am really proud of this episode. Episode source code: https://github.com/corsego/151-dialog-turbo-modals/commit/8864d6aab49ccdeada8cfa43bdf1208f3fe777af Based on this blogpost: https://blog.corsego.com/turbo-modal-dialog 0:00 Turbo Dialog Modals demo 1:48 Copy HTML Dialog form Ep 142 3:13 Turbo Frame within modal 7:00 Navigation within modal 8:50 Close modal after create 12:40 Add new comment to the list 14:20 Clean up modal after close 17:01 Edit, Show, Destroy within modal 20:44 Perfect Turbo Modals: Final demo ## Chapters - 0:00 Turbo Dialog Modals demo - 1:48 Copy HTML Dialog form Ep 142 - 3:13 Turbo Frame within modal - 7:00 Navigation within modal - 8:50 Close modal after create - 12:40 Add new comment to the list - 14:20 Clean up modal after close - 17:01 Edit, Show, Destroy within modal - 20:44 Perfect Turbo Modals: Final demo ## Transcript Hello. So in episode 63, we learned how to build models with hot fire. And in episode 142, we learned how to build models using the, uh, dialog H TM L element that is now supported by all the browsers. And, uh, I thought, what if we combine the two approaches, the hot fire pod and the H ttm L dialog pod to create the perfect models? So here's what I came up with. Uh, I have an empty list of comments. I click on create new comment. I have this, uh, um, model dialogue opened. I have, uh, possibility to click on cancel to close it. I can click on escape to close it. I can click outside of the model to close it. And, uh, let's say I want to create a comment. I click on create, you see the errors have been run it correctly. I click on, uh, let's add something in the body, like, hello. You see, uh, the model has been closed on successful form submit. And, uh, the element has been added to our page. Let's say I want to show the element and I want to show it inside another model. I click, click on Show. And you see I have this comment in its own model. I can, uh, go to edit it, it navigates to the edit. I click Update, it goes back to the show page. And let's say I want to destroy it. I destroyed it. The model has been closed. In this case I wanted it to be closed. And, uh, the comment has been removed from the list. So we are going to learn how to, um, close models, how to navigate, uh, from one model to another, and, uh, how to make the work nicely with HTML dialogue plus, uh, hot wire tool. So let's start with, uh, a blank state. We're gonna start from the beginning here. I just have a basic scaffold of comments. So comments, controller comment that has validation of body presence. And, uh, a comment just has a body. Okay, so here is a blank state, and let's add, uh, uh, this kind of turbo models vitalic element. I'm going to start, uh, with episode 142. I strongly recommend you to watch, uh, both of these, uh, before, uh, going to this episode. This kind of the third part in the model series. So, um, let's go, uh, to, uh, the final code of this, uh, episode and, uh, here had this JavaScript to handle the dialogue. Uh, I'm going to copy it. So I will say rail generate stimulus dialogue. I'm going to go to this, uh, generated dialogue controller. I'm going to copy, uh, this, uh, code. Again, we discussed how all this code works in episode of 142, but basically we have some code to, uh, enable and disabled body scroll, uh, handling the opening of a model, handling the closing of the model, and handling, uh, closing of the model. If we click outside of the model, and now let's go with this, uh, UG element into our application layout. So I will go to layouts application and put it somewhere, uh, somewhere here inside the body tag. Okay, now I will go back to the page. I will click on open dialog. And you see we have a dialog that, uh, opens and we have all the default model behaviors. Looks fine. Now, we want to actually render this new comment, not in a separate page, but inside this, uh, dialogue. How can we do it? Uh, first of all, I'm going to say that, uh, this is going to be, uh, this new comment clicking on it should, uh, open that dialogue. So here, uh, I have this button with data action, click dialogue open. So click on here, opens the dialogue. I'm going to add this data action to our LinkedIn new comment. So I'm going to go to the index page, and here I'm going to say, uh, data action. Uh, and the action is click dialog open. Now let me refresh. I click on your comment and well nothing happens. Uh, maybe let's open our console to debug. Okay, I click on your comment, and again, nothing happens. Why? Because this dialog, uh, controller is not initialized. Uh, here you see the dialog starts here and ends here. So the yield, the place where the, um, where the link where this link is rendered does not, uh, contain this controller. So it wouldn't work. I would need to put the dialog controller maybe on the whole body so that we can, uh, open the dialog out of anywhere in our application. So I will remove the Steve and, uh, the dialog is on our body tag. I will, uh, refresh. Let's, uh, go back. Uh, I click, uh, on your comment and you see for a brief moment of time, it opened a model and, uh, then already it rejected to the new page. So we don't want to have a full page direct. We want to open, uh, this inside a turbo model. So, uh, here inside the body of the dialogue, uh, here, I'm going to put a tag, a turbo frame tag, I will say equals turbo frame tag. Let's give it an arbitrary name of modal, and I'm going to remove some, uh, other placeholder code that we don't need anymore. So inside the, the dialog, we just have a balance to close the model and, uh, uh, tub of frame that can render anything. So I click on new comment, and you see there's still a full page direct. So we want to say that this, uh, link opens not in new page, but inside the UBA frame model. So turbo frame model. So clicking on this button should open the HTML model and it should, uh, navigate to the new page and join this new page inside this TOBO frame model that we have in our application layout. Let's refresh and try if it to see if it works. I click on new command and you see we get this content missing. And in the GS errors we see that we had an visit, a successful visit to the new page, but it didn't find the tub frame with the idea model. So it gives us the content missing error, not have this content missing error. We're going to actually wrap this page into the tub frame tag model. So I will say equals to ball frame tag model do, and I'm going to wrap it all into this. Okay, I will refresh now, click on new comment, and you see the new comment has been rendered in the model. Let's just demonstrate again, I just click on new model here and it renders an empty model. I click on new comment here and it renders, uh, the new comment page inside the model. Now let's uh, click on create comment. You see the errors are being rendered. I will click, uh, and add something to the body, like, hello, create comment. And, uh, there has been a successful form submission, but we again, get this content missing because, uh, if we go to our controller on create, on successful Create, we go to comments URL, so tries to open the show page. And inside the show page, we don't have this store frame model. So by default it wants to navigate to the show page. Let's actually navigate to the show page inside the model. Let's do it. I will just, uh, go with this tub frame tech model and wrap it, uh, around the show page and let's see if it works. I will, uh, create a new comment full. And you see the comment has been created and we have navigated to the comment show page. Here it is, uh, let's uh, say, we'll also want to navigate to edit the comment. So at the moment, I will click edit and it gets content missing. So, uh, we are just going to do, uh, here we have this edit. We're going to say, uh, Yeah, we're just going to wrap, uh, the edit page into the to frame model also. Hmm. And, uh, let's see. So I go to show the comment, or maybe let's do it via this model. So, uh, model, create, comment, uh, we can, uh, go to edit, update. Uh, yeah, so it seems to be working. I can go to show. And you see we can do some navigation, uh, and, uh, updates inside a model Looks. Uh, okay. And, uh, what if, uh, we want to break outta the model, for example, uh, when we create a new comment, I want to not just direct, I want to actually close the model. How can we do it? Well, uh, for this, we're going to go back to episode 63. I will, uh, go to my notes and, uh, here had this, uh, to submit and, uh, action that would, uh, check if there was a successful form submission. And based on this, we could, uh, potentially, uh, close the model. Let's try again, this behavior to our current model. So, uh, I will add this, uh, these lines, Uh, somewhere here. And now let's try, uh, triggering, uh, this submit end. So, um, we have this action and uh, let's, uh, wrap this action around the new page. So, uh, let's say I will have a new div and, uh, it'll have action equals to the submit end. I'm going to put another div here. So, um, this form is going to listen to tuba submit end, and if there was a successful form submission, they're going to handle UBA model submit end. Actually, it's not tuba model controller, it is dialogue controller. And, uh, final, just have a console lock here. So, um, yeah, I think we're missing something. Okay, this should work. So let's put a console log, um, console log e detail dot success. So, uh, let's refresh. I have our console broken. I will click on create comment. Uh, nothing has been rendered. Let's, uh, create a comment actually. Okay, I don't see anything, uh, here. Uh, advise it. So action Tobo and Tobo model Submit and, uh, the new thing. Yeah, I think this should work. Interesting. Uh, okay, let's not, uh, do this console locked in. Let's try to use the hide the model directly. And in our case, it's not, uh, hide model. It is, uh, this dot close. And the model target is, uh, the whole dialogue element. So let's, uh, create a new comment, create comment, and we still have this full page navigation. So this, uh, turbo submit and Dex Submit end does not seem to be working. Hmm, obviously, uh, I should have said data action here. Div Data Action, but not just div action. Yeah, uh, okay, I'll just add this, uh, here for us, uh, to understand it better. Yeah, it should be div data Action. Okay, let's, uh, try on scan to just log it in the console. So new comment two, two, create, comment, and it is true. So detail success is true. Let's make another option with false. I will click on create and, uh, well, we get an error. So, uh, only if, uh, e digital success equals true, uh, they're going to do the close. So let's try once again, I'm going to create new comment. Let's say three. The three create comment and the model has been closed, but it has not been added. The comment has not been added to the list. Let's, uh, make it so that the comment is added to the list on top of the list. When we create a new comment, let's see, I create new comment and it's not in the list, I refresh and it is in the bottom of the list. So for this, we're going to use Tobo streams, and I'm going to the comments controller, create action, and I'm going to say format, turbo stream. Do render, turbo stream, turbo Stream. Uh, replace the command with partial comments. Local command, uh, no, actually not replace. Uh, autocomplete is not being friendly. We are going to do a pen. So add on top of the list. So we are going to add the new created comment on top of the list. Let's try, let's make, uh, this did not work. Append to the list of comments via the list of comments. If we go to index, we have this, uh, uh, div with ID comments. Okay, so, uh, 1, 2, 3. Yeah, uh, append makes on the bottom of the list, let's use pre-end. Okay, refreshing, uh, 1, 1 1. And it has been added to the top of the list and the common, uh, model has been closed, so works well. So now when we create a new comment successfully, the model is closed, uh, in the correct way, and uh, the comment is added to the top of the list. But what if we try to just open the initial H TM L model? I click on open dialogue, and you see we still have the old H TTM L that we just submitted the present here. So we closed the model, but we didn't clean the model up from the, uh, old HTML, and we'll need to do it. So to clean up this model from the HTML that we don't need, we are going to go back to our application, HTML, and uh, on this uh, tubo frame tag model, we are going to add the new target. Uh, how should we name the target, let's say frame. So data, um, dialogue target will be frame. And inside this, uh, dialog controller, I'm going to add a new target. Name the frame. And when we close the model, we we're going to clean up everything inside this frame. So we will say this dot frame target, and, uh, we're going to remove, uh, attribute, SRC, uh, let me just not do it for a moment. And let's see what happens without it. Let's just have something here like, uh, an empty submission. Let's say I click cancel, I go back to open this, and you see we still have all this stuff here. If I go to inspect element, um, so, um, inside the dialogue we have, uh, the tub of frame with the URL comments, you, and we don't want it to be with the comments, you, we want it to be just like an empty tub of frame. So now I will add this and uh, I go to new comment. I, uh, close it. I open og, we still have it, but uh, do we still have the, um, SRC? So the source URL of the tuba model? No, we do not. So it's already cleaner, but if it still open the tuba frame tag, we still have, uh, uh, all the new comment stuff. So we are also going to go and say this dot frame target, and we're going to say inner HTML is empty. So we're going to remove everything from inside this, uh, tub of frame. I'm going to refresh. Let's create a new comment. I click cancel. I open og. And you see everyone has been removed from this, uh, model. So we have our cleanup work, uh, in place. Okay? Uh, now let's, uh, try a few other behaviors. Let's say I want to click on edit and edit should lead not to this, uh, edit page. It should lead to, um, yeah, you see we have some ation broken. It should lead to the model. So the same way we did on our index page where we added the data, uh, action, click, dialogue open and to the frame model, we're going to do this same thing on the, uh, edit. So here we have link to edit comment, and I'm adding this OG open to the frame model. So whenever I click on edit, uh, no, maybe not here, let's go back to comments. This is render here inside the comment partial. So okay, I refresh and I click on edit and opens the edit. In the model, I click update. And uh, when I click update, you see in the new page, we close the model when the submit is successful. But in the added page, we do not close the model when the submit is successful. So we will have, uh, redirect within the model to the show page. I click update, we're directed to the show page. It, uh, works. And what if we want to, uh, see the show page? I click on the common show. We want to see the show page inside the model. So in a similar fashion, we are going to add this data dialogue open model to link to show this comment in model. So I refresh, I click on show this comment, you see the show, this comment inside the model looks, uh, uh, fine. Now what if want to destroy the comment? Let's say I click on Destroy, and you see we have this content missing. So actually the comment is being destroyed, but we, uh, try to redirect to basically nowhere. So this is a case where we definitely want to add, uh, this behavior to close the model, uh, on Successful Submit. So I'm going to go to this new action, and, uh, in this comment, I'm going to wrap, uh, whether we have this destroyer action visible, uh, here. So, uh, I'm going to wrap the Destroy into data to submit End. Now, let's refresh. I'm going to show a comment. I'm going to click on Destroy. And, uh, the model has been closed. Uh, it has not been removed from this, uh, comments list, but if I refresh the page, it has been removed, but has it not been removed from the comments list? Because for this, we need some two, uh, streams. So I'm going to go to Destroy, and I'm gonna say four dot two Ball stream. Do, and I will say to Stream, remove the comment from the list. So now let's say I show the comment, I, uh, click on Destroy, the model has been closed, and the comment has been removed from the list. And this way we can remove other comments from the list. Let's remove a few. We have created a real lot of them. And let's, uh, start from a blank state. So let's say I want to create a new comment. I link new comment, it opens in the model, uh, errors have been rendered. I will have something in the body create comment. The model has been, uh, uh, successfully submitted and closed, and the element has been added to the page. If I just try to open the blank model, you see it is empty. Now, let's say, say I want to go to, uh, show this model. I can see this model. Okay, let's close it. I can go to edit, I will edit it. I will click on the update, and, uh, I have a navigation that happened to the show page within the model. So it works fine. I will click on Destroy. The comment has been destroyed, the model has been closed, and it has been removed from the list of comments. So basically in this episode, we learned how to, uh, close models if we want to, how to navigate within the models and how to make the ML dialogue element work nicely with two bo uh, frames and to streams. So thanks for being with me and see you in the next one. --- # #150 Global Search - URL: https://superails.com/posts/ruby-on-rails-150-global-search - Published: 2023-11-09 - Duration: 24:29 - Access: free - Topics: search "Global search" allows you to search multiple attributes of multiple models in a single search form. For example, on superails.com when you perform a search in the navbar you get results for Posts, Tags, Playlists. We will build a scalable global search, so that you can easily add new models and attributes to search on. This video topic was suggested by @Elgnonvis . Want me to do a video about a topic? Add your ideas in the ocmments. Episode source code: https://github.com/corsego/150-global-search/commit/6c25c09caac09a14056d570f389b56303ee97470 Based on this blogpost: https://blog.corsego.com/search-multiple-models 0:00 Global Search examples 2:02 Basic global search 11:02 Advanced global search 23:30 Final demo ## Chapters - 0:00 Global Search examples - 2:02 Basic global search - 11:02 Advanced global search - 23:30 Final demo --- # #149 DataTables - Filters. Form fields OUTSIDE form. Checkboxes, radio buttons - URL: https://superails.com/posts/rails-149-datatables-filters-form-fields-outside-form-checkboxes-radio-buttons - Published: 2023-11-06 - Duration: 22:42 - Access: free - Topics: datatables - Playlists: Datatables Previously we created a table that we can search and sort using gem Ransack, and paginate using gem Pagy. We also made results update without page refresh using Hotwire Turbo Frames. Now let's go DEEPER and have search in the navbar and FILTERS in the sidebar. To do this, we will have to have HTML form fields OUTSIDE of a form. This is an HTML and ERB challenge. We will also cover - form.select vs select vs select_tag - radio_buttons - collection_check_boxes And this is what I call LAYERED DESIGN. PROGRESSIVE ENCHANCEMENTS. STEP BY STEP. Episode source code: https://github.com/corsego/147-datatables-ransack-pagy-search-sort-paginate/commit/1344c9ce0e4f12fb2bb610840d234ddab3c1970c --- # Friendly Show E7 October summary, Brovember plans - URL: https://superails.com/posts/friendly-show-e7-october-summary-brovember-plans - Published: 2023-11-02 - Duration: 36:23 - Access: free - Playlists: Friendly Show Podcast I think it was one of our most chill, relaxed episodes. Take a break and join us as we discuss: - Building a new Rails starter kit? - Rails UI frameworks? - GoodJob vs Sidekiq/Redis? - Best way to do Multitenancy? - Devise vs other authentication approaches? - Avo 3 release date, Avo-2025 UI features - Providing feedback to software - How to make superails.com better? Show notes: Get Helvetic.rb tickets: https://helvetic-ruby.ch/ Helvetic.rb fondue evening RSVP: https://ruby.social/@marioschuettel/111296355470789293 Notion sharding article: https://www.notion.so/blog/sharding-postgres-at-notion Phlex UI: https://www.phlexui.com/ --- # #148 DataTables without page refresh using Hotwire Turbo Frames - URL: https://superails.com/posts/rails-148-datatables-without-page-refresh-using-hotwire-turbo-frames - Published: 2023-11-01 - Duration: 10:59 - Access: free - Topics: hotwire, turbo frames, datatables - Playlists: Datatables, Just enough Hotwire for Rails developers In the previous episode we added Search, Sort, and Pagination to a data table. Turbo Frames will help us make it work WITHOUT PAGE REFRESH! However we will have to solve some potential problems (page refresh, URL, update, redirects). Episode source code: https://github.com/corsego/147-datatables-ransack-pagy-search-sort-paginate/commit/8f4ed9a855cc626a8e71a428cbae714f82daf98b Based on this blogpost: https://blog.corsego.com/turbo-hotwire-custom-search-without-page-refresh 0:00 initial state of data tables, search, pagination 1:50 search without page refresh 4:10 search as you type (without clicking submit) 4:36 update URL as you search (turbo-action advance) 5:25 autosubmit after selecting in a dropdown (without clicking submit) 6:09 update URL as you sort 6:55 break out of turbo frame 9:22 clean up and summary ## Chapters - 0:00 initial state of data tables, search, pagination - 1:50 search without page refresh - 4:10 search as you type (without clicking submit) - 4:36 update URL as you search (turbo-action advance) - 5:25 autosubmit after selecting in a dropdown (without clicking submit) - 6:09 update URL as you sort - 6:55 break out of turbo frame - 9:22 clean up and summary --- # #147 DataTables - search, sort, pagination with Ransack and Pagy - URL: https://superails.com/posts/rails-147-datatables-search-sort-pagination-with-ransack-and-pagy - Published: 2023-10-29 - Duration: 24:54 - Access: free - Playlists: Datatables Datatables - tables that you can search, sort, and paginate. Gem Ransack is an great tool that I use in most of my apps to add search and sort. Let's dive deep into how we can use Ransack! At the end, we will also add pagination of our search results with gem Pagy. When I was starting this channel, a "Ransack" video was one of my first ideas. But ransack is a tool that has so many features, I just didn't know how to approach it in an easy-to-learn manner. I hope you enjoy the example usecases I demonstrate in this screencast. Episode source code: https://github.com/corsego/147-datatables-ransack-pagy-search-sort-paginate/commit/6581e1311af36870a6cddce883274ea803b4b153 0:00 DataTables example 1:40 Ransack SORT 7:13 Ransack SEARCH 9:15 highlight search results 10:15 Date search fields 11:53 Select fields 13:45 One search field for many attributes 14:50 Search association 19:43 Ransack settings 22:08 Pagination with Pagy 24:20 Search, Sort and Pagination works! ## Chapters - 0:00 DataTables example - 1:40 Ransack SORT - 7:13 Ransack SEARCH - 9:15 highlight search results - 10:15 Date search fields - 11:53 Select fields - 13:45 One search field for many attributes - 14:50 Search association - 19:43 Ransack settings - 22:08 Pagination with Pagy - 24:20 Search, Sort and Pagination works! --- # #146 Generate Barcodes with JsBarcode. StimulusJS, Importmaps - URL: https://superails.com/posts/ruby-on-rails-146-generate-barcodes-with-jsbarcode-stimulusjs-importmaps - Published: 2023-10-22 - Duration: 09:24 - Access: free - Topics: stimulusjs, jsbarcode, importmaps This episode is not what you expected, not what you asked for, and not what you deserve. Previously, in episode 56, I talked about generating barcodes with gem "Barby" and storing them in ActiveStorage. But really, why would you want to STORE a programatically generated file (where the result is always the same)? Why not just generate and display it on the client? Being a Full Stack Ruby on Rails dev requires sometimes venturing into the JS world; acknowledging that it's easier to do some tasks on the client side. This is one of the cases. Episode source code: https://github.com/corsego/146-jsbarcode-stimulus-importmaps/commit/d55bbede43fd374cd97d7a2796b609dcaaf57513 Based on this blogpost: https://blog.corsego.com/jsbarcode 0:00 Why use Javascript for Barcodes? 1:35 Auto-generate barcodes for products 3:14 use JsBarcode with importmaps and StimulusJS 6:58 customize the barcode (encoding, size, color) ## Chapters - 0:00 Why use Javascript for Barcodes? - 1:35 Auto-generate barcodes for products - 3:14 use JsBarcode with importmaps and StimulusJS - 6:58 customize the barcode (encoding, size, color) --- # Rails World - Hotwire Cookbook - Yaroslav Shmarov: Common Uses, Essential Patterns & Best Practices - - URL: https://superails.com/posts/rails-world-hotwire-cookbook-yaroslav-shmarov-common-uses-essential-patterns-best-practices - Published: 2023-10-19 - Duration: 31:10 - Access: free - Topics: hotwire, turbo, conference talk, stimulusjs - Playlists: Live talks **SupeRails** creator and Rails mentor Yaroslav Shmarov shares how some of the most common frontend problems can be solved with Hotwire. He covers: - Pagination, search and filtering, modals, live updates, dynamic forms, inline editing, drag & drop, live previews, lazy loading & more - How to achieve more by combining tools (Frames + Streams, StimulusJS, RequestJS, Kredis & more) - What are the limits of Hotwire? - How to write readable and maintainable Hotwire code - Bad practices Slides available at: https://www.icloud.com/keynote/031WsmVqF1yJVtjl2riyTgw_A#RailsWorld_2023_Hotwire_Cookbook_Yaroslav_Shmarov 0:00 Intro 3:37 Turbo Drive 7:16 Turbo Frames 13:32 Git repo: https://github.com/yshmarov/hotwire-cookbook/ 16:07 BACKEND -> FULLSTACK 16:57 Turbo Streams HTTP 20:38 Turbo Streams Broadcast/Websockets/ActionCable 24:10 StimulusJS 30:32 Just Enough Hotwire for Rails Developers video series: https://www.youtube.com/watch?v=csvaYIaBYpw&list=PLdTytUiloS16epXsqHswpCUMND_rksjr4 ## Chapters - 0:00 Intro - 3:37 Turbo Drive - 7:16 Turbo Frames - 13:32 Git repo: https://github.com/yshmarov/hotwire-cookbook/ - 16:07 BACKEND -> FULLSTACK - 16:57 Turbo Streams HTTP - 20:38 Turbo Streams Broadcast/Websockets/ActionCable - 24:10 StimulusJS - 30:32 Just Enough Hotwire for Rails Developers video series: https://www.youtube.com/watch?v=csvaYIaBYpw&list=PLdTytUiloS16epXsqHswpCUMND_rksjr4 --- # Friendly Show E6 RailsWorld, EuRuKo, FriendlyRB - URL: https://superails.com/posts/friendly-show-e6-railsworld-euruko-friendlyrb - Published: 2023-10-09 - Duration: 36:07 - Access: free - Topics: RailsWorld - Playlists: Friendly Show Podcast Adrian and Yaroslav completed a Triathlon by visiting 3 Ruby conferences in the span of 3 weeks! Today we meet to share our fresh emotions after visiting these amazing events. - EuRuKo (European Ruby Conference) took place in Vilnus, Lithuania (550 attendees) - FriendlyRB that was organized by Adrian in Bucharest, Romania (130 attendees) - The first ever RailsWorld conference was in Amsterdam, Netherlands (700 attendees) - Adrian gave an unconference talk about "Build Rails apps 10x faster" at EuRuKo - Yaroslav gave a talk "Hotwire Cookbook" at RailsWorld about common usecases and patterns for Hotwire --- # #RailsWorld opening! - URL: https://superails.com/posts/railsworld-opening - Published: 2023-10-05 - Duration: 13:46 - Access: free - Topics: RailsWorld --- # Yaroslav Shmarov - Hotwire Cookbook and common use cases - Lightning Talk - URL: https://superails.com/posts/yaroslav-shmarov-hotwire-cookbook-and-common-use-cases-lightning-talk - Published: 2023-09-30 - Duration: 09:00 - Access: free - Topics: conference talk - Playlists: Live talks Yaroslav Shmarov is the co-host of the Friendly Show podcast, a content creator on SupeRails.com, and a prominent Ruby community member who focuses on education. He gave a surprise (even from him) talk, encouraging us to try Hotwire. Friendly.rb is an International Ruby Conference hosted in Bucharest at the end of September and focuses on community and creating connections. https://friendlyrb.com --- # A whole train of Ruby developers on RAILS! Literally! - URL: https://superails.com/posts/a-whole-train-of-ruby-developers-on-rails-literally - Published: 2023-09-22 - Duration: 01:29 - Access: free - Topics: RailsWorld I’m visiting @Euruko (European Ruby Conference) in Vilnus, Lithuania right now. They organised a private train to take the attendees around. Everyone on this train is a Ruby Rails developer. How many can you count? --- # Friendly Show E5 André Arko and how we got RubyGems and Bundler. RubyConf 2023: what to expect? - URL: https://superails.com/posts/friendly-show-e5-andre-arko-and-how-we-got-rubygems-and-bundler-rubyconf-2023-what-to-expect - Published: 2023-09-20 - Duration: 01:24:28 - Access: free - Topics: friendly show - Playlists: Friendly Show Podcast André is a long-term maintainer for RubyGems and Bundler - fundamental tools of the Ruby community that we usually take for granted. 🎙️ He joins us to tell the exciting history of how RubyGems and Bundler were created and matured over the years, the challenges of working how the longevity of these tool 💰💰💰 Maintaining a LARGE open source project can become very expensive ($500,000/month) and about where this money comes from. André also talks about RubyCentral (organization that organizes RubyConf, oversees RubyGems and Bundler) and RubyTogether (the financing arm of RubyCentral) We also get to hear about future development plans for RubyGems 🚀🙏 Finally, we talk about RubyConf 2023 and what to expect from the grand event 🏆 ✈️ Visit RubyConf on 13-15 November in San Diego, USA 🇺🇸: https://rubyconf.org/ 💸 Sponsor RubyGems & Bundler: https://github.com/sponsors/rubytogether 💸 Sponsor André: https://github.com/sponsors/indirect --- # Stripe LIFETIME Subscriptions with Rails 7 - URL: https://superails.com/posts/stripe-lifetime-subscriptions-with-rails-7 - Published: 2023-09-10 - Duration: 17:09 - Access: free - Topics: saas, rails starter kit, rails boilerplate, saas boilerplate, stripe, online payments - Playlists: Stripe SaaS with Rails 7 In the previous episode I allowed users to purchase Monthly and Yearly subscriptions via Stripe and gain access to subscriber-only content. Now, let's introduce a "Lifetime" plan where a user pays only once and gains access to content without an end date. Episode source code: https://github.com/corsego/rails-7-stripe-subscriptions/commit/801f01ff812284ef18f8b39f4bed90498f54f45c Series source code: https://github.com/corsego/rails-7-stripe-subscriptions Stripe checkout API: https://stripe.com/docs/api/checkout/sessions/object --- # Stripe Subscriptions with Rails 7 - URL: https://superails.com/posts/stripe-subscriptions-with-rails-7 - Published: 2023-09-09 - Duration: 01:02:06 - Access: free - Topics: stripe, saas, rails starter kit, rails boilerplate, saas boilerplate - Playlists: Stripe SaaS with Rails 7 Previously I did a series on making Stripe subscriptions work with a Rails 6 app: https://superails.com/playlists/stripe-saas-subscriptions-app Now, let's make install Stripe and make subscriptions work from zero on a Rails 7 app! Episode source code: https://github.com/corsego/rails-7-stripe-subscriptions/commit/3e8bd229bff7a192e80dd07a0b03703cb9240338 Stripe gem: https://github.com/stripe/stripe-ruby Stripe docs: https://stripe.com/docs/billing/quickstart --- # #145 StimulusJS Hotkeys without any external libraries. It just works! - URL: https://superails.com/posts/ruby-on-rails-145-stimulusjs-hotkeys-without-any-external-libraries-it-just-works - Published: 2023-09-01 - Duration: 11:49 - Access: free - Topics: stimulusjs, hotkeys, javascript Bring your app one step closer to pefection: Assign keyboard hotkeys to let power users quickly navigate around your app! For simple scenarios, you don't need any any libarary dependencies. 15 lines of StimulusJS can get you very far! In this episode we will assign keyboard shortcuts to open different links in our application. This code is not ruby on Rails specific. It's just HTML & StimulusJS! Episode source code: https://github.com/yshmarov/superails/pull/176/files Based on this blogpost: https://blog.corsego.com/stimulusjs-hotkeys --- # #144 Why I am replacing Turbo Frames with Streams for ActsAsVotable? - URL: https://superails.com/posts/ruby-on-rails-144-why-i-am-replacing-turbo-frames-with-streams-for-actsasvotable - Published: 2023-08-25 - Duration: 09:17 - Access: free - Topics: hotwire, turbo streams, turbo frames, voting - Playlists: Just enough Hotwire for Rails developers Some server-side rendering problems can be equally solved by either Turbo Frames or Streams. Each approach has it's cons and pros. In this scenario I first implemented Voting with Turbo Frames. This invoked quite a lot of extra context and calculations when calling a separate posts_controller#show action. So I replaced Frames with Streams that required no extra context outside the current controller action, and explicitly defined the expected server-side rendered HTML (turbo stream) response. 0:00 How ActsAsVotable is set up on Superails.com 3:28 Turbo Frame refreshes vote buttons 4:50 Turbo Frames grabs new context 5:24 Turbo Streams response to replace Frames 8:37 Conclusion: choose the best tool for the job! ## Chapters - 0:00 How ActsAsVotable is set up on Superails.com - 3:28 Turbo Frame refreshes vote buttons - 4:50 Turbo Frames grabs new context - 5:24 Turbo Streams response to replace Frames - 8:37 Conclusion: choose the best tool for the job! --- # #143 Turbo Permanent: persist Audio Video and Search results while navigating pages - URL: https://superails.com/posts/ruby-on-rails-143-turbo-permanent-persist-audio-video-and-search-results-while-navigating-pages - Published: 2023-08-23 - Duration: 06:30 - Access: free - Topics: hotwire, turbo - Playlists: Just enough Hotwire for Rails developers Often when you visiting media websites, you will want an audio or video player to keep playing even after navigating to another page (like Spotify). data-turbo-permanent is a Turbo Drive feature that makes it very easy to persist (not refresh) elements when navigating to other pages in an app. On Superails I use data-turbo-permanent to persist latest search results when navigating around pages. In this episode we will keep audio and video playing across page navigation. As a bonus, we will look at opening video picture-in-picture when navigating to another page! Episode source code: https://github.com/corsego/143-data-turbo-permanent/commit/3a62b6a809d8cdec636f54b1a7da43a005216cd9 Based on this blogpost: https://blog.corsego.com/turbo-play-audio-across-pages 0:00 Examples of using data-turbo-permanent 2:13 Use data-turbo-permanent 3:24 permanent audio player while navigating around app 4:05 permanent video player 4:30 trigger picture-in-picture video player when navigating around the app ## Chapters - 0:00 Examples of using data-turbo-permanent - 2:13 Use data-turbo-permanent - 3:24 permanent audio player while navigating around app - 4:05 permanent video player - 4:30 trigger picture-in-picture video player when navigating around the app --- # #142 Modals with HTML dialog element, Tailwind and Stimulus - URL: https://superails.com/posts/tailwind-on-rails-142-modals-with-html-dialog-element-tailwind-and-stimulus - Published: 2023-08-16 - Duration: 13:10 - Access: free - Topics: modals - Playlists: Tailwind on Rails HTML "dialog" is the new standard for creating "modals" with less JS and CSS. Is now supported by all browsers! 🌍 Dialog has some behaviours out of the box: ✅ centered on page by default ✅ disables background clicks when open ✅ can be closed with native HTML (without extra JS) ✅ includes CSS to blur/dim background by default ✅ can be closed with Escape key We will explore how to open the "dialog" modal with JS (Stimulus) and add additional styles and behaviours: 👉 Tailwind modal styling 👉 Background color and opacity 👉 Scroll modal 👉 Disable background scrolling 👉 Click outside to close Episode source code: https://github.com/corsego/138-tailwind-layout/commit/f5a635631c492c594c7765b078908e1d52aad799 Based on this blogpost: https://blog.corsego.com/tailwindcss-dialog-modal 0:00 Dialog Modal demo 1:05 HTML dialog element 1:59 Separate buttons to close and submit dialog form 2:52 JS button to open modal 5:35 show VS showModal 6:10 prevent background scroll 7:04 close modal by clicking outside 9:25 modal background styles 10:17 scroll modal content 10:58 default modal width 11:45 blur background 12:26 dialog is much more than just semantic HTML! ## Chapters - 0:00 Dialog Modal demo - 1:05 HTML dialog element - 1:59 Separate buttons to close and submit dialog form - 2:52 JS button to open modal - 5:35 show VS showModal - 6:10 prevent background scroll - 7:04 close modal by clicking outside - 9:25 modal background styles - 10:17 scroll modal content - 10:58 default modal width - 11:45 blur background - 12:26 dialog is much more than just semantic HTML! --- # #141 Responsive Content layout: Grid, Flex, Centered - URL: https://superails.com/posts/ruby-on-rails-141-responsive-content-layout-grid-flex-centered - Published: 2023-08-13 - Duration: 15:00 - Access: free - Topics: tailwindcss, css - Playlists: Tailwind on Rails Previously we have created a responsive general application layout and navigation using TailwindCSS. Now, let's look at a few most common ways of placing content on a page: 1) Responsive column count using GRID 💻💻 big: 📜📜📜 💻 medium: 📜📜 📜 📱 small: 📜 📜 📜 2) center content on a page using AUTO MARGINS 💻💻 big: ↔📜↔ 📱 small: 📜 3) 2 columns on big screen, one column on small screen using FLEX 💻💻 big: 📜📜 📱 small: 📜 📜 Episode source code: https://github.com/corsego/138-tailwind-layout/commit/270e25ba1e489b9cf2c100e76f008504394b9fef Based on this blogpost: https://blog.corsego.com/tailwindcss-content-grid-flex 0:00 What we built before 0:42 Content page examples 2:20 Content Grid Columns 6:00 Content Centered 7:57 2-column Flex layout responsive width 12:48 2-column Flex layout fixed width 14:44 Summary ## Chapters - 0:00 What we built before - 0:42 Content page examples - 2:20 Content Grid Columns - 6:00 Content Centered - 7:57 2-column Flex layout responsive width - 12:48 2-column Flex layout fixed width - 14:44 Summary --- # #140 Click outside to close Menu with Stimulus-Use - URL: https://superails.com/posts/ruby-on-rails-140-click-outside-to-close-menu-with-stimulus-use - Published: 2023-08-12 - Duration: 02:07 - Access: free - Playlists: Tailwind on Rails "Click outside element to hide it" - this might be one of the most common javascript tasks! We will leverage Stimulus-use and it's clickOutside behaviour to trigger an action on a stimulus controller. In our case - to close the dropdown menu. Episode source code: https://github.com/corsego/138-tailwind-layout/commit/f58de741ddd87b52ededc1fcee5b7d0b8f67f229 Based on this blogpost: https://blog.corsego.com/tailwindcss-dropdown P.S. 🙈 Really, I just forgot to include this feature in the previous episode, so here you go. This might be my shortest episode yet! --- # #139 Advanced Navbar Dropdown Menu - URL: https://superails.com/posts/tailwind-on-rails-139-advanced-navbar-dropdown-menu - Published: 2023-08-10 - Duration: 18:15 - Access: free - Playlists: Tailwind on Rails Previously we created a responsive layout with a Sidebar. On a small screen we will hide the sidebar and display a dropdown mobile-only menu instead. To make the user experience seamless, it will follow a few common practices: - close dropdown by clicking Escape - close dropdown if screen size is more than sm (768 px) - hide "main" area and display ONLY dropdown on page - blur "main" - disable scrolling of "body" content I also think that this lesson is a GREAT resource on understanding how StimulusJS works. Episode source code: https://github.com/corsego/138-tailwind-layout/commit/46658fd185b55e14140faa56a9671ba9f02c124a Based on this blogpost: https://blog.corsego.com/tailwindcss-dropdown 0:00 Working demo 1:07 Mobile dropdown menu works on other websites 2:36 Dropdow menu HTML and CSS 7:05 Stimuls Navbar controller to open/close dropdown 10:28 Auto close dropdown menu on big screen 13:00 Blur background when dropdown is open 14:16 Disable background scroll 15:05 Hide main content when dropdown open 15:52 Close menu with keyboard 16:54 Dropdown menu style options 18:02 Steal dropdown style from Tailwind wbsite ## Chapters - 0:00 Working demo - 1:07 Mobile dropdown menu works on other websites - 2:36 Dropdow menu HTML and CSS - 7:05 Stimuls Navbar controller to open/close dropdown - 10:28 Auto close dropdown menu on big screen - 13:00 Blur background when dropdown is open - 14:16 Disable background scroll - 15:05 Hide main content when dropdown open - 15:52 Close menu with keyboard - 16:54 Dropdown menu style options - 18:02 Steal dropdown style from Tailwind wbsite --- # #138 Responsive Layout with Sidebar and Navbar - URL: https://superails.com/posts/tailwind-on-rails-138-responsive-layout-with-sidebar-and-navbar - Published: 2023-08-08 - Duration: 16:57 - Access: free - Playlists: Tailwind on Rails "Tailwind CSS: It looks awful, and it works." - Adam Wathan, TailwindCSS creator As of Rails 7, you can automatically install TailwindCSS when generating a new rails app by running rails new -c=tailwind -d=postgresql. I am super excited that just like me, Adam will be also speaking on Rails World! In this mini-series I will cover the main aspects of using TailwindCSS when building a Rails app. When you create a new Rails app, FIRST OF ALL you want to figure out navigation (navbar, sidebar, footer) and UI responsiveness (make it work on all screen sizes). Let’s build a responsive layout with a sidebar that is replaced by a dropdown on a small screen. Episode source code: https://github.com/corsego/138-tailwind-layout/commit/45240dcc6e819919eb53a1a82350c1b3230dad91 Based on this blogpost: https://blog.corsego.com/tailwindcss-layout 0:00 Intro 1:15 Demo of Responsive Layout 1:50 Create Rails app with TailwindCSS by default 3:40 Build sticky Navbar and Sidebar 11:40 Responsive layout, hide Sidebar on small screen 14:10 Add dropdown icon on small screen 16:00 It works! ## Chapters - 0:00 Intro - 1:15 Demo of Responsive Layout - 1:50 Create Rails app with TailwindCSS by default - 3:40 Build sticky Navbar and Sidebar - 11:40 Responsive layout, hide Sidebar on small screen - 14:10 Add dropdown icon on small screen - 16:00 It works! --- # Friendly Show E4 Jonathan Markwell about his new SaaS, BrightonRuby - URL: https://superails.com/posts/friendly-show-e4-jonathan-markwell-about-his-new-saas-brightonruby - Published: 2023-07-13 - Duration: 51:49 - Access: free - Playlists: Friendly Show Podcast When I was in middle school in 2005, Jon was already🥇pioneering Rails, startups, coworking, conferences, SaaS and other stuff that became 🔥hot over the years. Take your headphones, go for an evening walk and get inspired by Jon‘s story! --- # #137 Use ViewComponent to Abstract reusable HTML. Blocks, Slots, Variables - URL: https://superails.com/posts/rails-7-137-use-viewcomponent-to-abstract-reusable-html-blocks-slots-variables - Published: 2023-07-12 - Duration: 13:38 - Access: free Don't repeat yourself (DRY) applies not only to Ruby code, but HTML and CSS too! In this episode we will identify common HTML/CSS elements across some pages in our app and abstract it into a ViewComponent. A View Component can accept variables and blocks of html to accomodate for the small differences of each of the pages. View Components is an innovative approach for abstracting view logic elements in your Rails 7 frontend. This might not be one of my "greatest" episodes, but I had to make it because it displays a great usecase for the right tool. Episode source code: https://github.com/yshmarov/superails/pull/98/files Based on this blogpost: https://viewcomponent.org/guide/slots.html 0:00 identify duplicate HTML/CSS 1:25 grenerate component and display variables 4:22 move duplicate HTML/CSS to ViewComponent 5:20 display content block in Component 6:20 optionally full width 8:35 reuse the Component in other parts of the app 10:11 ViewComponent Slots 12:57 Summary ## Chapters - 0:00 identify duplicate HTML/CSS - 1:25 grenerate component and display variables - 4:22 move duplicate HTML/CSS to ViewComponent - 5:20 display content block in Component - 6:20 optionally full width - 8:35 reuse the Component in other parts of the app - 10:11 ViewComponent Slots - 12:57 Summary --- # #136 Highlight link to current page - URL: https://superails.com/posts/rails-7-136-highlight-link-to-current-page - Published: 2023-07-10 - Duration: 14:06 - Access: free Highlighting the link to the current page is a very common frontend navigation problem that needs to be solved in all sorts of Rails apps. In this episode we will explore different approaches to highlighting link to current page, and write our own universal method for it. We will also explore debugging and writing tests - crucial skills of a software developer! Finally, feel free to use active_link_to in your app! This can look like a very trivial problem to solve and to record, but in reality it took me more than 8 hours to research all the approaches, write a blogpost, write a script, and record this video. I spend my time on finding perfect solutions so that You do not have to! Episode source code: https://github.com/corsego/136-highlight-link-to-active/commit/da463fe627df9cd460063ba56183dca2d65b2faf Based on this blogpost: https://blog.corsego.com/ruby-on-rails-highlight-linkto-current-page 0:00 Current solutions to highlighting link to current page 1:28 The problem with RailsDevs approach 5:30 active_link_to helper that works with blocks and inline 10:58 Write tests, otherwise I will not approve your Pull Request! 13:45 Final demo ## Chapters - 0:00 Current solutions to highlighting link to current page - 1:28 The problem with RailsDevs approach - 5:30 active_link_to helper that works with blocks and inline - 10:58 Write tests, otherwise I will not approve your Pull Request! - 13:45 Final demo --- # Friendly Show E3 The Ruby On Rails Interview Bible - Author Nezir Zahirović talks about his book - URL: https://superails.com/posts/friendly-show-e3-the-ruby-on-rails-interview-bible-author-nezir-zahirovic-talks-about-his-book - Published: 2023-06-25 - Duration: 33:09 - Access: free - Playlists: Friendly Show Podcast Today we speak with Nezir Zahirović, author of "The Ruby On Rails Interview Bible", about how his first contact with technology and how he got started with development, about the tools he used, and more importantly about a few ideas of what to do to stand out in your future job applications. What is Nezir's favourite gem? - devise Audio podcast available here: https://friendly.show/episodes/the-ruby-on-rails-interview-bible-author-nezir-zahirovic-talks-about-his-book --- # #135 Export videos from ANY Youtube channel using Youtube API - URL: https://superails.com/posts/rails-7-135-export-videos-from-any-youtube-channel-using-youtube-api - Published: 2023-06-12 - Duration: 31:58 - Access: free - Topics: youtube api, api, ActiveJob, Google Cloud Console, YouTube Data API v3 How to Get List of YouTube Channel Videos using YouTube API? I’ve got 150+ videos on @SupeRails Youtube channel, and now I want to list them on my own superails.com website. I could copypaste the information manually, but that would take ages. I could use web scraping, but what's the point, if there is an easy to use Youtube API? In this episode we will: ✅ Learn basics of using Google Cloud Console ☁️ ✅ Enable and use Youtube API ✅ LIST videos from ANY channel ✅ Get detailed info for any particular video ✅ Paginate API results ✅ Store results in our local database 💾 ✅ Use ActiveJob to do it all async Episode source code: https://github.com/corsego/135-youtube-api/commit/8f5d71f1b3b52624c1c4986ab045a1b96ce15347 Based on this blogpost: https://blog.corsego.com/youtube-api 0:00 How to extract videos from Youtube? 1:20 Enable Youtube Data API in Google Cloud 2:03 Create API key 2:20 Install Google API Ruby Client gem for Youtube 5:45 Make API request to Youtube to LIST videos 7:25 Make API request to SHOW one video 8:50 Get video Duration 10:35 Save API data in Rails database 12:30 Create ActiveJob to LIST videos 18:44 Create ActiveJob to get each video and save in the database 28:17 Schedule video creation jobs inside video list job 29:35 Run the import job! 30:56 Final result! Let's look at the imported data ## Chapters - 0:00 How to extract videos from Youtube? - 1:20 Enable Youtube Data API in Google Cloud - 2:03 Create API key - 2:20 Install Google API Ruby Client gem for Youtube - 5:45 Make API request to Youtube to LIST videos - 7:25 Make API request to SHOW one video - 8:50 Get video Duration - 10:35 Save API data in Rails database - 12:30 Create ActiveJob to LIST videos - 18:44 Create ActiveJob to get each video and save in the database - 28:17 Schedule video creation jobs inside video list job - 29:35 Run the import job! - 30:56 Final result! Let's look at the imported data --- # RailsSaaS in Greece. Andy Croll from CoverageBook, OneRubyThing, BrightonRuby, FirstRubyFriend - URL: https://superails.com/posts/railssaas-in-greece-andy-croll-from-coveragebook-onerubything-brightonruby-firstrubyfriend - Published: 2023-06-11 - Duration: 00:56 - Access: free --- # #railssaas - URL: https://superails.com/posts/railssaas-greece-build-apps-10x-faster-adrian-marin-from-avohq-ruby-railssaas - Published: 2023-06-11 - Duration: 00:57 - Access: free --- # Friendly Show E2 Adrian and Yaro visited RailsSaaS Conference in Athens, Greece - URL: https://superails.com/posts/friendly-show-e2-adrian-and-yaro-visited-railssaas-conference-in-athens-greece - Published: 2023-06-06 - Duration: 29:25 - Access: free - Playlists: Friendly Show Podcast Today we talk about our awesome times at the Rails Saas conference in Athens last week. We touched on how well the conference was organized, the great talks, and the fantastic people we both met. We felt like in the presence of start and online friends. We also spoke about how important it is to have activities planned outside the conference too and how the people make all the difference. LISTEN TO AUDIO HERE: https://friendly.show/episodes/adrian-and-yaro-visit-the-railssaas-conference-in-athens-greece Ruby philosophasion: https://twitter.com/adrianthedev/status/1665076373860372483 Emmanuel from Rails Changelog: https://twitter.com/siaw23 Avo Admin: https://avohq.io/ --- # RailsSaaS conference in Athens, Greece. Jane Portman talking about business challenges of SaaS - URL: https://superails.com/posts/railssaas-conference-in-athens-greece-jane-portman-talking-about-business-challenges-of-saas - Published: 2023-06-01 - Duration: 01:00 - Access: free --- # #134 Date Range Picker - URL: https://superails.com/posts/rails-7-134-date-range-picker - Published: 2023-05-29 - Duration: 12:19 - Access: free Daterangepicker is an ultra popular library. Unfortunately, it depends on jQuery. Luckily, there is a re-write that uses vanilla JS: vanilla-datetimerange-picker. Let's integrate DateRange picker into a Rails app and make search work! Episode source code: https://github.com/corsego/134-daterangepicker-rails/commit/b03c27b23f288ddb93bba027d20b7e16c81a3784 Based on this blogpost: https://blog.corsego.com/datarangepicker-js-ransack 0:00 DateRangePicker demo 1:00 import DateRangePicker directly 1:49 store CSS and JS in correct locations 4:15 search form 6:20 search query 9:45 customize the DateRangePicker ## Chapters - 0:00 DateRangePicker demo - 1:00 import DateRangePicker directly - 1:49 store CSS and JS in correct locations - 4:15 search form - 6:20 search query - 9:45 customize the DateRangePicker --- # Friendly Show E1 Pilot 😬. You need a CS degree to be a programmer? How did you start? RailsSaaS conf - URL: https://superails.com/posts/friendly-show-e1-pilot-you-need-a-cs-degree-to-be-a-programmer-how-did-you-start-railssaas-conf - Published: 2023-05-27 - Duration: 24:38 - Access: free - Playlists: Friendly Show Podcast Yaro and Adrian are online and conference buddies that enjoy speaking about development and tech and want to take you in their journey. 🤪 This is our clumsy but spontaneous attempt at podcasting. We had a call on Sunday, scheduled for Thursday, and just went on the call to see how we fare. The rest is history. Friendly Show website: https://friendly.show/ --- # #1 Amanda Perino from Rails Foundation about Rails World - URL: https://superails.com/posts/supechat-1-amanda-perino-from-rails-foundation-about-rails-world - Published: 2023-05-19 - Duration: 19:11 - Access: free I meet Amanda Perino, the CEO of the newly created Rails Foundation. We talk about what the foundation does and about the upcoming Rails World conference in Amsterdam on 5-6 October, 2023. 0:00 Who is Amanda Perino? 0:57 Rails World - what to expect? 2:35 Early bid registration 4:48 Why four Ruby events within 3 weeks? 7:35 Call For Papers 9:56 What does the Rails foundation do? 11:30 Educational pillar 13:11 History of SupeRails 14:21 How to learn Rails from zero? 17:40 Summary ## Chapters - 0:00 Who is Amanda Perino? - 0:57 Rails World - what to expect? - 2:35 Early bid registration - 4:48 Why four Ruby events within 3 weeks? - 7:35 Call For Papers - 9:56 What does the Rails foundation do? - 11:30 Educational pillar - 13:11 History of SupeRails - 14:21 How to learn Rails from zero? - 17:40 Summary --- # #133 API Pagination with Pagy - URL: https://superails.com/posts/rails-7-133-api-pagination-with-pagy - Published: 2023-05-18 - Duration: 12:53 - Access: free - Playlists: Rails REST API If you don’t paginate GET LIST data in your API, most likely you will be giving your API consumers too much data, and adding too much load on your API. Episode source code: https://github.com/corsego/133-api-pagination/commit/217084cea158f151479797fcf54512fcc43d2000 Based on this blogpost: https://blog.corsego.com/api-pagination-pagy --- # #132 API Analytics and Rate Limiting - URL: https://superails.com/posts/rails-7-132-api-analytics-and-rate-limiting - Published: 2023-05-15 - Duration: 17:45 - Access: free - Topics: rails api, api development, rate limiting, api usage tracking - Playlists: Rails REST API If you don’t limit your API usage, bad users can DDOS your server. If you don’t track API usage, how do you know if it is even used? Apps like Twitter and Shopify have daily or monthly (30-day) API usage limits In this tutorial we will track API usage per user and add a 30-day max limit of API requests per user. Learn the best practices from Shopify API and Twitter API! Episode source code: https://github.com/corsego/132-api-request-tracking-limiting/commit/92812d49c4ff7666850a1b0bf7821143f811f614 Based on this blogpost: https://blog.corsego.com/api-usage-tracking-rest-rate-limiting 0:00 Overview of current API app 2:35 Why Track API usage 3:50 ApiRequest model 4:52 Log ApiRequests 7:10 Limit API requests 11:25 Display API call limit in header 15:27 Track API key usage ## Chapters - 0:00 Overview of current API app - 2:35 Why Track API usage - 3:50 ApiRequest model - 4:52 Log ApiRequests - 7:10 Limit API requests - 11:25 Display API call limit in header - 15:27 Track API key usage --- # Rails SaaS Boilerplates and Starterkits. Trying out Bullettrain for the first time - URL: https://superails.com/posts/rails-saas-boilerplates-and-starterkits-trying-out-bullettrain-for-the-first-time - Published: 2023-05-15 - Duration: 01:04:13 - Access: free - Topics: saas, rails starter kit, rails boilerplate, saas boilerplate Sometimes I have a "new genious app" idea, but when I start coding it I get demotivated with re-creating all the same functionality for the 100th time: - Authentication, SSO - Teams and Multitenancy - Roles (Authorization) - Payments & Subscriptions - Responsive layout - UI elements - Internationalization - CI setup - Tests for all the basic functionality These days there are many starter kits. I will try to make a general overveiw and comparison. I will also try using and understanding the recently open sourced Bullettrain SaaS template, that I have high hopes for! --- # #131 API Documentation with OpenAPI, Swagger, RSWAG. Generate manifest with ChatGPT?! - URL: https://superails.com/posts/rails-7-131-api-documentation-with-openapi-swagger-rswag-generate-manifest-with-chatgpt - Published: 2023-05-11 - Duration: 16:37 - Access: free - Topics: swagger, openapi, api development, rswag, AI - Playlists: Rails REST API Public API without GOOD Documentation is useless! Good API documentation is crucial for helping others adopt and use your public API. OpenAPI (previously Swagger) offers a standard for API documentation. OpenAPI documenation is basically a structured .yml manifest file. Swagger UI is a tool that reads your .yml file and displays it in a fancy UI. Episode source code: https://github.com/corsego/131-api-documenation/commit/29d7c64b926d66acc08681ed0b83b354659fc7f2 Based on this blogpost: https://blog.corsego.com/learn-openapi-swagger-rswag 0:00 Overview of what we did before 0:00 Why write API documentation? 1:26 What is OpenAPI 3:50 gem RSWAG to display Swagger UI 7:20 customize Swagger branding 9:33 Generate OpenAPI manifest with ChatGPT 11:01 How to use Swagger UI to test API endpoints 16:03 Summary ## Chapters - 0:00 Overview of what we did before - 0:00 Why write API documentation? - 1:26 What is OpenAPI - 3:50 gem RSWAG to display Swagger UI - 7:20 customize Swagger branding - 9:33 Generate OpenAPI manifest with ChatGPT - 11:01 How to use Swagger UI to test API endpoints - 16:03 Summary --- # #130 API CRUD endpoints - URL: https://superails.com/posts/rails-7-130-api-crud-endpoints - Published: 2023-05-08 - Duration: 20:10 - Access: free - Topics: rest api, api development, rails api - Playlists: Rails REST API Create an API Controller where you can LIST, SHOW, CREATE, UPDATE, DESTROY records. Episode source code: https://github.com/corsego/130-api-crud-endpoints/commit/de101248398714ec367cf3c4dfad7e95c8373d4f Based on this blogpost: https://blog.corsego.com/rails-api-bearer-authentication 0:00 Recap of what we already have 2:00 scaffold Posts, belong to user 3:45 WTF Format JSON 5:05 WTF JBUILDER 6:08 cURL request to access posts 7:07 Access Posts controller within our API 11:42 API controller authorization - user can access only his own posts 13:30 fix ActiveRecord::RecordNotFound error 14:59 CREATE via API, fix InvalidAuthenticityToken 16:40 UPDATE via API. Importance of strong params 18:17 DESTROY via API 19:20 cleanup API controller 19:36 summary ## Chapters - 0:00 Recap of what we already have - 2:00 scaffold Posts, belong to user - 3:45 WTF Format JSON - 5:05 WTF JBUILDER - 6:08 cURL request to access posts - 7:07 Access Posts controller within our API - 11:42 API controller authorization - user can access only his own posts - 13:30 fix ActiveRecord::RecordNotFound error - 14:59 CREATE via API, fix InvalidAuthenticityToken - 16:40 UPDATE via API. Importance of strong params - 18:17 DESTROY via API - 19:20 cleanup API controller - 19:36 summary --- # #129 API Token generation UI - URL: https://superails.com/posts/rails-7-129-api-token-generation-ui - Published: 2023-05-03 - Duration: 16:21 - Access: free - Topics: api development, rails api, create api key - Playlists: Rails REST API Allow users to generate and disable API tokens. Users can view the secret token only once, when it is generated using Turbo Streams API tokens are used for Bearer Authentication to the public API that we are building in this app. Episode source code: https://github.com/corsego/129-api-tokens-management-ui/commit/c846e088f74f0fbd9fbb4239fe8d292a7b357b51 0:00 Summary of what we built before 1:22 API token generation on Github 2:45 Display API tokens 5:18 Create API token 9:11 Deactivate API token ## Chapters - 0:00 Summary of what we built before - 1:22 API token generation on Github - 2:45 Display API tokens - 5:18 Create API token - 9:11 Deactivate API token --- # #128 Faraday CRUD API requests. Communicate between two Rails apps! - URL: https://superails.com/posts/rails-7-128-faraday-crud-api-requests-communicate-between-two-rails-apps - Published: 2023-05-01 - Duration: 12:59 - Access: free - Playlists: Rails REST API In this episode we will send API requests from one Rails app to another! While you can use CURL to make HTTP requests from your console, you would want to use Faraday gem to make HTTP requests from your Ruby app. Here's how you can make GET, CREATE, UPDATE, DESTROY API requets with Faraday. Episode source code: https://github.com/corsego/128-faraday-api-crud/commit/37148c869314b20ea99b08c3e93b091ce52450f5 Based on this blogpost: https://blog.corsego.com/api-requests-with-faraday --- # #127 cURL for CRUD API requests - URL: https://superails.com/posts/rails-7-127-curl-for-crud-api-requests - Published: 2023-04-28 - Duration: 12:12 - Access: free - Playlists: Rails REST API 7 years ago I was googling "rails call controller action from console" 😂. Well, THIS is the way! API let's apps communicate with each other. CURL let's you make http requests to any URL (or localhost) from your command line. You can test your API endpoints with CURL. Also, if you know how to use CURL, you don't need external services like Postman to play with your own API. Episode source code: https://github.com/corsego/127-curl-with-rails/commit/e99d3ef4b1525a5d61c19eb320d9778ca71168c9 Based on this blogpost: https://blog.corsego.com/api-requests-with-curl --- # #126 API from Zero. Bearer authentication - URL: https://superails.com/posts/rails-7-126-api-from-zero-bearer-authentication - Published: 2023-04-26 - Duration: 21:55 - Access: free - Topics: rails api, bearer authentication, api testing - Playlists: Rails REST API Let's build a public REST API with Rails! Have you ever obtained API keys to a service, using which you could read and write data? For example, Twitter, Slack or Github API? In this mini series we will build an API where users can authenticate via Bearer tokens and perform CRUD actions. We will make API requests via cURL and Faraday, generate API documentation with OpenAPI/Swagger, and write tests for our API. In this first episode we will build the backend functionality for users to be able to create API keys and use them to make authenticated requests to your applications public API. Episode source code: https://github.com/corsego/126-api-bearer-authentication/commit/24bdf07d38d84528ae551b1e6d065fe3343716b1 Based on this blogpost: https://blog.corsego.com/rails-api-bearer-authentication 0:00 create an api and make a cURL request 2:39 ApiToken model. generate tokens for users 7:55 Bearer authentication via ApiToken 16:15 render current_api_token and current_user in json response 17:16 writing tests for API authentication 21:40 summary ## Chapters - 0:00 create an api and make a cURL request - 2:39 ApiToken model. generate tokens for users - 7:55 Bearer authentication via ApiToken - 16:15 render current_api_token and current_user in json response - 17:16 writing tests for API authentication - 21:40 summary --- # #125 Test Devise authentication with Controller and System tests - URL: https://superails.com/posts/rails-7-125-test-devise-authentication-with-controller-and-system-tests - Published: 2023-04-17 - Duration: 20:53 - Access: free - Topics: devise, rails testing, tdd When setting up a new Rails app with Devise, you should set up your test suite to work with authentication and write controller (integration) and system tests for the authentication flow. Here’s a reusable approach to setting up your test suite for the long run! Episode source code: https://github.com/corsego/125-test-devise-authentication/commits/main Based on this blogpost: https://blog.corsego.com/minitest-faker-factory-bot-devise 0:00 tests in generated controllers 1:34 update routes and make tests work 3:49 install devise 6:15 fix duplicate index users on email in fixtures 7:03 test unauthenticated access 9:15 devise login in tests 10:48 system (browser) tests 19:43 run system tests without browser popup 20:20 test, test:system, test:all ## Chapters - 0:00 tests in generated controllers - 1:34 update routes and make tests work - 3:49 install devise - 6:15 fix duplicate index users on email in fixtures - 7:03 test unauthenticated access - 9:15 devise login in tests - 10:48 system (browser) tests - 19:43 run system tests without browser popup - 20:20 test, test:system, test:all --- # #124 Test omniauth login with controller (integration) tests - URL: https://superails.com/posts/rails-7-124-test-omniauth-login-with-controller-integration-tests - Published: 2023-04-13 - Duration: 09:38 - Access: free - Topics: omniauth, oauth2, rails testing, oauth testing Testing is a very important part of app development that I haven't covered a lot before. Previously we've added oAuth login for Microsoft & Github. Now, let's mock omniauth authentication with both oAuth providers in our tests. Episode source code: https://github.com/corsego/124-test-omniauth/commit/66d848b5405457704e8411865b3f7812b8d43262 Based on this blogpost: https://blog.corsego.com/testing-omniauth 0:00 walk through initial app setup 1:30 run test suite, fix errors 3:40 test Github omniauth authentication 6:04 test Microsoft Azure omniauth authentication 9:17 final test run ## Chapters - 0:00 walk through initial app setup - 1:30 run test suite, fix errors - 3:40 test Github omniauth authentication - 6:04 test Microsoft Azure omniauth authentication - 9:17 final test run --- # #123 omniauth with multiple providers - URL: https://superails.com/posts/rails-7-123-omniauth-with-multiple-providers - Published: 2023-04-11 - Duration: 05:36 - Access: free - Topics: oauth2, omniauth Most often you would like to enable more than one oAuth provider for your app. In this episode I will show a resuable approach to enabling multiple oAuth providers. In this episode we will add a second oAuth provider to an existing Rails app, and allow users to authenticate via both omniauth strategies. Episode source code: https://github.com/corsego/123-multiple-oauth-providers/commit/253f5e6a0e69f84a528ef0d0fb78eebf83b6884d 0:00 first oAuth provider - microsoft azure 0:46 install second oAuth provider - github 3:16 authenticate method in omniauth callbacks controller 3:55 Dynamic oauth provider authentication handling ## Chapters - 0:00 first oAuth provider - microsoft azure - 0:46 install second oAuth provider - github - 3:16 authenticate method in omniauth callbacks controller - 3:55 Dynamic oauth provider authentication handling --- # #122 sign in with Microsoft Azure OAuth and Single Sign-On - URL: https://superails.com/posts/rails-7-122-sign-in-with-microsoft-azure-oauth-and-single-sign-on - Published: 2023-04-07 - Duration: 14:08 - Access: free - Topics: omniauth, oauth2 Let’s add a “Sign in with Microsoft” button! Most big corporations use Microsoft to manage their employees email addresses. If you are building a B2B app, you will definitely need to add Microsoft single-sign-on. Microsoft Azure has a few oAuth authentication services, and it can be tricky finding the right API service and the right ruby gem to use. Good news: I've done the research, so that you don't need to! Based on this blogpost: https://blog.corsego.com/microsoft-azure-omniauth Episode source code: https://github.com/corsego/122-omniauth-microsoft/commit/748452eea6b01f0cb48321e39477d88ab4571381 0:00 different microsoft omniauth gems 0:46 install azure activedirectory gem 1:52 Register app on Azure, get Microsoft API client ID 04:48 generate Microsoft API Secret Key 5:30 try to authenticate with a Microsoft account 6:50 make omniauth work with our Rails app 10:27 Demo: sign in with Microsoft oAuth 11:11 Track provider and UID 13:20 Launching in Production ## Chapters - 0:00 different microsoft omniauth gems - 0:46 install azure activedirectory gem - 1:52 Register app on Azure, get Microsoft API client ID - 04:48 generate Microsoft API Secret Key - 5:30 try to authenticate with a Microsoft account - 6:50 make omniauth work with our Rails app - 10:27 Demo: sign in with Microsoft oAuth - 11:11 Track provider and UID - 13:20 Launching in Production --- # #121 Turbo Broadcasts CRUD Demystified - URL: https://superails.com/posts/rails-7-121-turbo-broadcasts-crud-demystified - Published: 2023-03-30 - Duration: 24:20 - Access: free - Topics: hotwire, hotwire turbo, turbo broadcasts, hotwire turbo streams - Playlists: Just enough Hotwire for Rails developers There are two different ways of serving Turbo Streams "via HTTP and via Websockets". The regular HTTP streams send updates ONLY to the user that performed an action, whereas Websocket streams (aka Broadcasts) send updates TO ALL users that are "subscribed" to a channel. in Episode 59 we did an in-deep dive into HTTP Turbo Streams. Now we will dive into the magic beneath Turbo Stream Broadcasts and understand the best practices while working with them. Episode source code: https://github.com/corsego/121-turbo-hotwire-broadcasts-crud/commit/609c00d0a286ac284dffcc4b5865851294c6cbe4 Broadcaster pattern for Turbo Streams Broadcasts: https://blog.corsego.com/turbo-broadcast-renderer-pattern Based on this blogpost: https://blog.corsego.com/turbo-hotwire-broadcasts 0:00 Introduction 2:22 Basic Turbo Broadcast from model 5:38 Demistify broadcast_to 7:39 Demistify broadcast_action_to 10:15 Broadcast posts count on create and destroy as HTML 13:31 Broadcast posts count as a partial 14:20 Invoke Broadcasts from Controller, not Model 18:32 Broadcaster pattern. Invoke Broadcasts from Console ## Chapters - 0:00 Introduction - 2:22 Basic Turbo Broadcast from model - 5:38 Demistify broadcast_to - 7:39 Demistify broadcast_action_to - 10:15 Broadcast posts count on create and destroy as HTML - 13:31 Broadcast posts count as a partial - 14:20 Invoke Broadcasts from Controller, not Model - 18:32 Broadcaster pattern. Invoke Broadcasts from Console --- # #120 Active Record Encryption usage and troubleshooting - URL: https://superails.com/posts/rails-7-120-active-record-encryption-usage-and-troubleshooting - Published: 2023-03-23 - Duration: 13:34 - Access: free - Topics: encryption, ruby on rails encrypted credentials, rails encryption, security Active Record Encryption let's you encrypt data that is stored in the database (it's a very useful layer of security in case your database gets exposed). In this episode we will add encryption to a few PII and PHI fields. We will encrypt data that is already present in the database, and ensure all new data is safely encrypted. Episode source code: https://github.com/corsego/120-active-record-encryption/commit/75ece9c396baee3983358cee68733e3b26420d94 Based on this blogpost: https://blog.corsego.com/encrypted-credentials 0:00 ChatGPT is confidently wrong 1:12 What is Active Record Encryption 2:10 Add encryption to a Rails app 5:10 Non-deterministic encryption 5:56 Deterministic encrypyion can be queried 7:55 Encrypt all existing records 8:56 Encrypt an integer? 12:03 Display encrypted values ## Chapters - 0:00 ChatGPT is confidently wrong - 1:12 What is Active Record Encryption - 2:10 Add encryption to a Rails app - 5:10 Non-deterministic encryption - 5:56 Deterministic encrypyion can be queried - 7:55 Encrypt all existing records - 8:56 Encrypt an integer? - 12:03 Display encrypted values --- # #119 Trello Clone. Advanced Kanban Drag and Drop Sortable Lists with Hotwire - URL: https://superails.com/posts/rails-7-119-trello-clone-advanced-drag-and-drop-sortable-lists-with-hotwire - Published: 2023-03-20 - Duration: 42:19 - Access: free - Topics: Trello Clone, drag and drop, drag and drop sortable lists, javascript, hotwire, learn ruby on rails, Sortable.js in Rails, sortable.js with hotwire, rails hotwire, hotwire turbo streams, drag and drop functionality, drag and drop with hotwire, sortable hotwire - Playlists: Just enough Hotwire for Rails developers I'm so proud of this one, it should be premium content! I've watched all the other youtube videos on this topic and I'm sure this approach is better and more versatile. Also this video gives great insight into the thought process and debugging required to complete the feature. Code deliverables: 1. Sort Lists 2. Sort tasks within List 3. Move tasks between Lists 4. Persist changes This functinality was achived thanks to: - gem RankedModel that stores the order of items in a list - RequestJS that sends http requests from your javascript to your Rails controller - SortableJS that provides the seamless sorting functionality Episode source code: https://github.com/corsego/119-trello-drag-drop-save/commits/main Based on this blogpost: https://blog.corsego.com/drag-and-drop-stimulus-request-js 0:00 Preview of what we will build, other solutions 1:15 Build the boilerplate app. List has many tasks. 7:25 Install SortableJS to sort List elements in the frontend 11:20 Install gem Ranked Model to track sort order of elements 20:00 Install RequestJS to make requests to Rails controller from StimulusJS 25:45 Updating Sort order works! 26:53 Sort Tasks within a List 32:50 Sort Tasks between Lists 40:05 CSS to stack lists horizontally like Trello 41:40 Final demo. Advanced sorting works! ## Chapters - 0:00 Preview of what we will build, other solutions - 1:15 Build the boilerplate app. List has many tasks. - 7:25 Install SortableJS to sort List elements in the frontend - 11:20 Install gem Ranked Model to track sort order of elements - 20:00 Install RequestJS to make requests to Rails controller from StimulusJS - 25:45 Updating Sort order works! - 26:53 Sort Tasks within a List - 32:50 Sort Tasks between Lists - 40:05 CSS to stack lists horizontally like Trello - 41:40 Final demo. Advanced sorting works! --- # #118 Dark mode. Toggle inside your app or use OS default preferred color scheme - URL: https://superails.com/posts/rails-7-118-dark-mode-toggle-inside-your-app-or-use-os-default-preferred-color-scheme - Published: 2023-03-16 - Duration: 12:33 - Access: free - Topics: css, Color Scheme Some say that Dark Mode should be an MVP feature. In this episode, we will allow users to toggle light and dark modes inside our app based on system preferences, or manually by clicking a button in our application. For this example, we will not use any CSS framework to achieve this, just raw CSS and Rails cookie storage! Episode source code: https://github.com/corsego/118-dark-mode/commit/917ad96b76da599799ee783529b9abc3ee69ba31 Based on this blog post: https://blog.corsego.com/ruby-on-rails-dark-mode 0:00 Dark Mode in other applications 1:44 Demo of the final application 2:45 use OS default light or dark mode 3:44 toggle light and dark mode within Rails app 8:07 CSS to conditionally toggle the color scheme 12:02 Works! Final Demo ## Chapters - 0:00 Dark Mode in other applications - 1:44 Demo of the final application - 2:45 use OS default light or dark mode - 3:44 toggle light and dark mode within Rails app - 8:07 CSS to conditionally toggle the color scheme - 12:02 Works! Final Demo --- # #117 Find code security vulnerabilities with Bearer static code analysis tool - URL: https://superails.com/posts/rails-7-117-find-code-security-vulnerabilities-with-bearer-static-code-analysis-tool - Published: 2023-03-14 - Duration: 35:38 - Access: free I used to work at Bearer together with Guillaume for a year (he's my EX-boss). Now they finally made the tool they've been working on public! It's a static code analysist tool (like Rubocop or Brakeman) to find data and security vulnerabilities. In this episode I will install the bearer tool and run it on a few repositories, while Guillaume explains the value of using this scanner. Today Bearer works for Javascript and Ruby, but there are plans to roll it out for other languages. I personally think that having an extra layer of CI checks wouldn't hurt any app, but it becomes more and more important the bigger your app is. The sooner you start solving "data security" issues in your app, the better. Bearer source code: https://github.com/Bearer/bearer Possible Chapters (not timestamps yet): 0:00 Met my ex after 6 months apart! 0:30 Bearer was just made open source! 1:19 What is Bearer? Who needs it? 4:18 Does Bearer connect to the database, or does in analyze only the codebase? 6:13 Install and run Bearer scanner 8:10 Analyze Bearer scan results 10:06 Skip a rule while doing a bearer scan 10:55 How to resolve bearer scan warnings 11:48 Can we Autocorrect with bearer? 13:50 Fix a bearer scan warning 14:36 Can I build bearer into my CI? 16:50 Generate privacy report CSV 20:05 Bearer vs Brakeman 21:10 Fork bearer and customize rules or add your own rules 24:01 Run Bearer on Corsego repo 26:04 Run Bearer on Insta2Blog repo 29:49 When should I use bearer? 32:48 Can bearer scan a Hanami or Sinatra app? 33:31 Run bearer on my Jekyll blog 34:04 Wrapping up ## Chapters - 0:00 Met my ex after 6 months apart! - 0:30 Bearer was just made open source! - 1:19 What is Bearer? Who needs it? - 4:18 Does Bearer connect to the database, or does in analyze only the codebase? - 6:13 Install and run Bearer scanner - 8:10 Analyze Bearer scan results - 10:06 Skip a rule while doing a bearer scan - 10:55 How to resolve bearer scan warnings - 11:48 Can we Autocorrect with bearer? - 13:50 Fix a bearer scan warning - 14:36 Can I build bearer into my CI? - 16:50 Generate privacy report CSV - 20:05 Bearer vs Brakeman - 21:10 Fork bearer and customize rules or add your own rules - 24:01 Run Bearer on Corsego repo - 26:04 Run Bearer on Insta2Blog repo - 29:49 When should I use bearer? - 32:48 Can bearer scan a Hanami or Sinatra app? - 33:31 Run bearer on my Jekyll blog - 34:04 Wrapping up --- # CertificateOwl day2 - building a proof-of-concept (POC) app with Ruby on Rails - URL: https://superails.com/posts/certificateowl-day2-building-a-proof-of-concept-poc-app-with-ruby-on-rails - Published: 2023-03-12 - Duration: 02:29:19 - Access: free - Playlists: Build a PDF generator (CertificateOwl) In this series I will try to build a SaaS that will let organizations (universities, schools, test centers, event organizers) easily bulk-generate PDF certificates and email them to recepients. Deliverables in this session: 1. Select certificate template 2. Upload recepients list CSV 3. Generate PDF & email certificates with QR Episode source code: https://github.com/yshmarov/certificateowl/pull/1 --- # #116 Custom error pages (404 Not found and 500 Internal server error) - URL: https://superails.com/posts/rails-7-116-custom-error-pages-404-not-found-and-500-internal-server-error - Published: 2023-03-09 - Duration: 09:05 - Access: free - Topics: error pages Ruby on Rails #116 Custom error pages (404 Not found and 500 Internal server error) When you create a new rails app, error pages like 404 and 500 are automatically generated in the public folder. To make your app look more professional, you might want to style these pages using the same CSS framework as all other parts of your app. Episode source code: https://github.com/corsego/116-custom-error-pages/commit/b6bebd791bd3d84e6a7a6d04769d96335557ba79 Based on this blogpost: https://blog.corsego.com/custom-error-pages 0:00 You need to update the error pages for your app 0:50 Add custom error pages 5:42 Style your error pages with Tailwind 8:43 Demo of the styled sexy error pages ## Chapters - 0:00 You need to update the error pages for your app - 0:50 Add custom error pages - 5:42 Style your error pages with Tailwind - 8:43 Demo of the styled sexy error pages --- # CertificateOwl day1 - product and market ressearch - URL: https://superails.com/posts/certificateowl-day1-product-and-market-ressearch - Published: 2023-03-06 - Duration: 50:43 - Access: free - Playlists: Build a PDF generator (CertificateOwl) In this series I will try to build a SaaS that will let organizations (universities, schools, test centers, event organizers) easily bulk-generate PDF certificates and email them to recepients. Before writing any code, one must first do some reasearch. --- # #115 Multitenancy, Teams and Roles without a gem - URL: https://superails.com/posts/rails-7-115-multitenancy-teams-and-roles-without-a-gem - Published: 2023-03-03 - Duration: 32:09 - Access: free - Topics: moneygun, Mulitenancy, saas, software architecture Ruby on Rails #115 Multitenancy, Teams and Roles without a gem Want to learn more on the topic? Here's a DISCOUNT for my complete course "Ruby on Rails: Learn to Build a Multitenancy SaaS app": https://yshmarov.gumroad.com/l/ror6saas/presale33 In this episode we will: - let users register - let users create tenants/accounts/organizations/workspaces - user who created tenant automatically becomes admin - invite users to become members of the app - invited users are not admins - allow only members to access parts of the app scoped to the tenant Setting tenant based on routes is the best, because you can have multiple tenants open in different tabs (unlike adding current_tenant_id current_user). Second best option is subdomain multitenancy, but that is harder to pull off and maintain. Row-based multitenancy is the most popular choice, and it is used in Rails SaaS starterkits like Bullettrain and Jumpstartpro. Episode source code: https://github.com/corsego/116-multitenancy-routes-rows/commits/main Database architecture: https://dbdiagram.io/d/63fbe3ed296d97641d83db94 0:00 Multitenancy examples 4:35 static pages controller 6:28 install devise 8:48 scaffold tenants 10:31 generate members resource 12:34 tenant creator becomes admin member, member authorization 16:12 install devise invitable 17:31 diplay tenant members 20:20 invite users to be members of tenant 26:42 member authorization 28:50 Final demo and thoughts ## Chapters - 0:00 Multitenancy examples - 4:35 static pages controller - 6:28 install devise - 8:48 scaffold tenants - 10:31 generate members resource - 12:34 tenant creator becomes admin member, member authorization - 16:12 install devise invitable - 17:31 diplay tenant members - 20:20 invite users to be members of tenant - 26:42 member authorization - 28:50 Final demo and thoughts ## Transcript What is multi-tenancy? Wikipedia says that it is a group of users who has common access to an instance of an application. Now let's have a look at a few examples. For example, we've got Jello. I have created my user account, and inside my user account I have created a workspace. And inside this workspace, I've got different information that is scoped to this specific workspace that isn't present in any other of my multiple workspaces. For example, I've got these columns and cards inside these columns that are not shared with any other workspace. So everything inside this workspace is inside this kind of tenant. And I have, uh, my membership in this tenant. And you see, I'm the admin of this board. So you see this member is an admin of the board. So I'm a member in this talent, and I can invite other users to become members of this talent. So I would need to, to add somebody's email address, add, uh, that role that I would want to give this user. And I would send a user invitation to, uh, a person to join this, uh, workspace. And again, I can have, uh, different access rights in different workspaces. Another good example would be slack. Here are multiple workspaces, and there are multiple members in each of these, uh, workspaces. And uh, you can be either an admin, you can be just a user inside one of these workspaces, but like all the channels, all the messages and so on, everything is scoped and contain inside this workspace. So no information has been leaked from one workspace to another. And, uh, there is, for example, Basecamp that, uh, also has, uh, different organizations. You can create, uh, one or multiple, uh, teams and invite members to the teams and everything inside all projects, everything is scoped to a team. And if you have a, a look at the UL inside Basecamp, you see the idea of the talent. So this is like your organization ID that you're looking at at the moment. And everything else is scoped under this idea of the organization that you are looking at. Um, similar with the cello here is I guess the idea of the organization and, uh, yeah, I guess the name of the organization. So if I would go to another tenant, then uh, the idea here would change. Now how do we build this kind of multitenancy in bound rails? So here's the basic, uh, kind of database architecture where we have a user here, again, in Trello or in Slack, I have the same user instance, the same user with one email, and there are multiple workspaces or tenants. And I can be a member of one or many, uh, workspaces or tenants. And, uh, member belongs to the user, a tenant and has tr So you see, I can have different trolls in different organizations. And then there is other types of data that are scoped under the tenant. So here for example, we have, uh, columns and cards and possibly comments. All of them have, uh, tenant id. And that way we can see all the tasks or projects or whatever that belong to the specific tenant. So everything that should be scoped to a tenant should have the tenant id. And this is called row based multitenancy. Other types are database multitenancy or schema based multitenancy. That gym apartment, uh, as far as I remember it, uh, offers a schema or database, uh, multitenancy, but it is, uh, not, uh, a more sustainable approach. And there are a lot of gems that, uh, offer you, uh, road based multitenancy, like exist tenant and active multitenant. Now, uh, bullet train, uh, this, uh, uh, software as a service, uh, boilerplate for ribbon rails offers teams and invitations as one of their core features. And they use a similar type of multi-tenancy as in Basecamp. So again, everything has a tenant id and they use, uh, route to set the current talent. And there is Jumpstart Pro and other stars, uh, multitenancy boiler plate for Ruben Trails that has multitenancy and accounts as one of their core features that they pay. Uh, they, you expect you to pay a premium price, uh, for, so yeah, let's try to build this kind of functionality. Now, here we are not going to use any gems, so we're not going to use any of these gems by the, we're talking about the gems. So apartment, I don't like it. Existent is very good. Active record, multi 10 is very good. Mil used the good five years ago, but now it's outdated. And this games gems. I haven't tried and Jumpstart Pro actually use this X as talent. Okay? So let's try to build Hamilton architecture where we have, uh, uh, different tenants or workspaces. User creates a workspace, he becomes admin by default, and user can invite others to become a member of this workspace. And a user who isn't part of this workspace can't see anything inside this workspace. So let's start by a new Ribbon rails application. I will, um, yeah, I have actually just, uh, created a new Rails app and, uh, let's, uh, start the rail server and have, uh, a look. Okay? So yeah, there is nothing. Let me create a few pages. So let's create a landing page that will be accessible by not logged in users and dashboard that can be accessed by logged in users. So let's say rails generate controller, uh, static pages, landing page and dashboard. Okay, let's open our code and, uh, let's go to our roots and say, uh, route will be to static pages landing page, and we will get the dashboard to static pages. Dashboard. Let's, uh, see if it works. Rail server, Uh, missing controller key. Okay, maybe I missed something here. Got, uh, okay, yeah, controller, key rail server. And here we are on the landing page. We can go to slash dashboard and we can see it. Okay, let's, uh, add the, the links. So application dot html, er BI will add the, uh, link to, uh, home, and it'll be a root path. Oh my god, too much help from the IDE and link to dashboard. And it'll be dashboard path. And let's see, we have two links. Okay, uh, let's save our changes. So I will say get status gi, add all GI Comet, uh, a static pages controller. Okay, now let's add device. So let's say rails. Uh, no actually bundle add device, uh, rails generate device install. We'll need to add these lines to our application, our B four flash messages, and we will need to add, uh, this to be able to send emails in our development environment. And, uh, yeah, let's now, uh, say rails generate device user to create our user model and migrations rails DB migrate. And let's require a user to be able to, to, to, let's require user beside in to be able to use our app. So before action authenticate user. And we are going to skip this before action for our landing page. So I will say skip before action authenticate user only Landing page. Let's see if it works. I will start the server. And let's see. So to access the dashboard, I need to be logged in, but for homepage, I don't need to be logged in. Let's, uh, try creating an account. Okay, let's now add the links to like send Inside Out. Now, I'm not going to write them down manually, I'll just copy them from somewhere. I, uh, remember. So going back to application HTML. And in our navigation part, I will, uh, add, so if the user send in, we can see his email and link to log out and link to the dashboard. And if the user is not sent in, uh, yeah, in all cases we'll see the link to the homepage, but we'll see also login and register. Let's go back and refresh. Okay? Home dashboard, my email and log out. And if I'm not logged in home, log in and register. Okay, looks good. Let's save our changes, get status, get add the all gi com main, uh, installed device. Okay, looks good. Now, let's go back. And here we have, so we've got our users that were provided by device, and let's create our tenants. So let's say rails generate scaffold, tenant, and then we'll have a name. Okay, let's, uh, start the server and have a look at our tenants. Yeah, we should actually run the migration rails to be migrate. And let's go to tenant RB and say it validates name presence, true. And let's add the link to tenants inside our application, followed in user. So let's make it tenants both. Uh, surely you can name it no tenants, but like workspaces or something like that in your application. So let's see. I will, uh, log in and have the tenants path. Okay, uh, Maybe I made a typo, uh, validate. Yeah, I've got s okay, let's create a talent. And I have created a talent. Looks good, but at the moment, the user and talent are not related at all. So we need to, uh, know that a user is a member of the talent. And for this, we're going to add this member association. So this is like the team access. Uh, a user will have many tenants through member and tenant will have users through member. So it'll save our changes and continue get status. Here we have just added the scaffold of tenants. Get add all, get commit, main scuff tenants, and let's add our, uh, member model. So let's say rail generate, uh, resource. And we will have member, we will have, uh, user references and tenant references. And importantly, they'll, they'll also have a roles table. So, uh, they're going to know that this user is an admin in this organization, but he's not an admin in the other organization. So we will say roles, JSOB. Now let's go to our migration and have a look at it, the B migrate here, and we'll say no, false and default will be an empty hush. Okay? Uh, rails, db, migrate, and let's add the business associations now. So going to our model VC member, member belongs to user internet and user will, uh, let's say has many members and has many tenants, uh, through members. And we'll do something very similar for our tenants. Tenant, uh, has many members and has many users through members. Okay? So, uh, yeah, let's save our changes, get status and have created the members controller. Uh, they've added roots, but uh, no views for the members for the moment. So I've generated no scaffold, but, uh, uh, resource. Let's have a look at the members controller. Yeah, it is just an empty blend controller. And in the roots we have just resources members, okay? So let's say Git add all gi, commit, message, uh, generate, uh, members resource. And now we need to make it so that when a user creates a tenant, he will become a member of the tenant and the user should be able to see only tenants that he's a member of. So let's go to our tenants controller, and here we will say, we will show not all the tenants, but the current user tenants, okay? And you see, I'm not a member of any tenant. And to make the current user member of the tenant that he has just created, we'll go here. So if the tenant is saved, we will create member at talent members, great. And we will say, uh, uh, user will be current user, and we can also assign a role like roles admin. True. Okay, let's see if it works. I will create, uh, at talent, uh, with member, and we have created a talent. And going back to tenants, you see, I can see this talent. So I'm most likely a member of this talent. Let's go to the console and check member dot account member dot first. And you see we have added this, uh, membership. So now I am the, a member of this tenant. And let's see, can I access another tenant? Let's go to tenant slash one. You see, I can still access this talent, uh, show page, but I don't see it in the index page. So, so I shouldn't have access to this, uh, tenant at all because I'm not a member of the tenant. How can they do it? Let's, uh, go, um, to tenant controller and let's say, uh, return redirect to, uh, route Path alert. You are not a member. Uh, and we'll do it, uh, if, uh, the talent. Uh, so unless, uh, talent dot, uh, users dot include current user. Let's see if it works. So yeah, you see, I'm not a member. I was directed to the main page. I'll go to the time that I'm a member of and I can see it. And we will add this, uh, as a before action to all the places where we need a current tenant. So I will say, uh, before action, uh, authorize member And I will apply it to the same, uh, actions and let's, uh, add this private action, private method and move it there. So authorize member, and let's see, can I go to tenants one slash edit? No, I cannot, but I can go to tenants two slash edit because I'm a member of this tenant. Okay, looks good. Let's, uh, save our changes. So, uh, yeah, we've done only changes in the tenants controller. We made it so that when we create a tenant, the create becomes an admin of this tenant. And we've also made it so that only, uh, tenant members can view tenant pages. So get, add all, get permit, um, tenant, uh, creator becomes, uh, admin member and authorization member of the authorization. Okay? Uh, what is next? Now, we want to be able to invite other users to become members of our application. And for this, we're going to use device inviable because if a user, if we, let's say have this kind of form where we inputter user's email, which to be able to actually create a user in our application and send him an invitation link to become a member of our, uh, application and also make him a member of this tenant. So we are going to install the GM device invite, And let's see how it is done. So I will say bundle add device inviable install, add it to the user model, run the migrations, and that's about it. Let's see, use of rb. We've got device, inviable and uh, dev device, rb, we have added, uh, the inviable, uh, here. And in, uh, our user model, we have added the, the invitation, uh, attributes. Okay, let's, uh, save this and move forward. So get status gi add all GI comment, main, uh, message install, device invite. And now let's actually create a form where we can input a user's email to be able to make him a member. But first we would need to have like a list of all members of it inside the tenant. So let's, let's say like we will open a tenant, okay, we have this error. I think I just need to restore the server, okay? Yeah. And let's, uh, within a tenant have a link to the members. So tenant show. And here I will have a link to members. And the members should be scoped under the tenant. So it would be something like tenant members path. Let's go to our roots and input the members under the resources tenants, Okay? Uh, let's go to our show view. And here will have add talent. Let's see if this works. Yeah, we have a link to members. And here you see we have tenant slash two slash members. And inside members controller, we don't have index action. So let's edit the index. And here we will have add members equals current tenant dot members. And then to set the current tenant somehow. So let's say private dev, uh, set current tenant, and we will say at current tenant equals tenant defined by, uh, PERS tenant id. Because you see we have this tenant ID in the, uh, in the route. So yeah, and, and we will add the before action set, current tenant, and this should kind of work a refresh, okay? No index, uh, view. So let's go to Vs. Uh, members. Okay, I've was created a folder. Let's make the index H, tml, eer B. And we have an empty view, let's say, uh, at members each do member, and we will display the email. So let's say at member user email and the roles at member shows, okay, yeah, if that playing the members of the standard, that's good. Let's actually save this change. So get staples, Get add, all, get, commit, message, uh, this lay, uh, talent members, okay? And now it's actually do this invitation team. So let's add the form to invite a member. Let's maybe list all members, H one, uh, members, and separate the thing for inviting members. So invite members and I will have a form with URL. We'll input A URL here, do form. And here we will have equals form text field. We're going to input an email that's add a placeholder, and we'll have a submit button, form dot submit, uh, invite. And uh, what will the URL be? Let's, uh, construc the URL. Uh, so for example, it can be, uh, resources members do, uh, post invite. And this is going to be inside collection, collection door post invite. Okay, let's see this rule that we have just created, let's say rails rules, grab invite, uh, yeah, rails route. So Invite tenant members both. So let's go back here and it'll be URL, invite, tenant members path. Let's, uh, see if it works for us. I will refresh. And here we have the phone for inviting members. Let's see the URL. We have tenant slash two members invite. Let's try, click on this invite and see what happens. So nothing happened because we don't have this action implemented, and we can see an error here. Action invite is not defined. So let's go to our members controller and say, dev invite. And here we can try getting the pars. Or let's say we'll just put the binding binding B and let's try clicking the invite. Let's put something in the email and they're in the bindings. Let's say perms. We have tenant id, we have, uh, email. So let's say we can work with this. So we will say email equals brands email. And we are going to, uh, find a user by this email user equals user dot find by email. And if we don't find the user here, they're going to use device inviable. So, uh, they're going to send invitation invite. Let's see, uh, yeah, they're going to have this line. So, uh, user invite by the email from the pre and current user. And then we are going to say, user, do members find or create buy and we'll say talent. There'll be, uh, current talent. And we'll also set roles as, for example, admin, false and, uh, editor. True. Okay. Uh, let's see if this works. I will drop the binding, continue. And let's see. So, uh, I will go here. I will, uh, try to input, uh, some kind of, uh, email click invite. And did anything happen? Uh, yeah, we did send some kind of email, but, uh, did we make the user a member? Let's refresh. Yeah, the user has become a member. Okay, I need to refresh because I didn't make a redirect here. So I will say, uh, redirect tool, uh, what is the path? Turn on member path at current tenant with a notice, uh, uh, member invited, or let's actually put the email. So let's say, uh, email invited. Okay, let's try again. I will input some kind of, uh, other email address. And here we have email invited and he has been added to this list. Okay, looks, uh, quite good. What if we have a look at some outliers? What if we, we submit an empty form, we will get some kind of error. Most definitely record, not saved. You can not call, great. Unless the parent is saved. So let's actually add some validations. Let's say, uh, return redirect to, uh, tenant member's path, uh, with an alert, uh, no email provided. Uh, if email is blank. And we'll make another return, uh, here if, uh, the user is not valid. So user dot, uh, valid, unless, unless the user is valid. So we'll put these guards to check if we have submitted an email and if the user with this, uh, email text is, uh, valid. Okay, let's, uh, let's see. I will try and put in an empty email. You see, no email provided something like this. And uh, yeah, it would have to say, yeah, email invalid. So email invalid. Let's try once again. Okay. And this email is involved, so our guard closes well correctly, and yeah, that's it. We've managed to invite users to become members of this tenant. Let's, uh, save our changes. Let's say get status, get, uh, all, get the comment message, uh, uh, invite, uh, users to be members of talent. Okay, and, uh, now let's check some validations. Can I go to tenant slash one slash members? And yes, you see I can, but I'm not a member of the first tenant, so we would need to put a guard for this. Uh, in our members control, you see, we have this authorized member inside tenants. We need something similar inside our members controller, but they would need this kind of, uh, set tenant and authorized member for all the controllers that belong to a tenant. So everything that's scoped, uh, to your organization here, like tasks or projects or in Trello, like, uh, all their columns, cards, comments, inside cards, everything would have to have, uh, authorized number and set current tenant. So we can, uh, not just duplicate this code. Instead we will create another controller. Let's make it authorized controller rb, I will say clause authorized controller that inherits from application controller. And here I will put the, the line for set current tenant I'll removed from the members. I'll move these lines also to the authorized controller. I will, uh, also add the before action authorized member. So we will first set the tenant and then we'll authorize the member. Yeah, let's also change the order here so that it is, uh, hierarchical and importantly inside our members' controller. Now we are going to inherit not from our application controller, but from authorized controller. So, uh, all this code is going to automatically included in members' controller. Let's see if it works. I will go back to the application. I will refresh this page and define method users. Let's see, uh, yeah, because I should say current talent And yeah, I'm not a member of the talent, so it kind of works. Now we've got this kind of additional authorization so that only members of tenant can view like all the pages inside this tenant. And let's see how our application works. So let me create a new, uh, account. I've registered as a user, I can go to the list of tenants. I'm not a member of any tenants. I can not visit any tenant because I'm not a member of any. I can create a new tenant and, uh, yeah, I've created this tenant. I can go to the list of members and, uh, I can invite another user to be a member. If I invite an user that already exists in the application, he'll be just added as a member of the attend as an editor, not as an admin. And, uh, if I, uh, invite somebody new, so I click invite, they're actually going to send an invitation email for a user to be notified that we have invited him to our application actually as an additional start that you can do on your own after inviting the user to become a member, you could also send an email that he has been added to this workspace space. Yeah, something like this, it's something you can do on your own. And, uh, yeah, that's, uh, basically it. So, uh, uh, if I log in as this user, I will, uh, be able to view the tenant. Let me try accepting the invitation. Uh, yeah, I'm already signed in. Let me Log out. I will, uh, accept the invitation, set the passport, and go to tenants. And you see, I have been invited to this tenant. So the same as Slack as, uh, Trello. Uh, you have this kind of tenancy architecture built, and uh, yeah, here is a nice bullet plate based on which you can add any kinds of, uh, structures that belong to a tenant or like a workspace, but not to a user. So this is how you build in teams into a rub rails application from the very beginning. And if you want to add any additional tables, again, tasks or projects, everything with this kind of route based setting of the tenant would have to be scoped under resources, tenants. And I think it's very comfortable because, uh, this way if you have, uh, the current tenant set in the route as base Camp does, you can have, uh, a few different tenants open in different tabs. So, uh, other approaches like, uh, a popular approach for be to set, uh, tenant ID in the current user and user would have to switch tenants. But, uh, if you set the tenant in the roots, then you can have like ten three open. You could, uh, create a new tenant, uh, And have ten four open. So I have ten four here, 10 through here, and it works in different browser tabs, and you don't have to like have a separate button to switch tenants. So, yeah, that's about it. Thanks for being with me and thanks for creating this, uh, um, multitenancy application together with me. See you in the next one. --- # Reverse Engineering a Web App: Pipplet Language Testing Platform - URL: https://superails.com/posts/reverse-engineering-a-web-app-pipplet-language-testing-platform - Published: 2023-02-26 - Duration: 27:31 - Access: free - Topics: reverse engineering, database architecture 🔍 Recently Pipplet app has come to my spotlight. It is an online language testing platform, used by businesses to evaluate a candidates skills. As a pleasant surprise, the platform does not offer multiple-choice questions. Instead, it has open-ended questions. That way candidates are less likely to be able to cheat. 👨‍💻 In this session I am planning to navigate their UI and map-out how I would reproduce their apps functionality. Link to the Database Diagram that I created: https://dbdiagram.io/d/63fa64e8296d97641d83b984 Text walkthrough with screenshots: https://blog.corsego.com/pipplet-clone ⚠️ I am in no way affiliated with Pipplet. I do not endorse their platform either. This video is solely in educational purposes. This is Episode 115. In Episode 57 I did database modelling based on a Figma file. Check it out: https://www.youtube.com/watch?v=NZlN73NEgWM --- # #114 Recently viewed products with Kredis - URL: https://superails.com/posts/rails-7-114-recently-viewed-products-with-kredis - Published: 2023-02-24 - Duration: 08:02 - Access: free - Topics: Coding, Recently viewed products, E-commerce, Database, Data caching, Gem Kredis, kredis, Kredis unique Recently viewed products is a very common feature in Ecommerce. Showing users a list of products they viewed is considered to increase conversion. The least complex way of implementing this kind of list can be in Redis. Gem Kredis allows us to associate a Redis attribute with an ActiveRecord model. We will store and update the list of recently visited products in Redis and associate it with our Current user. Episode source code: https://github.com/corsego/114-kredis-recently-viewed-products/commit/22cfd31ad68bbb67ebf3b5f57b976b2dff5f6d7a Blogpost - kredis recently visited pages: https://blog.corsego.com/kredis-recently-visited-pages 0:00 Store "recently visited" in Redis vs Postgresql? 1:58 Kredis unique list 2:50 Add kredis attribute to User 4:00 Update recently_viewed_products 5:25 display users recently_viewed_products 7:25 Works! We can now store and display a list of 5 most recently visited products by the user ## Chapters - 0:00 Store "recently visited" in Redis vs Postgresql? - 1:58 Kredis unique list - 2:50 Add kredis attribute to User - 4:00 Update recently_viewed_products - 5:25 display users recently_viewed_products - 7:25 Works! We can now store and display a list of 5 most recently visited products by the user --- # #113 Kredis Live counter, HTTP vs Websocket Turbo Broadcasts - URL: https://superails.com/posts/rails-7-113-kredis-live-counter-http-vs-websocket-turbo-broadcasts - Published: 2023-02-22 - Duration: 13:59 - Access: free - Topics: kredis, hotwire turbo, actioncable, live update, redis, live counter, websockets - Playlists: Just enough Hotwire for Rails developers In the two previous episodes we use Kredis to store website visitors and users online. Let's dive deeper into what Kredis is and other ways we can use it! Redis is now baked into Rails by default. Kredis let's you easily save and retrieve data from Redis. Killer feature of Kredis - reference ActiveRecord objects with Redis attributes. For example - sstore User "preferences" in Redis, not Postgresql. There are a lot of usecases for Kredis, and in this video I explore just a few. In THIS episode we will build a counter with a PLUS and MINUS button. When clicked, the two buttons will be able to increase or decrease the counter respectively. We will also use Turbo Streams to update the counter without having to refresh the page. We will compare HTTP turbo streams that send updates only in the window where the action happened, with turbo stream broadcasts, that send updates to ALL the clients that have a page open. Episode source code: https://github.com/corsego/113-kredis-counter/commit/7819719780b7bd46770b8d1cafc156674fa85df2 Blogpost - kredis overview: https://blog.corsego.com/gem-kredis-intro 0:00 Redis in Rails 7 1:09 install Kredis 1:39 use Kredis from Rails console, Kredis list (array) 2:30 Kredis unique list (array) 3:26 Kredis counter (integer) 3:54 display Kredis counter in Rails view 5:58 button to increment counter 7:16 button to decrement counter 8:05 refresh counter with HTTP Turbo Streams 11:20 push live counter updates with Turbo Stream Broadcasts that use ActionCable and Websockets 13:30 Works! Counter gets updated in all browser windows! ## Chapters - 0:00 Redis in Rails 7 - 1:09 install Kredis - 1:39 use Kredis from Rails console, Kredis list (array) - 2:30 Kredis unique list (array) - 3:26 Kredis counter (integer) - 3:54 display Kredis counter in Rails view - 5:58 button to increment counter - 7:16 button to decrement counter - 8:05 refresh counter with HTTP Turbo Streams - 11:20 push live counter updates with Turbo Stream Broadcasts that use ActionCable and Websockets - 13:30 Works! Counter gets updated in all browser windows! --- # #112 Devise finally works with Rails 7 and Turbo, but with a twist - URL: https://superails.com/posts/rails-7-112-devise-finally-works-with-rails-7-and-turbo-but-with-a-twist - Published: 2023-02-16 - Duration: 06:01 - Access: free - Topics: rails authentication, devise, devise rails 7 As I recently mentioned, Devise has been finally patched to work with Rails 7 out of the box. But when I tried installing it, I had a few minor issues: - remember to use MAIN branch of devise - re-generate the devise.rb initializer, or add these lines to it: ``` config.responder.error_status = :unprocessable_entity config.responder.redirect_status = :see_other ``` Episode source code with comments: https://github.com/corsego/112-rails-7-devise-patch/commit/4717a4a6e12bb292b5515e97ce980d0c17751af7# The devise patch: https://github.com/heartcombo/devise/pull/5548 0:00 Devise Drama queens 01:00 Install Devise 2:21 Check if Devise works (it doesn't) 2:46 Make devise work (add config.responders) 5:10 Link to sign out 5:43 Summary ## Chapters - 0:00 Devise Drama queens - 01:00 Install Devise - 2:21 Check if Devise works (it doesn't) - 2:46 Make devise work (add config.responders) - 5:10 Link to sign out - 5:43 Summary --- # #111 Twilio SMS API - URL: https://superails.com/posts/rails-7-111-twilio-sms-api - Published: 2023-02-16 - Duration: 12:13 - Access: free - Topics: send sms, sms Twilio makes it super easy to send SMS from your Ruby app. In this episode we will: - get Twilio API credentials - get a Twilio phone number from which we will send SMS - verify a test phone number for receiving SMS in development - write an SmsService that we can call from anywhere in our app - ensure that when using the SmsService we don't send SMS to a real phone number - explore the Twilio SMS Dashboard Episode source code: https://github.com/corsego/111-twilio-sms-api/commit/5cecefabfc2228207989677c1dd5b54b21cf9218 Based on this blogpost: https://blog.corsego.com/rails-twilio-sms-api Don't worry, I've updated my twilio credentials. Don't forget to never expose yours! 0:00 Do you need SMS in 2023? 1:02 Install Twilio Ruby in a Rails app 2:52 Get Twilio API keys, phone number and whitelist countries 5:50 Try sending an SMS 6:22 Make Twilio SmsService generic 8:50 Send generic SMS message 9:53 Summary of code changes 11:09 Explore Twilio dashboard ## Chapters - 0:00 Do you need SMS in 2023? - 1:02 Install Twilio Ruby in a Rails app - 2:52 Get Twilio API keys, phone number and whitelist countries - 5:50 Try sending an SMS - 6:22 Make Twilio SmsService generic - 8:50 Send generic SMS message - 9:53 Summary of code changes - 11:09 Explore Twilio dashboard --- # #110 Realtime Online User Tracking with Actioncable, Kredis, Turbo Broadcasts - URL: https://superails.com/posts/rails-7-e110-realtime-online-user-tracking-with-actioncable-kredis-turbo-broadcasts - Published: 2023-02-10 - Duration: 24:13 - Access: free - Topics: websockets, actioncable, hotwire, hotwire turbo, live updates, live counter, track users online, redis, kredis - Playlists: Just enough Hotwire for Rails developers This episode was inspired by the great work of other creators. Hopefully my input tackles some flaws that would appear in a production environment, demistifies some code and potential challanges. Zero lines of javascript. Don't store view login in models. Don't hit the relational database on every web action. Do you think this can be improved even more? Let me know what you think 🙏. Episode source code: https://github.com/corsego/110-live-users-count/commit/238ab48dd8871b20c30387fd1c64d362992560d8 Based on this blogpost: https://blog.corsego.com/action-cable-broadcast-users-online Timestamps approach to see users online: https://blog.corsego.com/set-user-status-online 0:00 Other approaches to tracking users online 2:41 Demo of the final application 4:22 Track online users with Kredis, Hotwire Turbo Stream Broadcasts and Action Cable 21:15 Not logged in users can see users online 23:32 Summary ## Chapters - 0:00 Other approaches to tracking users online - 2:41 Demo of the final application - 4:22 Track online users with Kredis, Hotwire Turbo Stream Broadcasts and Action Cable - 21:15 Not logged in users can see users online - 23:32 Summary --- # Rails 7 E109 Live vistor count. ActionCable, Turbo Broadcasts, Kredis - URL: https://superails.com/posts/rails-7-e109-live-vistor-count-actioncable-turbo-broadcasts-kredis - Published: 2023-02-07 - Duration: 22:01 - Access: free - Topics: hotwire, turbo, redis, websockets, kredis, live update, server side rendering Let's add "Live count for total website visitors" and "Live count for visitors inside a room". Importantly, we will not deal with user REGISTRATION to identify a visitor. One browser session = one unique visitor with a session_id. We will use "turbo_stream_from" to plug into an ActionCable Channel and track websocket subscriptions and un-subscriptions. Whenever a user "subscribes", we will add his browser session_id to a Kredis array. We will Broadcast the updated Kredis array COUNT to everybody subscribed to the channel. Sounds quite complex, but I still think of it as a very low-code solution. Text walkthrough: https://blog.corsego.com/action-cable-broadcast-users-online Episode source code with comments: https://github.com/corsego/109-live-visitor-count-action-cable-kredis-turbo/commit/645c08eb981edaaad919f070e300a2ad24d28b08 0:00 Demo - Track visitors on a website and in a room 1:51 Track all website visitors with Kredis and ActionCable 12:25 Update live vistiors count with Turbo Stream Broadcasts 14:29 Track visitors in a Room 20:40 Summary and final demo ## Chapters - 0:00 Demo - Track visitors on a website and in a room - 1:51 Track all website visitors with Kredis and ActionCable - 12:25 Update live vistiors count with Turbo Stream Broadcasts - 14:29 Track visitors in a Room - 20:40 Summary and final demo --- # #108 How to use the Browser Geolocation API with Rails - URL: https://superails.com/posts/ruby-on-rails-108-how-to-use-the-browser-geolocation-api-with-rails - Published: 2023-02-02 - Duration: 08:07 - Access: free - Topics: browser api, stimulusjs, javascript, geolocation api - Playlists: Maps & Geolocation Another geolocation episode! In previous episodes we learnt to get COORDINATES based on address, and place coordinates on a map. Now, we will learn to get a users' accurate geolocation based on browser Geolocation API. It is supported by any browser! We will integrate Geolocation into a Rails app with StimulusJS. Warning! Asking for access to the location on page load will result in most of your users rejecting access. Blogpost for this video: https://blog.corsego.com/stimulus-get-browser-location Episode source code: https://github.com/corsego/108-geolocation-api/commit/9aed77ce811492a16e8c78d1dccb74106c531b2b P.S. what do you think about my new setup 🌜? That's actually the reason I did the few previous episodes without showing face 🌝 0:00 Geocoding and maps recap 0:35 Example search by current location 1:08 Browser Geolocation API 1:40 Stimulus Geolocation Controller 4:04 Button to get location 5:10 Search near me (use browser coordinates in query) 6:40 Works! DEMO 7:18 Geolocation accuracy problems?! ## Chapters - 0:00 Geocoding and maps recap - 0:35 Example search by current location - 1:08 Browser Geolocation API - 1:40 Stimulus Geolocation Controller - 4:04 Button to get location - 5:10 Search near me (use browser coordinates in query) - 6:40 Works! DEMO - 7:18 Geolocation accuracy problems?! --- # #107 How to display locations on a map with Mapbox API and gem Mapkick like AirBnB - URL: https://superails.com/posts/ruby-on-rails-107-how-to-display-locations-on-a-map-with-mapbox-api-and-gem-mapkick-like-airbnb - Published: 2023-01-30 - Duration: 15:38 - Access: free - Topics: mapkick api, gem mapkick, map markers - Playlists: Maps & Geolocation In the previous episode we learnt to use gem Geocoder to calculate COORDINATES (latitude, longitude) based on address. In this episode, we will display multiple coordinates on a map. Together with Geocoder search, this will empower us to create interactive maps like AirBnB does! We will also make the links to the locations CLICKABLE! Text description: https://blog.corsego.com/gem-mapkick-rb Episode source code: https://github.com/corsego/107-gem-mapkick/commit/58295c740ec2a0bb96717a31095d93463c15fe03 Gem mapkick: https://github.com/ankane/mapkick 0:00 Previous work with Geocoding 0:54 Airbnb example 1:46 Install Mapkick-RB 2:45 MAPBOX_ACCESS_TOKEN 3:45 display a map with different options 7:23 separate map for each location 8:27 clickable links to locations 10:55 display all locations on ONE map 11:45 customize JSON data 13:41 force JSON data to respond to search params ## Chapters - 0:00 Previous work with Geocoding - 0:54 Airbnb example - 1:46 Install Mapkick-RB - 2:45 MAPBOX_ACCESS_TOKEN - 3:45 display a map with different options - 7:23 separate map for each location - 8:27 clickable links to locations - 10:55 display all locations on ONE map - 11:45 customize JSON data - 13:41 force JSON data to respond to search params --- # #106 How to use gem Geocoder, GPS coordinates, distance calculation, map markers - URL: https://superails.com/posts/ruby-on-rails-106-how-to-use-gem-geocoder-gps-coordinates-distance-calculation-map-markers - Published: 2023-01-27 - Duration: 20:48 - Access: free - Topics: geocoding, address to coordinates, coordinates on map - Playlists: Maps & Geolocation In this episode we will use gem Geocoder to: - find for GPS coordinates (latitude and longitude) based on an address; - calculate distances in miles and kilometers; - search for locations within range (near); - search for locations within location (nearbys); - use Mapbox API to display a static map Episode source code: https://github.com/corsego/106-gem-geocoder/commit/ca4485399e9b7d12e1ffde807eec3970eb314068 Text walkthrough: https://blog.corsego.com/gem-geocoder-ruby Mapbox API: https://docs.mapbox.com/playground/static/ 0:00 latitude and longitude coordinates 01:00 geocoder search, calculations 3:25 scaffold Location, seeds 4:40 Geocode existing addresses 6:30 Search Locations NEAR (console) 7:35 Search Locations NEAR (form, controller) 9:35 Configure search NEAR distance 10:31 Search NEARBYS (locations close to current location) 11:55 Distance between locations 12:43 use KILOMETERS, not MILES 14:00 Mapbox Static Maps 17:10 Works! DEMO 17:31 re-geocode if address changed 20:01 Summary ## Chapters - 0:00 latitude and longitude coordinates - 01:00 geocoder search, calculations - 3:25 scaffold Location, seeds - 4:40 Geocode existing addresses - 6:30 Search Locations NEAR (console) - 7:35 Search Locations NEAR (form, controller) - 9:35 Configure search NEAR distance - 10:31 Search NEARBYS (locations close to current location) - 11:55 Distance between locations - 12:43 use KILOMETERS, not MILES - 14:00 Mapbox Static Maps - 17:10 Works! DEMO - 17:31 re-geocode if address changed - 20:01 Summary --- # #105 parse JSON and YAML - URL: https://superails.com/posts/ruby-on-rails-105-parse-json-and-yaml - Published: 2023-01-25 - Duration: 09:12 - Access: free - Topics: yaml, json, data parsing JSON and YAML are great ways of storing data structures. You can parse a JSON/YAML file directly with Ruby, or within a Rails application. You can also WRITE to the file! Popular usecases: - parse file and create seed data - display parsed default data directly in a view (for example FAQ section) Episode source code: https://github.com/corsego/105-parse-yaml-json/commit/90282b5468fbfe78bcbb1df29ef91de5eaa160a2 Parse JSON with Ruby on Rails: https://blog.corsego.com/parse-json-with-ruby-on-rails Parse YAML with Ruby on Rails: https://blog.corsego.com/parse-yaml-yml-with-ruby-on-rails 0:00 incoming JSON and YAML data 0:50 parse YAML with Ruby (no Rails) 2:01 update and save YAML file 3:01 parse JSON with Ruby (no Rails) 3:50 display YAML in Rails controller 6:05 display JSON in Rails controller 6:37 seed database data from datafile ## Chapters - 0:00 incoming JSON and YAML data - 0:50 parse YAML with Ruby (no Rails) - 2:01 update and save YAML file - 3:01 parse JSON with Ruby (no Rails) - 3:50 display YAML in Rails controller - 6:05 display JSON in Rails controller - 6:37 seed database data from datafile --- # #104 Remote Calendar Subscription with WEBCAL - URL: https://superails.com/posts/ruby-on-rails-104-remote-calendar-subscription-with-webcal - Published: 2023-01-23 - Duration: 07:11 - Access: free - Topics: calendar api, calendar - Playlists: Build a Calendar WEBCAL protocol allows a user to "subscribe" to a calendar with many events in your app. The users calendar app will “ping” your calendar endpoint from time to time to get the updated events list. Don't forget that the route to the calendar subscription path should not require authorization! Episode source code: https://github.com/corsego/104-webcal-icalendar/commit/093302163c824ecd5aa42d9350c8f4c5543c567d Text walkthrough: https://blog.corsego.com/icalendar-ruby 0:00 What are calendar subscriptions? 1:00 Webcal subscription demo 2:04 Create link to subscribe to calendar 5:16 It works! 6:00 Warnings and Summary ## Chapters - 0:00 What are calendar subscriptions? - 1:00 Webcal subscription demo - 2:04 Create link to subscribe to calendar - 5:16 It works! - 6:00 Warnings and Summary --- # #103 Simple Omniauth without Devise - URL: https://superails.com/posts/ruby-on-rails-103-simple-omniauth-without-devise - Published: 2023-01-16 - Duration: 17:01 - Access: free - Topics: rails authentication, omniauth, oauth2 Previously I’ve covered "Github omniauth with Devise". An even simpler solution would be to sign in via a social login provider (Github) without Devise at all! Here’s the easiest way to create your whole social authentication solution from zero! Episode source code: https://github.com/corsego/103-simple-omniauth/commit/f52fd98c7fb6dc3ea61f7b2608d07dfe40ea4130 Text version: https://blog.corsego.com/omniauth-without-devise PR adding this feature to an app, together with tests: https://github.com/yshmarov/superails/pull/5/files 0:00 the problem 0:45 github omniauth 3:50 respond to auth callback 6:30 User model 8:39 Store User in session 10:10 Set current_user 11:46 Logout 12:50 user_signed_in? 14:50 require_authentication 16:20 Summary ## Chapters - 0:00 the problem - 0:45 github omniauth - 3:50 respond to auth callback - 6:30 User model - 8:39 Store User in session - 10:10 Set current_user - 11:46 Logout - 12:50 user_signed_in? - 14:50 require_authentication - 16:20 Summary --- # #102 Email Calendar Invite - URL: https://superails.com/posts/ruby-on-rails-102-email-calendar-invite - Published: 2023-01-12 - Duration: 15:17 - Access: free - Topics: calendar, action mailer, email invites, calendar api, event invitation - Playlists: Build a Calendar In the PREVIOUS episode we generated .ICS files, that can be downloaded and added to your calendar. In THIS episode we will EMAIL calendar invites and automatically add them to a users calendar! We will also handle updating/cancelling events! Episode source code: https://github.com/corsego/101-icalendar/commit/a76266bf86d78367984c5d6938c8425fd14d3241 Text description: https://blog.corsego.com/icalendar-ruby 0:00 Introduction 1:10 generate Mailer 2:04 attach calendar invite 3:02 button to send email 6:15 deliver emails 7:50 add organiser 9:50 RSVP (yes-maybe-no) 10:39 add attendee 12:10 UID has to really be unique 12:50 automatically RSVP Yes 13:40 update event details 14:20 cancel event ## Chapters - 0:00 Introduction - 1:10 generate Mailer - 2:04 attach calendar invite - 3:02 button to send email - 6:15 deliver emails - 7:50 add organiser - 9:50 RSVP (yes-maybe-no) - 10:39 add attendee - 12:10 UID has to really be unique - 12:50 automatically RSVP Yes - 13:40 update event details - 14:20 cancel event --- # #101 iCalendar and .ics format. Add events to calendar - URL: https://superails.com/posts/ruby-on-rails-101-icalendar-and-ics-format-add-events-to-calendar - Published: 2023-01-05 - Duration: 20:45 - Access: free - Topics: calendar, calendar api, calendar event encoding, ical - Playlists: Build a Calendar Learn to create valid calendar events, that a user can download as an .ics file and add to his calendar! Resources: - text version of this video: https://blog.corsego.com/icalendar-ruby - source code: https://github.com/corsego/101-icalendar/commit/9ff3a2aa99d32ab5a51e9b6ed158553f37b49dde - gem icalendar: https://github.com/icalendar/icalendar - icalendar documentation: https://www.rfc-editor.org/rfc/rfc5545 0:00 ics format 2:05 Boilerplate app 2:40 gem icalendar 5:50 download ics file 8:24 add location field 10:05 remove duplicates in calendar (uid, sequence) 12:07 include urls 13:55 change ics filename 15:16 summary 15:35 refactoring 19:10 final demo ## Chapters - 0:00 ics format - 2:05 Boilerplate app - 2:40 gem icalendar - 5:50 download ics file - 8:24 add location field - 10:05 remove duplicates in calendar (uid, sequence) - 12:07 include urls - 13:55 change ics filename - 15:16 summary - 15:35 refactoring - 19:10 final demo --- # #100 Browser tab title notifications - URL: https://superails.com/posts/ruby-on-rails-100-browser-tab-title-notifications - Published: 2022-12-27 - Duration: 16:43 - Access: free - Topics: hotwire, server side rendering, browser api, tab notifications, javascript, stimulusjs, turbo broadcasts - Playlists: Just enough Hotwire for Rails developers Do you like see the "new notifications count" in the LinkedIn browser tab? In this episode we will update the browser tab title, if the application user has new unsee notifications. We will use StimulusJS to add a blinking effect. We will use Turbo Stream Broadcasts to update the notifications count in all browser tabs. We will also add an "undead" symbol to the application thumbnail. In the context of StimulusJS, we will leverage setInterval and usage of disconnect(). Improvements you can do yourself: - Scope notifications per user - Mark notifications as "seen" when accessing "notifications#show" page/controller - Broadcast notifications count when notification is deleted or seen Episode source code: https://github.com/corsego/100-browser-tab-notifications/commit/bc5b318dfa8986b36f50fbd1dda1be6c5c4e1069 Text version: https://blog.corsego.com/browser-tab-title-notifications 0:00 Feature request 0:19 What we will build 1:02 Initial setup 1:35 conditional thumbnails 4:20 conditional notifications count 5:05 StimulusJS - blinking page title 10:40 Broadcast update thumbnail (no page refresh) 15:52 Summary ## Chapters - 0:00 Feature request - 0:19 What we will build - 1:02 Initial setup - 1:35 conditional thumbnails - 4:20 conditional notifications count - 5:05 StimulusJS - blinking page title - 10:40 Broadcast update thumbnail (no page refresh) - 15:52 Summary --- # #99 Hotwire: Search and Infinite Pagination (Ransack with Pagy) - URL: https://superails.com/posts/ruby-on-rails-99-hotwire-search-and-infinite-pagination-ransack-with-pagy - Published: 2022-12-09 - Duration: 17:26 - Access: free - Playlists: Just enough Hotwire for Rails developers Implementing search or pagination is relatively easy, but it can get tricky when you try to combine the two, especially when you factor in some frontend. In this eposide we will learn to use gem Ransack for search, and gem Pagy for infinite scroll pagination. Episode source code: https://github.com/corsego/99-ransack-pagy-search-infinite-scroll/commit/ab6b64bb3f233d3deac9b7ed4a6532eb30d610bc Text version: https://blog.corsego.com/hotwire-search-infinite-pagination 0:00 Final app demo with search and pagination 0:55 Boilerplate app 1:20 Ransack search 3:40 Search without page refresh 4:50 Remove submit button 5:40 Pagination with pagy 7:25 Infinite pagination 10:40 Stop endless pagination loop 11:35 Fix search 13:40 Query security 16:00 Highlight search results 17:05 Summary ## Chapters - 0:00 Final app demo with search and pagination - 0:55 Boilerplate app - 1:20 Ransack search - 3:40 Search without page refresh - 4:50 Remove submit button - 5:40 Pagination with pagy - 7:25 Infinite pagination - 10:40 Stop endless pagination loop - 11:35 Fix search - 13:40 Query security - 16:00 Highlight search results - 17:05 Summary --- # #98 Inline editing attributes with Hotwire - URL: https://superails.com/posts/ruby-on-rails-98-inline-editing-attributes-with-hotwire - Published: 2022-12-05 - Duration: 14:38 - Access: free - Topics: hotwire - Playlists: Just enough Hotwire for Rails developers Allow user to easily edit an attribute by just clicking on it! This way you can have fewer full-page redirects; don't have to always render a full edit form. 5 years ago I would have used gem best_in_place for this, but now it can be easily achieved with turbo_frames! Episode source code: https://github.com/corsego/98-hotwire-inline-edit-attributes/commit/1e6154863cff969bec5266add752a9d591f5a1a4 Text version: https://blog.corsego.com/hotwire-inline-edit-attributes 0:00 Inline editing problem 1:20 Edit single attribute inline 5:15 Edit any attribute individually 10:00 Link to edit empty value 10:35 Validations 11:40 Save by clicking outside 13:00 Optionally render whole edit form ## Chapters - 0:00 Inline editing problem - 1:20 Edit single attribute inline - 5:15 Edit any attribute individually - 10:00 Link to edit empty value - 10:35 Validations - 11:40 Save by clicking outside - 13:00 Optionally render whole edit form --- # #97 PASSWORDLESS login with MAGIC LINK - URL: https://superails.com/posts/ruby-on-rails-97-passwordless-login-with-magic-link - Published: 2022-11-16 - Duration: 20:34 - Access: free - Topics: authentication Passwordless authentication via magic link is an interesting alternative to email-password authentication solutions like Devise. A passwordless authentication flow looks like this: 1. Enter your email address 2. Receive login link in an email 3. Click link = You are logged in! I’ve implemented passwordless authentication in insta2blog.com, and for now I am super happy with the solution 🚀. Feel free to try it out! In a way this is a more secure authenication strategy, because there is no compromised password point of failure. It is as secure as your email account. However to even start using this solution in production, you will need to set up sending emails in production. It is not hard to create this kind of authentication solution on your own, however I prefer not to reinvent the wheel. Gem passwordless neatly solves the problem. Episode source code: https://github.com/corsego/97-passwordless-auth/commit/34517fddf74572308780c1593ef23c0c85872301 Text version: https://blog.corsego.com/gem-passwordless-magic-link-auth 0:00 Demo how magic link login works 1:29 install gem Passwordless 7:05 sign in flow, find or create user 9:13 missing host to link to 11:30 click on link to sign in, link to sign in and sign out 13:50 fix Turbo errors 16:11 customize magic link Email 17:11 gem letter_opener to preview emails 18:27 authorization - only logged in users can edit posts 19:47 Summary ## Chapters - 0:00 Demo how magic link login works - 1:29 install gem Passwordless - 7:05 sign in flow, find or create user - 9:13 missing host to link to - 11:30 click on link to sign in, link to sign in and sign out - 13:50 fix Turbo errors - 16:11 customize magic link Email - 17:11 gem letter_opener to preview emails - 18:27 authorization - only logged in users can edit posts - 19:47 Summary ## Transcript Hello. Recently I was building a hobby project and at some point I needed to let users log into the application, so I needed to add an authentication solution, and I didn't really feel like adding device because it felt like a cumbersome, uh, heavy solution to add to this simple lightweight application. So I wanted to use a more simple authentication strategy, and what came to my mind was to use, uh, magic link authentication. This is how it works. I would click some kind of log in, uh, uh, link and be redirected to a form that I need to input my email. So this is a form for a magic link, uh, authentication. I would be able to input my email address and click send magic link. After this, I would receive an, uh, email with a link to sign in. Here's the email, so the magic link and, uh, it says, please confirm and here is your link to sign in. It'll expire in, uh, an hour, and this is the device that you use to sign in. So if I click this link, here I am, I'm signed in and I can see this user specific data. I can see my username, I see the logo button. So you see, all I did was input my email address, receive an email and log in. That's it. And this way you don't have to store passwords and uh, this solution is as secure as your email account. But if somebody managed to break into your email account, I think you've got bigger problems to worry about. So we are going to implement this kind of magic link password list authentication in this episode, and we're not going to build it from zero. We are going to to use a nice solution named Passwordless. So it is a cool gem to add this kind of, uh, magic link authentication. So let's add it to a new rails application. Now here I've already generated a new Rails application and it has a list of posts. So, uh, if I start the server, I have, uh, I have created a new application and I can add posts. And I want to make it so that the uh, posts are tied to users and only a user who has value created the post can edit the post or delete this post, and only an authenticated user can create the post. So I would need to add current user and all this kind of authentication. So let's go through this gem. I will add the gem passwordless. So I will say, um, bundle add passwordless. This way I don't have to manually edit to the gem file and run bundle because I, uh, injected it into the gem file using this bundle add, and it automatically, uh, run the bundle command. Now I can run the migrations for passwordless. Let's do this. So let's see this migration file that was just generated. I'm going to DBM migrate, and here's this migration. So here we're going to keep all our authentication tokens, all these, uh, kind of, uh, magic link, uh, uh, information. So we know here's the magic link. The remote attributes, uh, is, uh, the IP address. This is the user agent. So, uh, that it was like Mozilla, Macintosh and so on, uh, claimed at time out, add and expired at. So how long until, uh, the authentication expires, how long until the token is made invalid. So quite, uh, basic information that you'll need to make this tokens valid and make them work. Okay, let's go further. We need to generate a current user. So we need a user table. And, uh, in our user model, we will need only an email. Now remember, when you create a a device user, that will be a real lot of information, but here, well, we need just some kind of unique idea. That is the email to which we're going to send the, the authentication links. So we are going to generate a user model with an email. So here is the migration. We just need an email for the user. And you can also say, null false. Well, you, you don't want to have a user without an email. Okay, now we will run the migrations rails. Did we migrate? And, uh, let's go back. So, uh, suggests, uh, us to go to the user model and to add this line passwordless with email to, well let the passwordless module know, uh, that we're using this model. They use model for authentication and we are using it, uh, to authenticate by the email attribute. So let's go to our user rb, and here we're going to add passwordless with email. And we are also going to add these validations. Now, part of these validations, I would also suggest adding validate in the format so that, uh, the red of the email, uh, does look like an email. I will say also format equals with URI mail tool. Re email re let's have a look at what this, uh, looks like in the console. I will start the rails console right in the strateg. And you see this is a project that you don't have to come up with, uh, on your own because it already exists. So this will improve the email validation. And let's continue. So we will need to add the passwordless rules for our users. I will go to our rules rb and uh, I guess on the top I'll add this Passwordless for users. Let's have a look at the route that have been generated. I will start the server and go to local host rails in info route. And here on the very bottom, we have this route for Passwordless engine. So it has created four new route that sign out, that sign in, uh, the submission of the form and the, the submission of, uh, the token sign in path. Okay, now let's, uh, continue. So also in the application controller, we're going to add the password list. Let's go to our application Controller. And here we're going to add a couple of methods. So, uh, the first method, the helper method, current user. This one is going to find our current user by the session. And the second method required user will be used to ensure that only an authenticating user can go to the new action, to the edit action, create, update, and destroy the post. So only authenticated users will be able to like, uh, do actions with posts, but uh, uh, authenticated users will be able to see posts. Only see posts. So, okay, let's see what else we have in this. Uh, uh, read. Nothing, uh, important at this moment. I guess let's already try using this parts for less authentication. I will, uh, oh, maybe start the server and go to our application. Okay, so I want to sign in somehow. Uh, how can I sign in? Let's have a look at the route. So it would be slash sign signin. Okay, no route matches, signin. I'll maybe go to users slash signin. And here is this route. Now I don't want this route to be slash users. I will make it just slash signin. So we also have this in our, we would update our route to be, not passwordless for user, but passwordless for users at the bare URL. So let's go back. I will, uh, refresh this, uh, send in page and I don't need the slash users anymore. Okay, it looks better. So let us try to send in, I will input and email address and it says, well, if you found the email, they'll have sent you an email. Looks good. Let's have a look at the, uh, logs. They have this email anywhere. So, uh, actually no. So no email has been sent. Let's also ensure that there was no passwordless session created. I will start the new console. So let's start the new console, passwordless session.com. So no session has been created because uh, we have to explicitly tell passwordless that, that we want to be able to create new users. Then, uh, searching for an email. So we have to either find or create by the email, not just find, because by default, passwordless would work as kind of invite only and uh, it wouldn't create new users if they're not found by this email. So again, in the rhythm, need to find this FE resource for passwordless and we will say find or create by email. This way we are actually going to create our users. So we are going back to our user rb and we are going to say, uh, find upgrade by the email. Now we don't need this email application because, uh, well it is the same. So, uh, let's go back. I will try to sign in. I will use the same email. I click send magic link and already you have a different problem. So missing host link to, let's have a look at our console. So here we went to the mailer. We tried to send a magic link email and we got an error. The error says that, uh, it's missing a host link to. So we didn't manage the process, send in an email. Okay, so it already tried to send an email, it's already some progress. Let's have a look in the console. So the first Passwordless, uh, session has been created. Uh, the token is filtered for security visit the user agent, the, uh, local host, uh, address, user ID one. Okay, so we created a possible session and we've also actually like created a user. So user first, yeah, the user with this email has been created. Okay? So we need to now set up sending emails in our development environment. So I will go to development rb and here I'll need to add, uh, a few lines. So I will say, uh, config action mailer default, UL options Equals host will be local host and port will be 3000. And I'll need to add a few additional grams. So action mailer delivery method, let's say SMTP and uh, form deliveries. Let's say a form deliveries equals, let's say false. And we will say, uh, raise delivery errors put true. Okay, so, uh, let's see if uh, this actually works. Let's say be dev, we were starting to sell because we have changed something in our environment settings. I will click send magic link and it and then happen. Yeah, so an email has been sent and if you don't have kind of an error in the console, but you see this form hasn't updated, we didn't go to the like successful submission page. So there is still a problem to solve. But here we have our magic link. So let's try and use this magic link. So I will just, well click this magic link, I will copy it and add it to my browser. And didn't happen, I dunno. We might be logged in. We might not be logged in. Let's see if we have a current user, let's go to our application HTML and try to find the current user. Let's say, um, equals current user, Okay? Uh, dot email. So yes, we are actually authenticated. There is a current user looks good. So let's add some, uh, code to be able to, uh, log out. So, uh, we have our current user email and we'll say link tool and we will add some kind of logout path. Uh, what is the route for logging out? Let's see. It is sign out path. So we would say, um, uh, sign out and I think it'll be out dot sign out off. And uh, we would actually need to make it not a link to button bottom. And we would say method would be delete. Let's see if it works. I'm going back. Uh, okay, some kind of syntax error. Yeah, Mr. Coma. So, uh, it would, is not sign up, but uh, sign out. Okay, let's try click sign out and no method, current user email. So if we did manage to sign out, let's update this links. Let's say, uh, if current user dot present, then they're going to show the email and the send out link. Let's see. Okay, it works. So now we'll also need to show a link to sign in if there is no current user. So we'll add an L statement, so else, and we'll have a link two Equals link two sign in and it'll be al sign in path. Now we need this alt to specify that they're using password plus for these uh, URLs. So okay, we have the sign in and it kind of works. So we've added our basic, uh, user standard navigation. And uh, now we need to fix this problem. You see, I will input an email and this phone doesn't redirect, so I kind of, uh, submit, but it doesn't redirect because we need to make Turbo work correctly with this jump. So I will need to generate the views and let's see how we can do it. So we will say rails generate passwordless views and uh, there are actually really few views. So in device and order like tive views I guess. And here we have all the three templates. Let's have a look at these templates. So, uh, I'm going to passwordless in our views and we have our mailer. So this is the text of our mailer. We can customize it the way we want. We can add the session details. For example, I'll say add session attributes. Then we have our sessions new and sessions create. I don't know why this was, uh, not created in the sessions folder in my case. I will, uh, I will move it to the correct folder now. So the correct action is going into the sessions folder. Okay, and what do we have here? So here we have, uh, uh, a text that, uh, I have successfully, uh, submitted the form and here is the form itself. So for my comfort, I will update it from form, photo form with no doubt. Session here has the URL, and we will importantly say data, Uh, turbo false. Okay? And we can also add some additional, uh, uh, frontend validation for our email field to require the email before submitting the form because, uh, I think if, uh, we don't require the email in this form, let's try to submit it. Um, yeah, so we can actually submit the form without an email and it should not kind of, uh, be possible. So let's say via required True, okay, now I can submit the form without having an email so it looks better. And let me now try to submit the form. So I will take one of these emails, click submit. And you see now as I have disabled turbo on this form, it does correctly redirect me to the next page. So it looks quite well we have, uh, updated this form and let's actually have a look at the email. So here is the email, and you see we've also added this session data. Now, you don't want to send all this kind of session data in the email, but you might want to add something. So here we have our session user agent, uh, the time the token was created. So, uh, we can say something like, you might have tried to log in from this device and we will have the session user agent and regards your app team. So this way would, would, uh, say that like there was a login from some kind of specific device. Let's, uh, try this email. Once again, here's the email. So here's the link and here's, you might have tried to log in from this device and the information about this device. Okay, looks good. Now it is kind of, uh, naturally nice to look at our emails in our consultative. Want a better way to preview our emails. So a good way to develop emails in a developing environment would be with the gem, uh, letter opener. Yeah, this one. So here's the gem and let's add this gem to our application. So I'm going to our GEM file development group, and here I will add the gem letter opener. Now I'm running the bundle And to make letter open up work, I'm going to say delivery method lab, the opener in our development environment. So not as MTP and perform deliveries through like this. Now let me stop the server. Once cancel, I will restart it bin do. And let me once again try, uh, signing in. So here I will try to send a magic link. And you see in a new tab, it actually opened the email. So here we can have a good preview of what our emails look like. And uh, we can click the magic link without going to console. So I cleared the magic link and you see I'm signed in. Okay, now let's actually work on, uh, some, uh, authorization. So only users that are logged in can go to create. And you post, I will now sign out and I will add this action we've added to our application controller. This one require user to outpost controller. So I will say before action require user and only for a few actions. So only let's start with, uh, new, let's see if it works. I'm going back. You see, whenever I click new, it directs me to the root path, but I can still go to all other paths. So let's require a user for all the actions except the index and show. So require user only, let's say, um, we have, uh, new added create date and destroy. Okay? Now, whenever I try to not just view a post, but uh, edit it or go to new or destroy a post, I have to be signed in. So let's try signing in And now I can access all these actions. So this way you can, uh, require a user to be authenticated to access specific actions in your controllers. And, uh, I think that's, uh, basically it. So, uh, there isn't really much to it. Just you must also remember that you should, uh, also enable, send an actual real emails in your production environment before going further with this, uh, authentication method because, uh, uh, if, uh, well you only have, uh, emails sent in your development environment, uh, you won't be able to actually authenticate any users except, uh, if you see the emails in your console in the production environment. So I encourage you to have a look at my other videos where we set up sending emails in production using AWS Simple Email service. So I guess that's it, uh, and see you in the next one. --- # #96 Deploy to Digital Ocean App Platform - URL: https://superails.com/posts/ruby-on-rails-96-deploy-to-digital-ocean-app-platform - Published: 2022-11-11 - Duration: 32:48 - Access: free - Topics: deployment In this episode we will deploy a Rails 7 app that uses Postgresql and Redis to Digital Ocean App platform - an "easy deploy & manage" solution. We will also connect a custom domain and set up log forwarding. Text description: https://blog.corsego.com/deploy-to-digitalocean-apps-platform App that I deployed (restaurant ordering app): https://github.com/yshmarov/qr-menu-app ## Transcript Hello. So as, uh, you must already know, Heroku is depreciating its free tier as of the 28th of November. And it's really sad because over, I guess 10 years or even longer, Heroku was the first place where a developer could, uh, deploy an application to production and see how it would look in, uh, a reduction environment versus a development environment. But this error is over. And personally I don't see any sense of, uh, paying for a paid, uh, tier on Heroku where you can, uh, pay the same amount or approximately the same amount on other platforms. And, uh, the platform of choice for me is digital Ocean. So, uh, here I've already deployed one application on digital Ocean App platform that, uh, costs, uh, minimum $12 per month. That will be $7 for the database and $5 for the application, uh, itself. And, uh, I'm kind of satisfied and it gives you a great, uh, possibility to, uh, have more control than you have in Ruku over your application. Now there are droplets that, uh, are like containers and they, uh, can potentially be cheaper, but, uh, apps on the ad platform are much easier to manage and, uh, it is much closer to what you'd get on Heroku. So in this episode, they go to deploy an application to Digital Ocean app platform. And if you still do not have a digital, digital Ocean account, uh, first of all try to create one. Now here I've got this referral link. So if you use it, I'll also add it in the video description. You'll get the $200 to, for the start end, I will get 25 onto my credits. So for everybody anyway, if you create additional Ocean account and go to billing, so, uh, there'll be a tax, uh, dependent on the country you're working, uh, you're creating the account from. So, uh, you can also try changing that billing address. And if the billing address is in the us, then there will be no additional tax. If it'll be in the U, then you'll pay around 20%, uh, more on all everything you do in the digital ocean. So if you have a US address, then it's kind of going to be cheaper everything by default. So when you're done with the creation of, uh, your account, uh, you will go to apps and you can deploy and your application to do it, you would go to create and you select app. And I'm going to select one of the repositories that I already have. So I have already connected my GitHub to Digital Ocean and I can select a repository. And the repository that I'm going to select is going to be this UR menu app. Now, before we continue, let's have a quick, uh, look and understanding of this application that I am going to deploy. So imagine you don't know anything about this application yet and you want to deploy it. Uh, so, uh, let's have a quick look at this application in development. Now, when I see an application that, uh, I haven't worked with, uh, before and I need to understand something, the first place I go is the Gem file. So yeah, the gem file, I see it's running Ruby 3.1 rails seven, uh, input maps for JavaScript. Uh, and uh, yeah, it's from Puma. Okay, so well everything is uh, nearly default rails, uh, seven and yeah, it's also got read as, uh, most of the default stuff on rail seven VDI for two broadcasts. Now, um, we are going to check rails application credentials, whether the application is using any credentials. No, as you see all the credentials files are, uh, lines are commented out. So we don't use any secret credentials in this application at the moment, but we still need a master key. So, um, I've, uh, deleted the previous credentials via ml and I've generated a new master key by, uh, typing editor VM rails credentials added. So now I'll type it once again, and as I got a master key, it can be crypt, uh, decrypt credentials via mail. So I'm going to exit fim and okay, I've had a look at the GEM file. So just a simple rail seven application. Let's have a look at our rules. So we don't have device, we don't have, uh, current user as I see, and we've got just a few, uh, different rules and we've got an admin namespace. So what is this admin name space, maybe we have some kind of admin that we can log in with. Let's have a look. I'm going to our app controllers and here we've got this, uh, admin orders and stats, and they belong to secure controller. So secure controller. Here it is, we have some kind of basic HGTP authentication. So to access the admin pages, we will need to use this, uh, username and this password. Okay, now let's try running this application locally. So I've, uh, run bundle, run rails, db, create, I've run Rails, DB seed. If I look into the seeds, there are actually some seeds here. And let's try run this application locally. I will start the server by saying rail server and let's have a look at it. So this application is a basic, uh, like menu, online menu, order ordering application. So, uh, here I can select a few things, add them to cart. So I will add like three bounties. I will add one split like this, or two, I'll add some ellos. I'll go to my cart. Yeah, here is my cart. And you see I have a bunch of items in my cart. I can remove some items if I want or I can complete my order. And if I complete my order, let me display. So if I click complete order, then it appears in the orders in progress. And you know, like you go to McDonald's and uh, you see all these screens with the, like the orders that have been prepared. So the same way you would be able to see orders in progress. So, uh, there is one that is done and one that is, uh, submitted and users that have access to this admin, they can go to manage orders. So here I will, uh, go to manage orders and I can, uh, change the status of the process, uh, of the order. So like I see, uh, from the admin side, okay, this order has appeared. I can have a look at the items in the order and I can say, okay, I'm going to start working on this order. So I update the status and everybody who's uh, ing this page of the queue receives a broadcast Adobe broadcast that the status of this order has changed. So I'm changing to delivery and to dawn. And you see there are already zero orders in progress. So the application is using the hot wire broadcast for delivering this, and Hotwire broadcast require Redis. This is another important thing, uh, you need to know, uh, to be able to run a Rail seven application that uses Hotwire. And the more thing, if you look to at Gem file log, you would have a look at, uh, the platforms and you would need to ensure that you have, uh, uh, the Linux platform, otherwise you would have, uh, problems with running the application. Okay, enough about this application. Let me comment the changes that I've created. So here I've got this updated credentials file. Let's, uh, save it and push and name of the application on GitHub is QR menu app. So let me finally start, uh, this application on the digital app Ocean Apps platform. So I'm going to create, uh, I think I've done here already. Yeah, I'm going to create, I'm select GitHub, and uh, I will select QR menu app. So I'm going to use the main branch and let's click next. Now, I'm also selecting auto Deploy. And uh, actually if you don't select Auto deploy the main branch, then I'm not sure how you can, um, select it once again. So usually it's better to have auto deploy. I'm clicking next and here are the resources. So, uh, this is what you're going to pay for. So I will go and click edit plan. Okay, everything looks um, fine here, let's go back. Uh, yeah, here is added plan. And you see by default it says this one, but the, the one that is $12 per month, but you can, uh, select something cheaper. So, uh, you can select this basic thing and by default it's $10 per month, but you can select a cheaper tier. So $5 per month. So if you clicks and you select the cheapest tier, and that's actually enough to get you going for the beginning, you can always, uh, scale it up. Now I'm going back and I'm going to add some resources. So, uh, you can say detect from Source code, but usually I would, uh, explicitly say that I want to add a database. So I would say Add, and it's going to be a, uh, Postgres scale database, and the cheapest one is $7 per month. And uh, you see it's not, uh, a production ready database, but it is the cheapest one. The, the cheapest production Ready one is around, uh, 12 I guess dollars per month. So anyway, $7, I'm saying, uh, grade and attach. So we've got our application and we have got our database. Okay, and, uh, let's continue. I'm going to click next and we've got environment variables. Now this is also really important. So, uh, for our application we are going to add our rails key. So, uh, if you go and search for Rails Master Key, so the Rails master key is the one that you've got in the, uh, master dot key. So you will need this key. Without it, your application will, well just not strong. So we've got the Rails master key, but it's not the only variable that you'll need. Uh, let's actually have some inspiration from a Heroku application. So I will take, uh, one of the Heroku apps. I've got, uh, let's take, um, this one for example, I'll go to settings, uh, reveal configure variables. And you see, uh, Haruku did a lot of stuff for, for you that you didn't have to think about. So you will need to set these things yourself on Digital Ocean and on any other place where you are deploying your code. So we are going to set our, uh, rec environment. Uh, yeah, let's actually just say bulk editor. So Rails master the key rec n equals production, then Rails n equals production. Then uh, rails log to SDD out enabled. If you don't, uh, make it enabled, then you will see no logs. So enabled then Rails serve static files also enabled, otherwise UCSS and GS will not load. And we have our, uh, rails Gnostic key and our secret key base is available inside our Rails credentials. So we don't need to serve the secret key base separately. Let's again go to editor BIM Rails credentials edit. And here is our secret key base. So they're not serving it separately, but uh, if you're not using real credentials, you could just serve the secret key base. Anyway, uh, we have added these uh, environment arrivals, so the master be key, uh, and a few settings for our application. And let's click save. Okay, and uh, again, I'm clicking save and click next. So now you are deciding where you want to deploy. Uh, it can be London, it can be any other region that is closest to where your application users are. So you should think of uh, choosing a region that is uh, close not to where you are, but to where the biggest quantity of your users are. That way they will have the best experience. So anyway, it can be London for me, that's fine. And I'm clicking next. And you see we've got our application, we've got our database. Same on Heroku. You would have database URL, but it'll come back to that later. And you see it says, uh, it would be five plus $7 a month and I'm gonna create resources and application is being traded and is being built. Okay? So it took around seven minutes to deploy and uh, here it shows that uh, the build was successful and that the deployment was successful. So, uh, it says deployed successfully and going up, you see it was provided a URL and you can also click live app. So if you click, it says, so something went wrong. Now let's see what went wrong. I will go to runtime logs and they are available. These runtime logs are available because uh, we added the environment variable, uh, to send the logs. So I'm going to set ins two components, uh, to the application component, QR menu app, and uh, he have this rails log to out enabled. If it was not enabled, then the runtime logs would not be updated. But again, what's the problem? Active record connection not established. So, uh, we don't have the connection to the database. We can also try to check the connection to the database by going to their console. So this is kind of a web console for your Rails application. I'm going to Rails console and uh, let's see if something does get started. Okay, we've got the rails. Let's uh, try getting something from our application. I'll go to Schema for example, and we have menu items or we have orders, let's say order.com. And you see the connection is not established. So the problem is that we have created a Postgres scale database, but we did not attach it to our application. So now we need to attach our Postgres database. Um, we can go to our database in our settings. So we go to our database and here we see that it is yes that database and we need to get the connection details for this database. So you see on Heroku we have this database, URL and there some kind of long URL. So we are going to get the same kind of URL for our digital ocean database that we have created. So I'm going to disconnection details and click view. Here we have the connection parameters. I'm going to use the connection stream. So here we have the connection stream. I'm going to copy it and I'm going to add this connection stream to our application here. So I'm going to this other component, QR menu app. And in all these environment variables, variety, environment variables, I am going to add the this database, URL. So here it is and I'm going to just say that is the database URR, okay? And uh, now it is going to redeploy the application. So you see it's building the application again because each time you change something in the settings, it's going to redeploy the application. Okay? And again, the application is deployed, so let's try going to the live application. And yeah, we still have this that, sorry, but something is wrong. So let's have a look what's wrong this time I'm going back to the runtime logs of the application and what do we have? So, uh, statement invalid relation orders, that does not exist. So here the problem is that we just need to run rails to be migrate. So the easiest way would be to go to the console and here you would say rails be migrate, but the solution would, uh, require you to run Rails to be migrate each time we deploy with new changes in database. So I'm not going to do that. Instead I'm going to say rails be migrate inside the settings of the application. So each time when we deploy the application, they're going to run the migrations. So here I'm going to find again going not to the app settings, but to the component settings. Here is our application component. And here I'm going to find the build command. So here is the build command and the run command. And inside the run command, before running the rail server, I'm going to say, uh, uh, rails, they be my grade and they should do it. So the expectation is that each time before the application is started with the new deployment, we're going to run Rails DB migrate. So I'm going to save. And again, considering that we've done some changes in the application settings, it is redeploying the application and I will expect it to run the migrations. We can go to the deploy logs and uh, monitor what happens. Okay? So once again, it says that the application has been deployed and have see our build logs and our deploy logs. And if I go to our deploy logs, we say that the application has been started, but before the application has been started, all these immigrations have been drawn. So the immigrations have been drawn, okay, I'm going back to the application and I will see, yes, I see the menu. Now of course, I didn't have anything seeded, so I can actually try see it, uh, now and here we also see there was an error once again. Let's see what kind of error this falls. I'm going to the runtime logs of the application. Yeah, let's give it a moment. Let me refresh and uh, I'll refresh once again. Yeah, apparently it needs some time. Maybe it's because I've just added a new application, okay, this time it seems to be working Okay. So yeah, here's some kind of error. And there error is with SOT connection. So I can assume that it is because I don't have red running. Yes. So see here I see the problem with red. Why do I have the problem with the red? Because then going to this menu page slash menu, uh, I have a tool of broadcast from this page. So let me go to menu index in the code. And here I will have, uh, or maybe not here broadcast. Let's see if I have, uh, broadcasts, uh, turbo stream from, okay, so on the order page and on the uh, queue page, so I have a turbo stream from uh, orders. And to establish this double stream, we would need the uh, red dis connection. So the application doesn't work now, uh, because we don't have, uh, red disconnected. So now I will need to connect the register the application. So to connect red this I will need to go to databases and create a new database. So here I already have one database. I'm going to create a new database And uh, yeah, the region is London, that's fine. Uh, I already have a Postgre scale database and now I'll need a red database. And we see that unfortunately the cheapest plan is $15 per month. So it already puts our price to uh, five plus seven plus 15. So that's $29 per month. If our application does need Redis. So if our application is using the Gem credits or um, sidekick, or if we're using the top stream broadcasts, then we we'll need Redis. So yeah, the name of the Redis cluster, let me name it QR menu app, Redis. We don't need any tags or actually we can say QR menu app. Okay? And I'm creating this database cluster. Now, it'll take a few minutes, uh, possibly to create the application, but we already have our connection parameters, so I'm going to take the connection stream, I will copy it and I will add this connection stream to our application. I'm going back to apps. Um, here is the app that we just created, going to settings, going to the settings of the Rails application. And here I'm going to connect the Stratus, uh, uh, database that we are creating at the moment in our databases. So here we have six environment variables. I'm going to add one more. I will say Redis, URL and add this variable. And let's have a look at web, have the Stratis, URL in our code. So I will search for this CRL. And here you see we have config cable via ml. And here we would look for the red CRL in our environment variables for production. Okay, so uh, I'm going to click save. And now again, it's trying to redeploy our application. I hope there will not be a collision that our database for Redis is uh, not ready yet. You see, it's still being prepared, but our application is already being deployed, so possibly I will have to redeploy or forced redeploy the application once more. Maybe I should have first waited for the Redis application to be, uh, fully created and then connected to our, um, app, uh, because now it's trying to redeploy our application and it might, uh, give an error. But anyway, let's wait and see. Okay, so now I see that the database has been deployed. Let's have a look at our application. Okay, not droplets, but I should have gone to apps. So going to apps, here's our app. The app has, uh, well also been deployed. Let's, uh, open the app and yeah, it seems to be working. So let me open the app in a new tab and see if the to broadcast part that uses Reddits actually works. But for this, I would need to add some records to the database. So I will need to run the seeds to run seeds. I'm going to go to the, uh, console and say Rails the B seed, and this should, uh, well run our seeds. So yeah, it must have worked. Let's go back to the application. I will refresh and you see some records have been created. So I will, uh, try to add some records to my card. So add in a few of these. You see there is no full page refresh. Looks fine. I'll go to the cart. Uh, on the other screen I'm going to the queue. I will say complete order. And you see an item has appeared in the queue. So read this and Tobo stream broadcasts are working, looks, uh, quite satisfying to me. So yeah, here is how we deployed Rail seven application with the Postgre Scale database and the drug to production. But let's go further. Let's add a custom domain. So, uh, at the moment we don't have a domain and if we go to the app settings already, not the application, not the Rails application settings, but the whole app settings here, we have these domains. So let's add a domain. Uh, let me go to name Chip and here I have a few domains. So, uh, let's see. Uh, I will connect this domain, let's say very 30. I'm not using it for anything at the moment, I guess let's just check, no, I'm not using it. Okay, so I'm going to connect this domain er.com. So I'm going to our domains. I'm going to add a new domain. So it's going to be very cty.com, like very certified or whatever. And uh, uh, I manage my domain and I'm going to create a CNAME and add this LAS. So I'm going to do the main settings. I'm going to advance DNS. And uh, here I already have some kind of, uh, CNAs. I'm going to delete these and I'm going to create, uh, a few new ones. So these ones were using Hero previously, and these ones are used to, uh, get the domain verified on Amazon. So I'm going to create a C name, uh, it's going to be at and, uh, the Digital Ocean application. So at is when they're using the bare domain. And if you're using www, then we'll add the www on. So I'm saying add Domain, and yes, I'm also going to add the www to direct to the same, uh, application. So, uh, I will say www.veryc.com. So again, I manage my domain and it says www CNAME should direct to this, uh, uh, coral app. So I'm going back to name Chip, going to create a CNAME record www. And here's Dogg, save the Changes. So I'm done with name chip and uh, let's click add domain. And the domains are being added on the digital Ocean side. Now you see it's deploying once again. So let's wait for the deployment to be complete. Okay, it says it's complete. Let's go to the live application. Now it says, uh, it's got some trouble maybe because it, uh, requires a few minutes for the, the settings to be updated and for it to work. So here, sometimes when updating like the main related stuff, you just need to wait for a little time. So let's wait. Okay, so the domain still doesn't seem to be working and uh, I've been waiting for like 20 minutes now and uh, I've tried pinging the CNAME of a domain in some kind of DY check tool. It does find the domain name. So really it can take, uh, longer to make the domain work, but from the setting perspective, we've done everything that we needed. We've added the domains and uh, if we've added the CNAME records, so we just need to wait until they get refreshed. And a final touch to our deployment would be log forward in. So log forwarding is here, and what it allows us to do, it allows us to connect a specialized application to which we would send all the logs of our application. So all the logs that we see in runtime logs, you see now I click runtime logs and we see just a tiny bit of, uh, history and, uh, different applications like Datadog or Paper Trail allow you to keep a big history of logs and to potential analyze these logs or search for something specific in your logs. So a good practice for production applications would be to use log forward in the tools like Paper Trail for an example. So Paper Trail by default does have a free tier and they're going to connect it. So here, if I go to my dashboard, I already have just created an account on Paper Trail and I already have one application connected. I'm going to create one more, uh, paper trail application. So I'm going to go to settings and I'm going to add a new log destination. So, uh, I'm going to click Create Log Destination. Uh, I will name it, uh, how do I name this application, uh, QR menu app. So QR menu app, uh, allow logs, uh, click and create. And here we have this log destination. So it is 52 38 4. And I'm going to add this log destination in our application settings. So I'm going, uh, here, going to our application settings, log forwarding, maybe actually do it on our, uh, yeah, app level settings. So here I'm going to go to log forwarding. I'm going to, uh, add Paper Trail and the Endpoint. So I've added this endpoint and uh, now I expect to be able to go to that dashboard in Paper Trail here. I, uh, must see already not one system, but two systems maybe I need to actually send some kind of logs. So I will, I will try again to refer this, this application. Okay, it still doesn't work. So I will go to this, uh, domain name that was provided by, uh, by Digital Ocean. Where is it? Domains. Here's this domain. I'm going to this domain and uh, well, let's try pushing some logs. So I'll also open the runtime logs here. So, uh, let me, I don't know, add something to the code. I, Yeah, you see, it's not super comfortable to see stuff here, but uh, you see there was a Adobe Stream rendered menu item 88. I will click once again. There was again a top stream. I will click, uh, once again. Okay, it's been a bit, uh, slow. Maybe because the application has just been deployed for, well the first time it's still not working on full speed. So I guess that's okay. So let me add some more items, uh, and see if we see them in the locks. I'm adding this item. You see we have many item I nine, uh, top stream. And let's see if we already are sending this logs to Paper Trail. So I will refresh that dashboard once again and I still don't see the second system. Interesting. Okay, so now I do see how this Coral app from Digital Ocean in our paper trail account. And what I had to do is go to the Digital Ocean application, go to actions for Build and Deploy and Deploy. And when I did it, it send information to Paper Trail and Paper Trail registered this, uh, second system, the Coral app. So now if I go to this Coral app in Paper Trail, I will see the whole log history and let me try something. I'm just going to the application here. I'm going to refresh it. I'm going to and click on a few pages and you see all these logs are being registered in Paper Trail. So you have the whole log history and that's about it. I think it was quite a long video, but, uh, covered a lot of stuff. So we've created a new app on Digital Ocean app platform. We've set up a red database, we've set up a regular database. We've set up, uh, the domain names, we've set up log forwarding, and importantly we've set up our application variables. So, uh, here we have our environment variables, the rails tic key, the environments, the log for paper trail, and for being able to see the logs in runtime logs. We've set up the rail self static files so that the CSS and Gs are bundled. The database URL and this URL so that the, our Rails application on Digital Ocean can communicate with our Redis and the, uh, and with our, uh, postgre scale application on Digital Ocean. And we've also added added rails DB migrate before Rail Server on the run command so that we don't have to run Rails DB migrate each time we deploy. And that's about it. Thanks for your attention and have, uh, a great experience deploying your Rails applications in the future. Goodbye. --- # #95 parse text and convert hashtags and mentions into links - URL: https://superails.com/posts/ruby-on-rails-95-parse-text-and-convert-hashtags-and-mentions-into-links - Published: 2022-11-07 - Duration: 13:57 - Access: free - Playlists: Mentions, Hashtags Parse text to find hashtags and mentions and turn them into searchable links. This is a CORE feature when building a social texting app! Episode source code: https://github.com/corsego/95-hashtags-mentions/commit/d71f12ff792572c0ef7127a302115cb16c428487 Blogpost: https://blog.corsego.com/search-by-hashtags-or-mentions Insta2Blog: https://www.insta2blog.com/ --- # #94 Rails 7 Select Box with Search using Slim-Select - URL: https://superails.com/posts/ruby-on-rails-94-rails-7-select-box-with-search-using-slim-select - Published: 2022-11-01 - Duration: 18:19 - Access: free I was planning to create a short episode on enabling slim-select, but it ended up having a big boilerplate for adding "select" and "multiselect". If you don't need the boilerplate, feel free to skip to 9:25. The boilerplat app uses Importmaps for JS and Tailwind for CSS. I like slim-select because it uses vanilla javascript and does not rely on jQuery. If you don't like slim-select, the second best alternative is tom-select. Episode source code: https://github.com/corsego/94-slim-select/commit/13daec3b195cbc126adf6588e96b2850b0d883c4 Text version: https://blog.corsego.com/stimulus-slim-select --- # #93 Slack API - send transactional messages from your app to Slack - URL: https://superails.com/posts/ruby-on-rails-93-slack-api-send-transactional-messages-from-your-app-to-slack - Published: 2022-10-17 - Duration: 17:18 - Access: free Configure Slack Bot permissions and get an API token. Send transactional messages from your app to Slack. Episode source code: https://github.com/corsego/93-slack-api/commit/deec85cc1e9940dbcec4f6a03da42e34fce5458d Gem slack-ruby-client: https://github.com/slack-ruby/slack-ruby-client Text version of this video: https://blog.corsego.com/slack-api-with-rails Slack API: https://api.slack.com --- # #92 Github Actions CI (run Tests, Rubocop, Erblint) - URL: https://superails.com/posts/ruby-on-rails-92-github-actions-ci-run-tests-rubocop-erblint - Published: 2022-10-14 - Duration: 35:43 - Access: free ⚡️ Here's how you can set up **Github Actions** on a new Ruby on Rails project with: ✅ Postgres ✅ Redis ✅ Minitest ✅ System tests ✅ Rubocop ✅ Erblint Git repo: https://github.com/corsego/92-github-actions-ci/pull/2 Rails tests CI script: https://blog.corsego.com/github-actions Install Rubocop, CI script: https://blog.corsego.com/install-and-use-rubocop Install erblint: https://blog.corsego.com/erb-linting --- # #91 Rmagick with Rails ActiveStorage - URL: https://superails.com/posts/ruby-on-rails-91-rmagick-with-rails-activestorage - Published: 2022-10-06 - Duration: 20:41 - Access: free - Topics: image generation In the previous episode we used Rmagick to generate images with pure Ruby. Now we will learn to generate images based on ActiveRecord objects and store them in ActiveStorage. Text version: https://blog.corsego.com/rmagick-activestorage Episode source code: https://github.com/corsego/91-rmagick-activestorage-activerecord/commit/091b7f93736e3eacf9631d975d962d56d7107ecf --- # #90 How to use Rmagick to autogenerate images - URL: https://superails.com/posts/ruby-on-rails-90-how-to-use-rmagick-to-autogenerate-images - Published: 2022-10-03 - Duration: 28:52 - Access: free - Topics: image generation ImageMagick is a library that allows you to do all kinds of image manipulations. Rmagick is Ruby plugin for ImageMagick. In this episode we will learn to read, resize, crop images; overlay images above images; overlay text above images. Rmagick docs: https://github.com/rmagick/rmagick Episode source code: https://github.com/corsego/90-rmagick-ruby Blogpost: https://blog.corsego.com/rmagick-activestorage --- # Yaroslav Shmarov - 18 months of using hotwire and viewcomponent in production - wroc_love.rb 2022 - URL: https://superails.com/posts/yaroslav-shmarov-18-months-of-using-hotwire-and-viewcomponent-in-production-wroc_love-rb-2022 - Published: 2022-09-23 - Duration: 35:32 - Access: free - Topics: conference talk - Playlists: Live talks --- # Live talk: Hotwire, ViewComponent, StimulusJS in production (my experience ) - URL: https://superails.com/posts/live-talk-hotwire-viewcomponent-stimulusjs-in-production-my-experience - Published: 2022-09-10 - Duration: 28:57 - Access: free - Topics: conference talk - Playlists: Live talks On August 25 I gave (my first ever) live talk in Warsaw, Poland. I described my experience developing Rails monolyth apps using Hotwire, ViewComponent, StimulusJS, that I've been doing for over a year now. I think I've found a lot of great patterns that can be re-applied. I think the biggest challenge is discovering "what is the limit of X technology? how far can I push it?" Afterwards, we had BBQ and drinks at @Visualitypl together with @Arkency --- # #89 export to CSV (different approaches) - URL: https://superails.com/posts/ruby-on-rails-89-export-to-csv-different-approaches - Published: 2022-07-28 - Duration: 21:14 - Access: free Explore different ways of exporting data from a Ruby on Rails database to CSV. First we will create flexible custom templates for generating CSV data in a specific way we want to display it. Next, will create a generator that downloads a whole database table as CSV. Each approach is good, depending on your specific situation. Episode source code: https://github.com/corsego/89-csv-export/commit/608fa8f9a9960f14b5c7619c8ccfb3a95b94da35 Text version: https://blog.corsego.com/export-csv-from-rails-database --- # #88 Import CSV and parse it within a Rails app - URL: https://superails.com/posts/ruby-on-rails-88-import-csv-and-parse-it-within-a-rails-app - Published: 2022-07-25 - Duration: 17:17 - Access: free In this episode we will import a custom CSV into a Rails app. We will also map columns from the CSV, so that they fit our database. To conclude, we will extract the business logic from the Controller to a Service. Episode Source Code: https://github.com/corsego/88-csv-import/commit/6a6a43687ca5c967e32f3f1772d70f4d480a51ea Text version: https://blog.corsego.com/import-csv-to-rails-database Ruby CSV class: https://ruby-doc.org/stdlib-2.6.1/libdoc/csv/rdoc/CSV.html --- # #87 StimulusJS select all checkboxes - URL: https://superails.com/posts/ruby-on-rails-87-stimulusjs-select-all-checkboxes - Published: 2022-07-18 - Duration: 14:41 - Access: free - Topics: stimulusjs Episode Source Code: https://github.com/corsego/87-stimulus-select-all/commit/cab81cc09d1dc9052eb776b15b2fe5b619359f9a Text version: https://blog.corsego.com/stimulus-checkboxes-select-all --- # #86 Bulk Actions, Edit Multiple Selected Records - URL: https://superails.com/posts/ruby-on-rails-86-bulk-actions-edit-multiple-selected-records - Published: 2022-07-11 - Duration: 11:47 - Access: free Episode Source Code: https://github.com/corsego/86-edit-multiple/commit/93be87e9b0a65b0afdd1ccd1782d7458e481c9ad Text version: https://blog.corsego.com/checkbox-mass-update-records --- # #85 HTTP Authentication in Production - URL: https://superails.com/posts/ruby-on-rails-85-http-authorization-in-production - Published: 2022-07-04 - Duration: 10:13 - Access: free Sometimes the most simple authorization solution is just enough. HTTP Basic Authorization is built into every browser. Here's how you can use it with Rails Episode Source Code: https://github.com/corsego/85-http-authorization/commits/main Text description: https://blog.corsego.com/http-basic-authentication --- # #84 Cookies Consent Banner - URL: https://superails.com/posts/ruby-on-rails-84-cookies-consent-banner - Published: 2022-06-27 - Duration: 10:10 - Access: free - Playlists: Just enough Hotwire for Rails developers Here's the easiest way to add an "Accept Cookies" pop-up banner modal and save preferences in a Rails monolyth app using Hotwire Turbo Frames. No 3rd party providers required! Episode Source Code: https://github.com/corsego/84-cookies-gdpr-banner/commit/88390dff756021b70e2da3b9a5961da377921081 --- # #83 import and use SVG in Rails. gem inline_svg - URL: https://superails.com/posts/ruby-on-rails-83-import-and-use-svg-in-rails-gem-inline_svg - Published: 2022-06-15 - Duration: 12:05 - Access: free Sometimes instead of depending on an external SVG library you will want to import your own SVGs into a Rails app. In this episode we will look at different approaches and problems while importing and styling SVGs in a Rails app. I recommend using the gem "inline_svg" that offers a well-maintained and tested "inline_svg_tag" that will let you apply any HTML attributes to your SVG. Also consider that you might sometimes have to update the source code of an imported SVG, so that it can be transformed using classes and styles withing your app. 0:00 The problem 0:45 Import SVG from Figma & Fontawesome 2:27 image_tag (render SVG as IMG) 4:00 Try styling SVG 5:03 SVG fill currentColor 6:00 svg_tag (render SVG as SVG not IMG) 8:00 Fix SVG size scaling 9:20 Fix Fontawesome size scaling 10:21 Use gem inline_svg Episode source code: https://github.com/corsego/83-rails-svg/commit/c96ca0ae88d989abc5579def927cecb39e2201e1 Blogpost: https://blog.corsego.com/gem-inline-svg Gem inline_svg: https://github.com/jamesmartin/inline_svg ## Chapters - 0:00 The problem - 0:45 Import SVG from Figma & Fontawesome - 2:27 image_tag (render SVG as IMG) - 4:00 Try styling SVG - 5:03 SVG fill currentColor - 6:00 svg_tag (render SVG as SVG not IMG) - 8:00 Fix SVG size scaling - 9:20 Fix Fontawesome size scaling - 10:21 Use gem inline_svg --- # #82 Chained Select Fields in a form. Gem City-State - URL: https://superails.com/posts/ruby-on-rails-82-chained-select-fields-in-a-form-gem-city-state - Published: 2022-06-02 - Duration: 22:56 - Access: free - Topics: hotwire - Playlists: Just enough Hotwire for Rails developers Episode source code: https://github.com/corsego/82-gem-city-state-dynamic/commit/6b0738c9b5338fa19f1d93406e2deafbbebb1d72 Gem city-state: https://github.com/loureirorg/city-state 0:00 Intro 1:00 Scaffold Addresses 1:41 Gem City-State 5:25 Basic validations 6:09 Advanced validations 12:00 Humanize labels 13:45 Form validation button 15:38 Stimulus autosubmit 18:32 Turbo Frame 20:40 Stimulus reset form 22:17 Summary ## Chapters - 0:00 Intro - 1:00 Scaffold Addresses - 1:41 Gem City-State - 5:25 Basic validations - 6:09 Advanced validations - 12:00 Humanize labels - 13:45 Form validation button - 15:38 Stimulus autosubmit - 18:32 Turbo Frame - 20:40 Stimulus reset form - 22:17 Summary --- # #81 StimulusJS: Conditionally display form fields based on selected value - URL: https://superails.com/posts/ruby-on-rails-81-stimulusjs-conditionally-display-form-fields-based-on-selected-value - Published: 2022-05-30 - Duration: 08:22 - Access: free - Topics: stimulusjs - Playlists: Just enough Hotwire for Rails developers Episode Source Code: https://github.com/corsego/81-stimulus-conditional-fields/commit/2aa716d1d9d233487209153d3c006bc76ef5f5e1 Blogpost: https://blog.corsego.com/stimulus-display-show-hide-div-based-on-value 0:00 Intro 1:36 Create Stimulus controller 7:05 Final result ## Chapters - 0:00 Intro - 1:36 Create Stimulus controller - 7:05 Final result --- # #80 Dynamic Forms with Turbo (not JS approach) - URL: https://superails.com/posts/ruby-on-rails-80-dynamic-forms-with-turbo-not-js-approach - Published: 2022-05-26 - Duration: 18:23 - Access: free - Playlists: Just enough Hotwire for Rails developers Learn how to add fields to a form dynamically based on your selection in previous fields Episode Source Code: https://github.com/corsego/80-turbo-dynamic-forms/commit/7d416aa68aeb49a360aaded21517dcf3e4ae72e3 0:00 Intro 1:05 Scaffold Posts 2:05 Conditional Validations. Enum 4:25 Conditionally Display Passcode field 5:51 Custom Form Button 9:07 Stimulus clicker 13:28 Turbo Frame 15:03 Turbo Stream inside Frame 16:52 Summary ## Chapters - 0:00 Intro - 1:05 Scaffold Posts - 2:05 Conditional Validations. Enum - 4:25 Conditionally Display Passcode field - 5:51 Custom Form Button - 9:07 Stimulus clicker - 13:28 Turbo Frame - 15:03 Turbo Stream inside Frame - 16:52 Summary --- # #79 Classless CSS frameworks - URL: https://superails.com/posts/ruby-on-rails-79-classless-css-frameworks - Published: 2022-05-16 - Duration: 06:41 - Access: free Episode source code: https://github.com/corsego/79-classless-css-frameworks/commit/c569484b89ae6f41a13ec64c1c4326059897a3c5 Text description: https://blog.corsego.com/classless-css-frameworks Simple CSS Framework: https://simplecss.org MVP CSS Framework: https://andybrewer.github.io/mvp/ --- # #78 Semantic HTML in Rails - URL: https://superails.com/posts/ruby-on-rails-78-semantic-html-in-rails - Published: 2022-05-12 - Duration: 08:11 - Access: free Episode Source Code: https://github.com/corsego/78-semantic-html/commit/3a85f3f6b59e9799b372ab03306de76d4d2b05b7 Blogpost about Semantic HTML in Ruby on Rails: https://blog.corsego.com/semantic-html-in-rails --- # #77 HACKERMAN: strong params authorization - URL: https://superails.com/posts/ruby-on-rails-77-hackerman-strong-params-authorization - Published: 2022-05-09 - Duration: 07:26 - Access: free Even if some parameters are not visible or editable in the HTML, it does not mean a user won't be able to change them. This way a user can potentially do malicious actions in your app. Make sure that only expected attributes are editable, using strong params! Episode source code: https://github.com/corsego/77-strong-params-authorization/commit/4609d17e23b5b5f244e554d91b32e68d852b6ae8 Text version: https://blog.corsego.com/permit-params-by-current-user-roles Rails Docs: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html --- # #76 Fontawesome with Importmaps in Rails 7 - URL: https://superails.com/posts/ruby-on-rails-76-fontawesome-with-importmaps-in-rails-7 - Published: 2022-04-24 - Duration: 07:28 - Access: free Episode source code: https://github.com/corsego/76-fontawesome-rails7-importmaps/commit/669b9a5b1a0a064d55c1d5ec6b5fa95e450fd005 Text guide: https://blog.corsego.com/fontawesome-importmaps-rails7 --- # #75 ActsAsVotable: likes, bookmarks, hotwire, vote scopes, cached votes - URL: https://superails.com/posts/ruby-on-rails-75-actsasvotable-likes-bookmarks-hotwire-vote-scopes-cached-votes - Published: 2022-04-21 - Duration: 52:20 - Access: free - Playlists: Just enough Hotwire for Rails developers Complete guide to ActsAsVotable Episode source code: https://github.com/corsego/75-acts-as-votable-revised/commit/9b315211f4f5d53b2453b0a0296a1c551d3c3c2b Some code clean up after all the hard work: https://github.com/corsego/75-acts-as-votable-revised/commit/4f5feac95557393dcbb435da4b157e4f7b7e5ee5 Gem ActsAsVotable: https://github.com/ryanto/acts_as_votable --- # #73 Hovercards with Hotwire Turbo Frames - URL: https://superails.com/posts/ruby-on-rails-73-hovercards-with-hotwire-turbo-frames - Published: 2022-04-14 - Duration: 14:03 - Access: free - Playlists: Just enough Hotwire for Rails developers Learn to build lazy-loading github-styled hovercards(tooltips) with Hotwire Turbo Frames. Great example of using lazy-loading feature of turbo_frames. Blogpost: https://blog.corsego.com/hotwire-hovercards Episode source code: https://github.com/corsego/73-turbo-frame-hovercards/commit/99e270c9aee903c69dc37d946eddb3accb4eb188 0:00 Final app demo 1:35 HTML details tag 4:00 Load on open dropdown 9:03 My cat! 9:15 Load on hover 12:30 Customize CSS 13:15 Make links work ## Chapters - 0:00 Final app demo - 1:35 HTML details tag - 4:00 Load on open dropdown - 9:03 My cat! - 9:15 Load on hover - 12:30 Customize CSS - 13:15 Make links work --- # #72 gem Rouge for code highlighting in Markdown - URL: https://superails.com/posts/ruby-on-rails-72-gem-rouge-for-code-highlighting-in-markdown - Published: 2022-04-11 - Duration: 09:31 - Access: free If you are using Redcarpet, you will definitely want to also add Rouge! Text version: https://blog.corsego.com/markdown-styling-with-rouge Episode Source Code: https://github.com/corsego/72-gem-rouge/commit/6c2b27eaf3cc951b32ae6099602544c724446a9a Gem Rouge: https://github.com/rouge-ruby/rouge 0:00 Intro 1:13 Install Gem Rouge 4:25 Rouge CSS Themes 6:16 WORKS. WHY? 6:48 Customize CSS Theme ## Chapters - 0:00 Intro - 1:13 Install Gem Rouge - 4:25 Rouge CSS Themes - 6:16 WORKS. WHY? - 6:48 Customize CSS Theme --- # #71 Gem Redcarpet to Parse Markdown text to HTML - URL: https://superails.com/posts/ruby-on-rails-71-gem-redcarpet-to-parse-markdown-text-to-html - Published: 2022-04-06 - Duration: 15:12 - Access: free Text version: https://blog.corsego.com/markdown Episode Source Code: https://github.com/corsego/71-gem-redcarpet/commit/5a45b34b3c56e459d68aa45e196b1d974b5dc0ce Gem Redcarpet: https://github.com/vmg/redcarpet Faker Markdown: https://github.com/faker-ruby/faker/blob/master/doc/default/markdown.md 0:00 What is Redcarpet? 1:40 Add fake markdown text 2:58 simple_format 3:30 Install gem Redcarpet 5:07 Redcarpet Extensions 7:48 Improve the input form 8:20 Style code blocks 10:05 Redcarpet Renderers 14:41 Outro ## Chapters - 0:00 What is Redcarpet? - 1:40 Add fake markdown text - 2:58 simple_format - 3:30 Install gem Redcarpet - 5:07 Redcarpet Extensions - 7:48 Improve the input form - 8:20 Style code blocks - 10:05 Redcarpet Renderers - 14:41 Outro --- # #70: Hotwire: Live form validations and previews - URL: https://superails.com/posts/ruby-on-rails-70-hotwire-live-form-validations-and-previews - Published: 2022-04-04 - Duration: 18:46 - Access: free - Playlists: Just enough Hotwire for Rails developers Get live character counter, validations, content preview while you fill in a form using Hotwire Article: "Live form validations and error rendering. Live markdown preview": https://blog.corsego.com/live-form-validation-errors-markdown-preview Episode source code: https://github.com/corsego/70-live-form-previews/commit/2c7747af6b5326f56035efd565e0705be0febb05 0:00 Solution walkthrough 1:46 Scaffold Tasks 2:30 Validate Tasks 3:16 Add second submit button 6:58 Previewable Object 9:45 No page redirect. TurboStreams 12:50 Stimulus Autosubmit 16:47 Debounce ## Chapters - 0:00 Solution walkthrough - 1:46 Scaffold Tasks - 2:30 Validate Tasks - 3:16 Add second submit button - 6:58 Previewable Object - 9:45 No page redirect. TurboStreams - 12:50 Stimulus Autosubmit - 16:47 Debounce --- # #69 Frames + Streams: Infinite Scroll Pagination (Best approach) - URL: https://superails.com/posts/ruby-on-rails-69-frames-streams-infinite-scroll-pagination-best-approach - Published: 2022-04-01 - Duration: 08:40 - Access: free - Playlists: Just enough Hotwire for Rails developers In the last 2 episodes we implemented Infinite Scroll Pagination with Frames and Streams separately. In this episode we will combine Frames + Streams to create an event simpler way to add Infinite Scroll to a Rails 7 app with Hotwire. Episode source code: https://github.com/corsego/69-infinite-pagination-revised/commit/3018c50d1049cc3f9e8bc5bcf2fb1a84609ea7e1 Pagy Countless: https://ddnexus.github.io/pagy/extras/countless.html#gsc.tab=0 Thanks to https://github.com/secretpray/ for showing me this solution! 0:00 Previous approaches 0:50 Scaffold and seed Artists 1:25 Paginate Artists 2:05 Turbo Frame responds to Turbo Stream 5:15 WORKS! 5:30 Stop scrolling if no more pages 6:32 Summary 7:02 Pagy Countless ## Chapters - 0:00 Previous approaches - 0:50 Scaffold and seed Artists - 1:25 Paginate Artists - 2:05 Turbo Frame responds to Turbo Stream - 5:15 WORKS! - 5:30 Stop scrolling if no more pages - 6:32 Summary - 7:02 Pagy Countless --- # #68 Frames: Infinite Scroll Pagination - URL: https://superails.com/posts/ruby-on-rails-68-frames-infinite-scroll-pagination - Published: 2022-03-22 - Duration: 17:47 - Access: free - Playlists: Just enough Hotwire for Rails developers In this previous episode we added a button to "Load more" records at the bottom of the list, with the help of Hotwire Turbo Streams. Episode Source Code: https://github.com/corsego/68-frames-infinite-pagination/commits/main In the previous episode we automatically added more records to the bottom of the list while scrolling, with the help of Hotwire Turbo Frames. 0:00 Turbo Stream pagination 0:35 Turbo Frame pagination 2:16 Scaffold and Seed Posts 4:48 Basic Pagination 6:12 Lazy Load Turbo Frame 9:47 Display next page 12:50 Keep displaying next pages 14:30 Loading Placeholder 15:22 Autoscroll? No. 15:50 Summary ## Chapters - 0:00 Turbo Stream pagination - 0:35 Turbo Frame pagination - 2:16 Scaffold and Seed Posts - 4:48 Basic Pagination - 6:12 Lazy Load Turbo Frame - 9:47 Display next page - 12:50 Keep displaying next pages - 14:30 Loading Placeholder - 15:22 Autoscroll? No. - 15:50 Summary --- # #67 Streams: Infinite Scroll Pagination - URL: https://superails.com/posts/ruby-on-rails-67-streams-infinite-scroll-pagination - Published: 2022-03-22 - Duration: 18:06 - Access: free - Playlists: Just enough Hotwire for Rails developers In this episode we add a button to "Load more" records at the bottom of the list, with the help of Hotwire Turbo Streams. Episode Source Code: https://github.com/corsego/67-streams-infinite-pagination/commits/main In the NEXT episode we will automatically add more records to the bottom of the list while scrolling, with the help of Hotwire Turbo Frames. --- # #66 StimulusJS: Targets, Values, Classes. Build a PRO dropdown - URL: https://superails.com/posts/ruby-on-rails-66-stimulusjs-targets-values-classes-build-a-pro-dropdown - Published: 2022-03-14 - Duration: 34:30 - Access: free - Topics: stimulusjs - Playlists: Just enough Hotwire for Rails developers In this episode we will learn to leverage StimulusJS values, targets and classes to build advanced controllers like a PRO! Source code: https://github.com/corsego/66-stimulusjs-dropdown/commit/22e6ddf2167e21173daf724ff6aa6455dc29b739 0:00 Intro 1:06 final app demo 1:53 create stimulus controller 33:34 Outro ## Chapters - 0:00 Intro - 1:06 final app demo - 1:53 create stimulus controller - 33:34 Outro --- # #65 Hotwire SPA: Flash Messages - URL: https://superails.com/posts/ruby-on-rails-65-hotwire-spa-flash-messages - Published: 2022-02-21 - Duration: 27:34 - Access: free - Playlists: Just enough Hotwire for Rails developers The right way to render flash messages without page refresh, while mixing Turbo Streams and Stimulus JS. Automatically dismiss flash in 5 seconds. Creating a Single-Page-Application with Hotwire is quite easy! Text guide: https://blog.corsego.com/turbo-hotwire-flash-messages Source code: https://github.com/corsego/65-hotwire-flash/commits/main 0:00 FINAL APP DEMO 0:59 Create app 2:00 Scaffold Posts, Validations 3:28 SimpleCSS 4:15 Flash with page redirect 5:54 Turbo Streams - Create Posts 14:00 Flash without page redirect 19:55 CSS for flash 23:27 StimulusJS auto-remove Flash messages in 5 seconds 25:50 FINAL APP DEMO 2 26:16 Bonus: Render multiple Flash messages 26:57 VERY FINAL APP DEMO ## Chapters - 0:00 FINAL APP DEMO - 0:59 Create app - 2:00 Scaffold Posts, Validations - 3:28 SimpleCSS - 4:15 Flash with page redirect - 5:54 Turbo Streams - Create Posts - 14:00 Flash without page redirect - 19:55 CSS for flash - 23:27 StimulusJS auto-remove Flash messages in 5 seconds - 25:50 FINAL APP DEMO 2 - 26:16 Bonus: Render multiple Flash messages - 26:57 VERY FINAL APP DEMO --- # #64 Cloud9 IDE - Rails 7, Github SSH, Heroku - URL: https://superails.com/posts/ruby-on-rails-64-cloud9-ide-rails-7-github-ssh-heroku - Published: 2022-02-15 - Duration: 24:50 - Access: free Install Rails 7 on Cloud9: https://blog.corsego.com/install-ruby-on-rails-7-on-ubuntu Github SSH: https://blog.corsego.com/aws-cloud9-github-ssh AWS Cloud9 usually costs me under $10/month. Totally worth it. 0:00 Intro 1:50 Create Environment 3:40 Increase Disk Space 5:28 Install Ruby on Rails 7 9:30 Create Postgresql User 10:20 Create Rails 7 app with Tailwind 11:33 Run app on Cloud9 13:10 Procfile.dev 15:40 scaffold posts 16:25 Tailwind 18:20 Github SSH 22:20 Heroku 24:14 Outro ## Chapters - 0:00 Intro - 1:50 Create Environment - 3:40 Increase Disk Space - 5:28 Install Ruby on Rails 7 - 9:30 Create Postgresql User - 10:20 Create Rails 7 app with Tailwind - 11:33 Run app on Cloud9 - 13:10 Procfile.dev - 15:40 scaffold posts - 16:25 Tailwind - 18:20 Github SSH - 22:20 Heroku - 24:14 Outro --- # #63 Hotwire Modals (the right way) - URL: https://superails.com/posts/ruby-on-rails-63-hotwire-modals-the-right-way - Published: 2022-02-13 - Duration: 21:15 - Access: free - Topics: modals - Playlists: Just enough Hotwire for Rails developers ⛔DO NOT use a StimulusJS controller to render TurboStreams!⛔ Extended text guide to this video: https://www.bearer.com/blog/how-to-build-modals-with-hotwire-turbo-frames-stimulusjs * Git repo: https://github.com/corsego/63-hotwire-modals/commits/main * TLDR: https://blog.corsego.com/hotwire-turbo-frame-modals P.S. Feel free to add CSS "p-5 bg-slate-300 absolute z-10 top-10 right-10 rounded-md w-96 break-words" to the modal, so that it shows nicely above page content. 0:00 Intro 0:49 App Demo 2:12 Create New App 3:20 Scaffold Posts 4:44 Turbo Frame Modal 7:40 Add validations... 8:50 No matching turbo frame 9:35 Turbo Stream - to add new post 11:25 StimulusJS - dismiss modal 17:30 Modal to EDIT a post 20:20 Outro ## Chapters - 0:00 Intro - 0:49 App Demo - 2:12 Create New App - 3:20 Scaffold Posts - 4:44 Turbo Frame Modal - 7:40 Add validations... - 8:50 No matching turbo frame - 9:35 Turbo Stream - to add new post - 11:25 StimulusJS - dismiss modal - 17:30 Modal to EDIT a post - 20:20 Outro --- # #62 Live session: User Roles: Different approaches - URL: https://superails.com/posts/ruby-on-rails-62-live-session-user-roles-different-approaches - Published: 2022-01-21 - Duration: 40:57 - Access: free A recording of a workshop I gave. Different approaches to setting user roles Topics covered: 1. post.user_id 2. admin:boolean 3. roles:integer enum 4. roles json hash 5. rolify 6. rolify - resourcify Posts Git repo: https://github.com/corsego/62-workshop-user-roles Resources: * roles json hash: https://blog.corsego.com/user-roles-one-field * rolify 1: https://blog.corsego.com/rolify-edit-roles * rolify 2: https://blog.corsego.com/rolify-scopes --- # #60 Hotwire Turbo Streams Autocomplete Search - URL: https://superails.com/posts/ruby-on-rails-60-hotwire-turbo-streams-autocomplete-search - Published: 2022-01-07 - Duration: 27:28 - Access: free - Playlists: Just enough Hotwire for Rails developers Source code: https://github.com/corsego/60-turbo-autocomplete-search Text guide: https://blog.corsego.com/hotwire-turbo-streams-autocomplete-search --- # #59 Hotwire Turbo Streams CRUD - URL: https://superails.com/posts/ruby-on-rails-59-hotwire-turbo-streams-crud - Published: 2021-11-12 - Duration: 40:56 - Access: free - Playlists: Just enough Hotwire for Rails developers Just Enough Hotwire for Rails Developers with Yaroslav Shmarov | Open Source Every Day TEXT VERSION: https://blog.corsego.com/turbo-hotwire-CRUD GIT REPO: https://github.com/corsego/59-turbo-streams-crud/ 1. CREATE APP: https://github.com/corsego/59-turbo-streams-crud/commit/38e51d378518313d02fa0f691363aec11037dc5d 2. SCAFFOLD "MESSAGES": https://github.com/corsego/59-turbo-streams-crud/commit/70d81e9399f8e204b8a6498d9c69135b88caddad 3. Turbo #1 Re-render form when creating a message: https://github.com/corsego/59-turbo-streams-crud/commit/4516d1777daa963dbc65ba49ff4e9f7c4d3285ef 4. Turbo #2 Add new messages to the list: https://github.com/corsego/59-turbo-streams-crud/commit/9c238c62e1d3be270c674ca7fb82b9ebab52df36 5. Turbo #3 Remove messages: https://github.com/corsego/59-turbo-streams-crud/commit/b03bb338ef36a140338a0d4723bafa719021a808 6. Turbo #4 Edit messages: https://github.com/corsego/59-turbo-streams-crud/commit/f38797b79e0daa82dff2c81047672f6574abca54 7. Turbo #5 Update messages counter: https://github.com/corsego/59-turbo-streams-crud/commit/9468767a9171464b0d50c03dcb9860602196ffbe 8. Turbo #6 Basic flash: https://github.com/corsego/59-turbo-streams-crud/commit/f103be90a5574418dbde4bcc155ef987d29f9b72 ## Transcript So they're going to build a basic application using hot fire and more notably using Turbo and more notably using Turbo Streams. So I think streams are actually the coolest and most powerful feature in uh, uh, turbo. Now, uh, let's uh, start with creating a new rail seven application. Yes, as you see, I'm running Rail seven Alpha and DR three. So I will say Rails and u Let's uh, say, uh, app and database will be post graph SQLK. Let's create the application. And you see it's from input maps install nice and also turbo install stimulus install. So I don't have to bother about the installation of turbo and the stimulus. That's also good. And you see it's also generated at dummy stimulus controller. Anyway, let's navigate with application. As I'm running on cloud nine, I will have an error if I don't go to development RB and say, config hosts equals Neil. Okay, now I can say rails B Great, and start the application rails us. I'll go to preview. Okay, so the application is running loop school. Now I will say get the add. Oh. So let's say get initialized GI papers, GI at all gi commit main create app. Okay, now let's, uh, actually create the, uh, scaffold. I will say, uh, rails generate scaffold, uh, message. And a message will have a body, for example. So body text, okay? And, uh, let's draw this command. Now I will go to our roots file and say that we'll have a root path to messages index. Okay, let's start the server. Okay, run the pending migrations. And we are on messages index. And it is also the root part. So I can go to create a new message, create it. Now, as you see, I can create it with an empty body. So I will add some basic validations. I will go to app models, message validates, uh, body presence. True. Now let's try on scan, going to edit. Okay, so it has to be present looks good. And let's, uh, save our changes. So give status, give, add, all, give, comment, Main messages. Okay, looks good. Now you see the application is looking well, not really beautiful because it doesn't have any kind of, uh, CSS style by default. So to fix this, I will just add a simple CSS uh, CSS framework. So let me run, add this command to our layouts going to views, layouts, application, HTML, adding it, refreshing, and yeah, looks a bit better. So, okay, now we are looking at our messages index page. Now this is a bit misleading. Let's go to the messages page, messages index. Okay, it is message, yes, I don't know why the scaffold generated, didn't pick it up, but it is messages. And we have, uh, well, one message displayed. And first of all, we are going to make it so that we can add messages without page refresh. So how can we do this? Well, we are going to add a form, first of all. So we will display the form to add a message on the index page. Let's do this. I will say equals render partial shell, uh, messages form and locales will be message, message and closing the braces. And let's see if it is done. Okay, so we have a field for body. I can press create message. If it doesn't fail, I'm redirected to the, uh, edit page with the, uh, notice that the body can get blank. If I, uh, add a message successfully, I'm redirected to the message message. Now, I will override this to behaviors with, uh, our turbo. So first of all, what do I want to do? If a message is, uh, successfully created, then I will, uh, re-render this form here. And if I have, uh, an error. So if, uh, I tried to create a message without any text, for example, this error is going to also be rendered here inside the messages index page. And I'm going to easily do it with the turbo. So I'm going to go to our controllers messages controller, and you see, uh, we can respond with the a format named Turbo Stream. So here we have format html, format J by default in a scaffold, but we can also say format dot turbo stream. Now, I'll put it in a do block. So if you go to the Turbo Handbook, uh, we will have examples of, uh, responding form Turbo Stream, and they're going to render something inside. Anyway, let's try to do it on our own. I will say form Turbo Stream, I will say render Turbo Stream. And uh, next I will say, uh, turbo Stream dot update. And we need to update something. Well, the point of turbo stream is to update, uh, some kind of element, some kind of, uh, H TM L element with an ID on a page. So I will say element ID with some kind of HTML or with some kind of partial locales. So they'll have an element here. Let's just have it element ID for now. Then we'll pass in the, the partial shell that they're going to send. So the partial shell is going to be, uh, messages and what are the locals going to be? So locales are going to be, uh, if they save the message, then we want to, to send a new message form. It'll be message equals message new. Okay? And now this element id, well let's, uh, give an ID to this, uh, partial right here. They're rendering a post shell messages form and they want it to be replaced with a turbo stream. So turbo stream is going to send the HTML of this partial with this locales into our HTML view for index. And it's going to replace this. Now, it must replace some kind of element id. So let's give it an id. I will say diviv id. Uh, new message. I will wrap this all into ad. Let me just, uh, refresh the page. I am going to messages. I will go to inspect element. And um, there is it, you see, uh, I've wrapped it into this ID new message. And now I'll go to our messages controller. And here in this Turbo Stream update, I'm going to say update the HTML, uh, element on the page that, uh, is it sent to with a partial messages form and these locales. So let's see this in action. Now I am, uh, well, I can refresh or I don't actually have to. I'm going to create, uh, a message and you see it has disappeared. Now, what has happened, if I go to the logs, I will see that and message has actually been created. So if I refresh, the message will be added to the list. But uh, this form basically gets rerun. So, uh, only this part of the HTML page gets, uh, r rendered. I might be able to see it, uh, somewhere in the network, I guess. Let me once again add something. And you see I get this messages and I get this stream action update. Target new message and template is going to be the form. So the messages form with the correct, uh, locales. So you see, uh, turbo Stream sends, uh, this, uh, HTML with a turbo stream action update right into this place. And what we've done, we've, uh, rended the form, we've rended the form with the, the locals message message new if we have saved the message. So if it was saved correctly, but what if I press create message and there is no content, I press create message, and you see, I just get, body can be blank. So what do I get? I get this render new status, unprocessable entry. And what I want to get, I want to actually have this form re rendered with the locales message is going to be the message that we are working on at the moment. And they're not going to have any page refresh. So see, we will redirect the new action. And I don't want to be redirected. So I'm going to just copy this formative stream into the L statement and I'm going to say, so turbo stream update, new message, partial messages, form and locales message is going to be then message that, uh, was, uh, rendered invalid. So it is going be this message at message, okay, let me actually also beautify this a bit so that it is also more readable, Okay? Just like it. And now I will once again try to create a message. So I press create message. And you see there was no full page refresh. I sent a request to the server. The server went to the create action, it evaluated the message, it understood that it cannot save the message, and it sent it to robust stream with the, the messages form to the target new message. And with the locales, with this current message that we have working with. And once again, I will refresh, I will go to inspect element and go to network. I press create message, I go to messages. And here you see I get the, this HTML, the form, and I have this errors rendered so body can be blank. So if I go to the form messages form, you see this HTML was run with the correct uh, uh, error message. So this is the basic principle of uh, how, uh, of how turbo transfer. Now let's actually save, uh, our changes we've done already. Advanced small theme, we can, uh, create a message with the, the form being re rendered so GI at all. Now, what did it do in application HTMO? Okay, I just added the style sheets. So GI main, uh, ring render Form, event grading message. Okay, and now let's go back to our application. So, uh, it is far from being complete. You see, I will try to add a new message like hello world. And you see it is not added to the list. So why is it is it not added to the list? Because I need to refresh the page, but it's not the approach that we want to have. We are going to have turbo actually send messages directly into this page. It's going to inject these messages on top on the bottom of the list. So first of all, let's actually just add some ordering to our messages. I will go to our messages controller and go to the index action. Let's say messages dot order, rate it at the send. So the newest ones are going to be on top, okay? And now going to the index page, we have an ID messages already generate by a scaffold and here have a rendering arm messages, okay? And everything that they're going to do now is when a message is created, they're going to have another tool stream that will, uh, add the new message, uh, to this, uh, id. So how is it going to work? I'm just going to cope with this. I'll put a coma so that the syntax works. And you see here I had update. So update is kind of replacing one element with another, but keeping the same id. And uh, here I'm going to update it with the, the a partial messages that, uh, messages message. So it's going to be this partial, the actual message content that, uh, we have sent partial messages, message, and locals is going to be this message. So I will say at message, locals at message, so what am I doing here? Oh, actually not update, but they're going to say prepend. Now if we go to the Turbo handbook, uh, it'll be said that there are a lot of different actions. So there is append, there is prepend, there is replace update and so on. So we'll come to the other ones later. Anyway, this say turbo stream prepend meaning add on top of the list, the list is going to be messages that I have these messages from. I have it from index ID messages. Okay, so add on top of, uh, this id, it'll be added somewhere here. And they're going to send this special message with locales of this message that has just been created. Now again, I can uh, try and create the and new message, press create. And you see actual Turbo did two things. So it uh, updated this form and it added a message on top of the messages list. So works fantastic, doesn't it? Now, what if we had to stream append, let's say to stream append as we have here, action at end. So, uh, append will basically add an element to the bottom of the list. Let's just see how it works. I will say I am appending great. And you see it was added to the bottom of the list and you decide what you want to use based on your particular application needs. So, uh, I usually use, uh, uh, prepend more often anyway. They are running two turbo actions at the same time from the same, uh, great action. So again, I can uh, go to the network, going to inspect element, let's see network, I say hello world, great message. And if I look here, you see the turbo stream action is update. And also somewhere there must be that we are, uh, sending the second turbo stream to append or to Prepend. Yeah, here it is. So Here is the second turbo stream action prepend. And here is the message partial. You see in this, uh, from this controller, I, we send, uh, two different, uh, things that we want to replace. We replace two different, uh, DS with ideas. So kind of to ideas. Here's one and here's the second. And looks cool again, if I just press create message to get the error without refreshing the page. So I think it looks really great. Now let's, uh, save our changes. So get status, get add all GI comment, name, add messages to the list. Okay, looks great. And what if you want to delete a message from the list about how to delete go? Let's see, I would do maybe want to add a delete button. So let's go and see if we already have delete button inside the template. So the show template, we have this button to destroy message. Let's scope it and add it to our message right here. So we have have a link to show and we'll also have a link to destroy. Now I will update the local. So message po message method, delete. Looks good. Okay, now what will happen if I just press delete, if I press delete? You see we have a page refresh. So I press destroy once again and well, these are all page refreshes. Now if, uh, you want to be certain I can just add add timer to our application, h ml dot rb. So I would say something like, uh, click files, time do zone now. Okay, so I press Destroy and you see the time updates. So we are actually having a page of fresh at the moment. And uh, to do it without a page of fresh, we would want to do it with turbo. So turbo Stream would have an action, uh, of uh, delete, I guess of remove. And it would find an element that an ID and remove it from the H TM L that we have rendered from. So from this H TM L index page. Okay, now let's add a few messages and we will go to our messages controller to the destroy action and have a going to also respond to format turbo stream. So format, turbo stream and we can say render Turbo Stream and Dobo Stream Remove. And we will say at message Now by saying at message here, it's uh, where do we know that this is going to be this message? Well, if we look at the HTML, I go to inspect element. You see, uh, each message has a unique ID here in the HTML by this, each message have a unique id because in the default scaffold generator, you see we have this DOM ID message and the what this DOM ID team does, it creates an ID like message and the ID of this message. So each message in our list has a unique id and this is really great and out of the box. And if I go to our messages controller, we have this remove message. And how does, uh, rails know that we're removing the message with this id? Why don't we say something like, uh, uh, message and embraces it would have message id. Well, both things, uh, would work, but uh, turbo just knows that we would want to use this message, uh, Dom id, so we can just have add message. But if you added message, message id, then it would also work. Let's, uh, yeah, we can actually check both just in case. So, um, I will have at message here and let's see. So when I press the destroy button, I expect no page refresh because we will respond to the format of Turbo Stream and I expect the turbo stream to remove this message from the message list. So I will refresh, let's open network just to see if there are any changes. Let's see if, uh, we have a page refresh. So pay attention at the time and I press destroy. And you see there is no page refresh, but elements are being removed from the list. If I refresh the page, you see the elements will total deleted. So it works. Elements, display works. Now you see out a page refresh, they can add messages, they can destroy messages, they can render errors, then creating the messages. So it works so well fantastically. And we then tried a single additional line of JavaScript. Now let's just try this other option that this, uh, id. So just gen generating, well, the ID in the this way just in case. So I will press destroy and you see it works the same way. So you can use both, but uh, this one is more explicit and this one is better looking. So anyway, it, uh, is working well. And let us save our changes, get status, get, uh, add all. Now, what did the change inside the application? Okay, we added the time. Now get go main, uh, remove messages and valve works perfectly. Okay, now what if you want to go a step further and do something more complex? What if you want to actually edit messages without a pager crush? Now the perfect candidate for the job would be to use turbo frames, but now we are going to accomplish this thing with turbo streams. So we would do want to have an added button. First of all, we want to be able to edit each message. So I'm going to go to the message partial and going to add a link to edit. I'm going to go it from here. So edit this message. Okay, let's refresh. I will update the locale. Okay, so we have the button to edit, and if I press edit, they're directed to a new page. Now, uh, how can we make it not re direct us to a new page? How can we, well run the turbo stream. Let's, uh, just go back to the list Of the rabbi now. So going back to messages, okay, uh, we are going to respond with the format of, uh, turbo stream on the added action. So going to the messages controller, added action, and let's say respond to to format, format, Tubo stream. And, and, and inside this format, Tobo Stream, we'll say, uh, render Tubo stream Tub Stream dot update. And we are going to update this message that the p show, uh, messages message and the locales, uh, are going to be message will be the message that they already have. So message now, uh, updating the syntex, okay? So we are responding with turbo stream. Now, I don't like writing this long lines, I don't, but I say do so that it doesn't look that long and put an end statement just like Okay? So we want to respond with the format of turbo stream and replace the message with the edit form. Oh yeah, not with the message, but with the form. So partial messages form locales message. So they want to be able to render the form with the content of this message. Now, if I press edit this message, what do I got? I get action controller unknown format because by the whole design of turbo streams and turbo frames. Uh, so turbo streams can respond to any request except a GA request. So streams can respond to patch, post, destroy, whatever, and the tub frames can respond only to GA requests. So streams to anything except to GA and frames only to ga. And now they're going to kind of play, uh, a work around. So we want to accomplish this particular thing using tobo streams. So, and Tobo streams cannot respond to a GA request. And uh, this edit is a GA request. So if I go to Roots, uh, edit message path, you see it is, uh, a get request. So I'm going to say that it should also respond to, for example, uh, post, yeah, for example, post Let's, uh, go to our roots and have have resources messages, I will say do, then I will say member do, 'cause they're going to have an ID and they'll say, uh, post edit. So it's just going to also give us a post request to edit. Now if I refresh, uh, edit message path, and you see it is now a post request. Now again, I'm saying this is a bit hacky and unusual, but I'm just showing how you can, uh, overcome some natural barriers and two things through box strings. So they're going to respond with post. Now, going back, I will refresh, I'll address edit message, okay? And it still says unknown format. Now, uh, what am I doing wrong? It says unknown format because possibly I am going with a link to, I should actually say method post in this link. So in edit message, I will, uh, add method, uh, post. Now going back refreshing, press an edit message. It still doesn't work. It is because, uh, we are not using rails UGS anymore. And Rails UGS is what was given our links to possibility to use something like method post or method delete. So we are going to have to use a button to, I'm going to add a button to edit this message. Now I will, uh, refresh. So you have button two, edit message. Let's press, uh, edit. Okay, this is still not working, but it's not working because I was added in the wrong template. So you see I'm in the show view and I wanna do it in the partial, okay? So anyway, going uh, to the partial, actually we will not need to show you. I will remove it just for us not to bother about it again, okay? And here in this uh, link to edit message will be replaced with this bottom two edit message. And now this method post convert, I will refresh. Okay, I will say message. And here we have a button to destroy and to edit. Now I'll press edit. And you see, uh, it was re rendered. So we have rendered the form. Now I can go to the other message. And you see it also renders the form for the other message. So seems to be kind of welcome, but if we update something, so I will say hello, update message. What happened? There was a page refresh. So you see we have this notification, the time has changed and the, the other form was removed and we are now going to do it without page refresh. So I will go to our messages controller, I will uh, go with this and I will go to our update action. And we are going to also respond with format to stream in the the successful case and note in the successful case. So first of all, I will say to stream update the message. Partial is going to be message, message. The locals are going to be message and if, uh, it doesn't manage to update. So if there are errors we should render not the partial there should render that for. So I will do the same but render form. Okay? So let's see if it works. Now here's the time I go and press edit message. Hello world. I will also open editing the second message. Hello Yaro. I'll press update. You see it was updated. I press update on the other and there was no page refresh. Now I can refresh the page and you see, well all the changes are persisted. So wow, it works. We managed to edit our messages also using toolbox streams without a single page refresh. Really amazing in my opinion. So actually we did quite all the work and did one thing that can seem a bit hacky. So we had this, uh, get method to edit a message, but we replaced it with a post. So it works. Now let's save our changes, get status, get add all, get com main. And what we do, we can edit messages. Okay, looks good. And going even further to the next level, what if we want to see an updated quantity of messages? So we will see how many messages we have on the page. Well, usually I would just go to our index page. I would have messages, I would have, uh, equals message account. For example, I refresh and you see I'm adding new messages, but the count doesn't increase until I refresh the page and we can do it also with turbo. So in what case it says can we, does the message count change if we destroy a message or if we add a new message? Well, they're going to add a few more turbo actions. So I'm going to go to our messages controller and to the create action. And in case of, uh, a successful message save, I'm going to also, uh, say that we want to replace this particular, uh, count with and new counter. So I will say, uh, to stream the update, then we'll need some kind of id I will say message counter. And we don't need a partial, we don't need locales. We will just stream some HTML. So I will say HTML. And inside I will say something for example like, uh, uh, inbox.com. And now I will close the braces and put a coma here and it might work. Then I add this, uh, id. So I'm going to index page and here I will put this into a diviv id. I will say div ID equals message counter. Just like this. I refresh. Now you see this on a new line. So I will use spam instead of div refresh. Okay, messages five. Now I add some message and I get an error by, do I get an error? Okay, because I used the word in inbox, doesn't matter. Press messages, count again, and you see the calendar is updated. And I didn't have to create any HTML template for this. So I don't have to run the and HTML template. I can just run some HTML. Let's actually try doing the same. But without these uh, braces, let's just say HTML message count. Will it work or not? I don't know. I try and yeah, it works even simpler. Even better. Fantastic will work without the verb. HTML. Let's bend the line. So, and it's very amazing. Okay, looks good. And now let's do add the same, uh, action in case we delete a message. So you see now when we increment the account, it increases, but when we destroy, it doesn't increase only when I reload the page. So let's go to our destroy action. Here we have format to stream and uh, I will change the syntax, I will say dual And then run the to stream. I will add these prices so that we can run the multiple streams to stream, remove message, and to stream, update the message counter. And I'll put a coma between and it should kind of work. So let's try. I'm adding a message. I press destroy and it works. I try to edit the message. Uh, hello and it's working. Amazing. So now you see we also update the message counter books like clockwork. Fantastic. Get status, get add, all you commit main, uh, update messages counter. Fantastic. Now is there anything else that, uh, we can do right here? Actually see when the add message or something, we don't have any kind of flash notification that, uh, they've done it. So, uh, what if you want to have some kind of notifications on the same page without a page refresh that some action has uh, happened. Now this also an interesting case. Um, There's actually a quite easy way we can solve it. Easy and elegant. So, uh, let's, uh, just go to this page and somewhere on this page, yeah, we already actually have by fold this Id notice. Okay? And let's try to see how it works. So we have this, ID notice it is in a big bag. Okay, so we want to update this notice with the something else. Now, by default, we have nothing in this. Notice it is empty. And uh, yeah, they're going to replace this notice with something else. Let's, uh, go to our messages controller and say, uh, then we create a message. We'll, uh, also update the notice, sorry, this notice with the something let's say, uh, message created. We'll put a coma and I now refreshing. Great. And you see we have this message created. We can also add an ad or something. So message, message ID created. Okay, now I'm refreshing. And you see we have the message Id also visible and we can add this kind of, uh, uh, to stream to all other actions. So going to the update action, we will also have, uh, run to stream. They're going to render two streams. So update this partial and they will also update the notice. And the notice will be updated with message updated. Let's see how it works. I will, uh, press create message. I will edit this message. And you see we have this updated. I go and edit another message. And you see it was updated once again. And we can do the same for our destroy action. So going to our destroy action, we will toolbox string update notice with message deleted. Let's see, I delete a message and you see we have this message deleted notification. Okay, so it's broken. Fantastic. Let's again save our changes IT status gi add all GI, main and basic slash Okay, looks cool. And now what have we done in this, uh, small basic application? We've made it so that the, we can using just one page, so it is a single page application. SPA, we can create messages. You see there is no page refresh. We can create messages. The county has been updated. The, uh, flash is been updated. We can edit messages, we can edit multiple messages at the same time. We can destroy messages. And this all happening without any JavaScript and without any page refreshes. And for this, we're just leveraging the one simple functionality of turbo, the turbo streams. So, well, that's it for today. Thanks for being with me and have fun coding. --- # #2 Production! Let's build a User Feedback app with Rails 7 - URL: https://superails.com/posts/2-production-let-s-build-a-user-feedback-app-with-rails-7 - Published: 2021-11-08 - Duration: 03:45:01 - Access: free - Playlists: Build a Q&A app - I'll attempt to build and launch an app in 10 dev hours. User Feedback App (QnA) (askdemos.com): - hosts can create "inboxes" - users can add "messages" to "inboxes" - users can vote on "messages" Tech stack: - Rails 7 alpha - Hotwire (Turbo + StimulusJS) - ViewComponent? Source code: https://github.com/yshmarov/askdemos KANBAN board: https://github.com/yshmarov/askdemos/projects/1 --- # #1 Essentials! Let's build a User Feedback app with Rails 7 - URL: https://superails.com/posts/1-essentials-let-s-build-a-user-feedback-app-with-rails-7 - Published: 2021-11-07 - Duration: 03:21:16 - Access: free - Playlists: Build a Q&A app - I'll attempt to build and launch an app in 10 dev hours. User Feedback App (QnA) (askdemos.com): - hosts can create "inboxes" - users can add "messages" to "inboxes" - users can vote on "messages" Tech stack: - Rails 7 alpha - Hotwire (Turbo + StimulusJS) - ViewComponent? Source code: https://github.com/yshmarov/askdemos KANBAN board: https://github.com/yshmarov/askdemos/projects/1 --- # #58 Nested Resources - URL: https://superails.com/posts/ruby-on-rails-58-nested-resources - Published: 2021-10-18 - Duration: 17:58 - Access: free Episode source code: https://github.com/corsego/58-nested-resources/commit/9885432f93f1c5af100e10edc57d8f3358527738 Official docs: https://guides.rubyonrails.org/routing.html#nested-resources ## Transcript Hello. So in this episode we're going to talk about nested resources. It's basically nesting tasks inside projects or nesting messages inside an inbox. So nesting, uh, one resource that is logically connected to another resource inside in our roots, in our controllers and in our views. And uh, here's an example of message resources in the visual guides. And they're going to, to try to do it in the action in the Real Rails app. Now you see I'm already going to try to use RO and Rail seven Alpha, so I think it's not going to really matter in this specific application that we are going to build with message resources, but in the future it's going to be fun to see what works and what doesn't work in Rails seven Alpha. So let's create a new Rails app. I'll say Rails new. Let's name the app like Twitter database, postgre sq, it's been created and uh, yeah, it's run Input Map install instead of uh, web pack install instead of young. Also installed Turbo. Let's see, installed, uh, turbo stimulus all out of the box. Okay, looks nice. So, uh, let's navigate to the application, run rails to B great And try opening the application. So I'll go to preview, preview run application. Now I get this error because I'm on cloud nine. So I will stop the server and go to the application config environments Development RB config hosts equals meal restart and it should be broken. Okay, so here's the new Rails app. I will uh, actually commit to gi. So GI at all GI commit main create app. And now I'm going to scaffold inboxes and messages. So an inbox can have many messages. I'll say rails generate scaffold inbox, uh, title and that's it gi. And we are going to scaffold message title and it'll be referenced to an inbox. So inbox references. Okay, now rails TB migrate. And let's again comment this to Git gi add all GI commit main, uh, scaffold, inboxes and messages. Okay. And now we can start the real server and we can start making the nesting work. So what is this all going to be about? Basically heavy, have our inboxes our regular route, we can create an inbox and to be able to see the messages that are related to this inbox, we would maybe want to navigate to inbox one slash messages and to create a new message for this inbox. We would maybe want to navigate to inbox one messages new. So how are we going to make this work? Well, first of all, of course we are going to add the, the associations go to our models inbox. RB has many messages and of course inside our messages you can say belongs to inbox, but it is done by default when they run the scaffold with references. Okay, so next we're going to go to our route. Let's have a look at our roots. Here they are. We have our messages that are at just slash messages without inbox ID slash messages. And we have our inboxes. And let's have a look at our roots, uh, file. You see we have resources, inboxes and resources messages, and they're going to nest messages inside inboxes. So we'll say resources, inboxes, do resources, messages. Let's uh, go back to our roots and see what changes. I'll go to a new roots page. And here we have inbox messages part, so not messages part, but inbox messages part. You see all the route for all messages are now nested inside our inbox. And let's see how it's going to work. So you see I go to inbox one messages for example, and you see we get this undefined local variable new message path. Why is it so? Because new message path does not exist anymore. Instead we have new inbox message path, you see, so it's kind of nested. Now let's see if uh, it actually works. I will uh, go to our views, update the index view. So go into our index. It'll say new inbox, message path, and we can pass in the inbox. Oh, actually it worked. And you see by default we have inboxes one messages new. Now if we go to our inboxes, let's just create a second inbox and navigate messages. You see it already sees inboxes two messages new. So the path works and navigate another error in our form build. We have and define method messages part because uh, we're trying to build the form for going to slash messages post, but we need to go slash uh, uh, inboxes ID messages post. So let's go to our messages form builder and we'll have phone with model. And here we'll need to pass in our current uh, inbox and uh, the new message object that is this one. So we will say inbox first, for example. Goma message new. Let's see. And it seemed to have worked. And here we also have this back to messages, messages path. So this one isn't working either. Now what are we going to do with this? Let's actually do it the right way here. Instead of just getting inboxes first we are going to find the inbox in the controller by the PERS id. So I will say add inbox. And you see we have no add inbox. So we are going to go to our messages controller controllers, messages controller. And here we're going to go to private methods and say Dev set inbox. And we will say at inbox equals inbox find, uh, grams inbox id. So this first is ID, is going to be the inbox id. And if you have messages slash two for example, or three here is just going to be the id. So we are finding message by ID and inbox by inbox id. Okay, and let's say that we will set inbox on the very top. So before action set inbox for all the actions inside the messages controller. Okay, let's refresh. Okay, so the form was built, but we have a further error in our messages. New partial uh, new template back to messages isn't available. So instead of messages portal, we have to use once again something like inbox messages spot. Let's go to our new page. And here we have messages spot and we would have inbox messages, spot and pass in the inbox. Let's save it works. So the new message view works. I can press back to messages and I will be directed to inbox slash two slash messages. Okay, let's go to create a message. And actually we don't want to be able to hard code an inbox ID here. This should make it, uh, automatically the inbox ID that uh, we are browsing, uh, uh, in the URL. So we are going to remove this from the form. So no more inbox, uh, ID input in the form. And uh, let's try to create a press create and inbox must exist. So we must add the inbox in the controller. I'm going to go to the messages controller. And here actually I'm going to say not just new, but uh, I will say at, uh, message equals@inbox.messages.build instead of just message new. So here we're going to initiate the inbox. Let's see if it works. I'll go back to messages. New message. Great message. It still doesn't work. So next we are going to go to our create action. And yeah, first we'll also remove this inbox ID from the message PERS user should not be able to pass it in. And we'll go to the create action. And here we will say add message equals At inbox. Do messages, build message brands just like this and let's see if it works. So once again, I'm going to create a message, create message. And you see we seem to have saved the message, but we get already the another error and define method messages. URL. Again, we should redirect to the, uh, at inbox at message. Let's see if it works. I will resubmit. So going to new message, create in the message. And okay, so now we seem to have submitted it. And uh, in the message post, we have no link to show message. So here again, we need to add the, the new path. I'm going to go to the post. And here we have at inbox coma message. You see it's uh, kind of, uh, troublesome and takes, uh, some time to adjust. All the code of the messages that are going to be inbox, uh, are going to be inside an inbox spot, usually worth it. Okay? So again, I'll try to create the message. Press create, uh, I still have something wrong in messages show here. It's going to be edit message path at inbox, at message. Actually you can say not at inbox, but you can just say at message inbox. But I think it's more correct to say, um, at inbox, but it doesn't really matter in this particular case. And here, button to Destroy will say at inbox, at message. And here, back to messages. We'll say, uh, something like, uh, at inbox Goma messages, I think this should work. So it would, should lead to the inbox messages path. Let's see if it works. Again, resubmitting great inbox. Okay, this path doesn't seem to be broken. And define method, edit, message path, edit inbox, message path. Sure, Resubmitting again and it failed. Okay, let's show this message. Let's, uh, go back to messages. You see it leads us to inbox two messages. Let's try destroying a message and we still get an error and define method messages, URL. So again, if we need to go to the messages controller and uh, say that if we destroy a message, it should redirect us to something like inbox messages. Let's see. Okay, this message doesn't exist anymore. I'll go to inbox two slash messages. Uh, I will destroy this message. And you see they're directed back to the right path. And I think we still need to do our updating. So, uh, here we should direct, direct, not to the message, but to the at inbox coma message. Okay, and now let's, uh, try going to, let's say inbox one messages. And you see, we see the same messages. So in inbox one, we don't want to see the same messages, we want to see all the messages that are relevant to this inbox. For this, we're going to go to our index action and say messages equals at inbox messages just like this. Let's see. Okay, in this post inbox, we don't have any messages. So let's add one message for first inbox back to messages so the Nest inbox correctly. And actually we can also do something similar inside our set message. So instead of having the message message find pers id, we can uh, say message equals inbox messages, find per id. So this way actually it should potentially search for the messages a bit faster because they're searching not between all the messages that they have in the application, but only between the ones, among the ones that are relevant to this inbox. So the search scope is, uh, a bit smaller if you have like thousands of messages I guess. Okay, so, uh, seems to look fine. And this is basically how you can add the message resources. But, uh, let's go another step, uh, further, you see in our views folder we have inbox and messages and also in our controllers we have our inboxes controller and messages controller. And logically, uh, it'll be good to also nest them in the views. So let's say we'll want to move the folder of messages into inboxes. Let's do it like this. Okay, uh, I guess yes to all. Now if I refresh, you see no template. So the controller doesn't know that it needs to go to this template. Now we could actually potentially just, uh, say in each action that we want to redirect to a template that is nested, but it would be better for us to also move our controller into a folder. So I would say new folder inboxes move the messages controller into inboxes. And here, will it work? No, it still doesn't work. So we are going to say that it is going to be in the module inboxes. So we are just saying that it's kind of in the this folder and that, uh, the messages are in the inboxes folder in both views and controllers. So let's refresh. It Still doesn't fe So sidebar says that, uh, it didn't manage to find the path. It is because we need to go to our messages controller and say that, uh, it inherits from the module inboxes. So inboxes, messages, controller. Now I go back and it seems to be working. So let's just go back and say inboxes. I will create a third inbox. Okay, now I'll go to the messages, I'll create a first message for the third inbox, I'll Mm. Let's go back to messages. New message. I'll try updating. Okay, here in update, I didn't, uh, change the links. So I'll go to edit here will have, uh, show this message. So in messages edit, let's go to messages, edit. We have show this message. It'll say at inbox, at message. And, uh, inbox messages pop, uh, at inbox. Let's see if it works. Update and ITFers. So this is basically how you can use message resources in the Rub rails application. Now at first glance, it can seem like a lot of stress on updating all the scaffolds, but again, usually when working in a Ruby Rails application, you don't really do everything by scaffolding. So I really had to update each of the views and update the links to work, uh, inside these message resources. But usually it helps you keep the logic of your whole application, uh, in, uh, intact. And you'll always know that messages belong to inboxes and you'll know that you can access them both in controllers and in views inside your inboxes. And it is easier to scope the messages. It's easy to find the inbox ID for each message at the glance. And you have the nice, uh, logically correct URL. So that's basically it. This is how you can really use message resources in, uh, your views in Rails application. So thanks for being with me and have fun coding. --- # #57 Database Architecture 101: Building an ERD (Entity Relationship Diagram) for UberEats clone - URL: https://superails.com/posts/57-database-architecture-101-building-an-erd-entity-relationship-diagram-for-ubereats-clone - Published: 2021-10-10 - Duration: 44:58 - Access: free In this episode I'm looking and thinking aloud what the database for a restaurant management & delivery app can look like. Link to the diagram: https://dbdiagram.io/d/614cd4f2825b5b014610c1de ## Transcript Hello. So today's episode will have nothing to do with rebound rails. Basically, a friend of mine sent me a Figma design sketch of an application that he wants to build. It is a restaurant menu ordering and delivery application. And, uh, I'm going to build an entity relationships diagram for this application based on the sketches. So I'm going to build out the tables, the attributes of the tables and the relationships between them. And to do this, I found the, I feel like couple minutes of Googling, I found this, uh, online tool, db diagram dot I owe. And I think it looks good. And the cool thing is that I can, uh, uh, type the words here and, uh, it'll be displayed, uh, as an image. So I don't have to draw anything actually myself. I can just drag if I don't like something. Okay, so, uh, here's the design sketch, and, uh, I guess I'm going to start. So let's look at the first screen. Okay, I see that we have, uh, internationalization. So we'll have to have different languages log in, shopping cart. And, uh, here's the menu. So we have menu items and uh, uh, categories. Okay, so let's start building. They're going to have, uh, uh, menu categories, And they're going to have, uh, menu items. Now, a menu category is going to have, uh, an id, a name and a menu item is going to belong to a menu category. Okay? So menu category has many menu items. Let's see. Yeah, looks like this. Okay. And a menu item, it'll have a name, a description, and the price. So name, uh, string, uh, description, text, and the price integer. Okay? And it'll be not parent id, it'll be menu category id. Okay, so we have a menu category and the menu item. Uh, next, let's see what else we have. So nothing else on that page. Okay? We have user sign in. So we're going to have users, like device users, I guess. So we'll have a new table ID in, uh, email stream, whatever user. Okay. What do we have next? Okay, a few options that can be visible after sign in. So these are adjust the pages, like the sign in pages doesn't matter much. Okay? Just the devise, uh, page stylings doesn't matter much. Okay? After sign in, repeat your last order. So a user will be able to have, uh, many orders, and he has a shopping card. So we will need an order model. So we'll have a table order ID in, uh, user ID int and a reference user id. Okay, so an order belongs to a user, makes sense, and, uh, an order. We'll be able to have, uh, many other items. You see, we have other items. So an order can have, uh, different menu items, and it'll have like many menu items as other items. So we'll create the NU table, table order item ID in. It'll belong to an order and to a menu item. So order ID and menu item.id. Okay, now it's becoming a bit twisted. So we are going to move this around a bit. So a user has an order, a menu category has a menu item, and an order has many other items, something like this. Okay, what is next? So there are the prices and there is the total amount of the order. So, um, and order will have a total amount of total price. That'll be an integer. And again, the prices of menu items can change, uh, in the future. So inside each order, we need to save, uh, the exact price the menu item had at the moment of ordering. So, uh, we'll also add the price into the order item. Okay, so we are going to go to our order item and, uh, add the, uh, menu item price. I think if we can just go with the price integer. Okay, so a user has an order, an order has many order items. Uh, an order item is basically a menu item, uh, saved for the order, and a menu item belongs to a menu category, okay? Looks quite simple for now. Then we have the total price. And here is we have these different options. So plus vegetable, milk plus sauce, and so on. So our order items or our menu items can have some additional flavors, and we might want to add them later on. Here. Again, this is something connected with flavors. So, uh, one option costs 85, another costs 89. I think we'll have to dig into this later on, or it might be visible somewhere here. Okay, I think we've gone through the first, uh, design flow. And here's the order flow. Let's see. Okay, here are some options, uh, that can be visible possible when they hover on a menu item. So here we see actually the ingredients. Here are the ingredients. So a menu item can have, uh, many ingredients. So we would have an ingredients list and, uh, a joint table between the menu item and ingredients. Okay? Now it's becoming more complex. So we'll add a table ingredients, ID integer, uh, name string, and our menu item will have, uh, many ingredients through ingredient items. All this can actually be, uh, recipe receipt. So I will create table, uh, in menu item ingredients ID integer, or actually, this one might not need NID because it'll be just joint table. And we would have, uh, ingredient ID integer and how we type it here, ref ingredient id. Okay? So, uh, an ingredient can belong to many menu item ingredients. And, uh, a menu item will also be able to have many menu item ingredients. So we'll have menu item ID and menu item id. Okay, so here we have, uh, a joint table starting to look quite complex. Now, um, let's just move this up here so that it looks a bit nicer. Okay? So many items can have many ingredients through many item ingredients. And you see we have these, uh, flavors. Now some can be, uh, added as multiple. So we can add multiple check boxes, and in some cases it'll be either or. And we can also add quantity. So we would add the quantity to this order items. So whether we have order items, we would have price and quantity. Okay, so we can add many menu items as an order item. And again, we have these, uh, additional attributes. I think we'll see more about them later, but we have to remember this. Okay, so what do we have here? Uh, select an item, choose additional attributes and add to card. Okay? So an item will be able to have, uh, different kinds of attributes below this. Let's keep going. Preview your order in your card. Okay? Again, what do we have here? We have items, quantities, and price, and total price. And this total price will be calculated based on the, uh, price of each other item multiplied by its quantity. Actually here, for it to be easier, we can also add something like, uh, total price of this order item. Well, it can be done later on. Uh, let's see. So we have two flows. Click on order for not signed end user. Okay, so apparently these, uh, orders can exist without a user being signed in. So a user ID would not be obligatory, and we could store this either in the session or actually create an order. And later on, when a user creates an account, assign it to the user, okay? And you have delivery addresses. So, um, a user would have a delivery address or possibly many delivery addresses. Now, many delivery addresses sounds, uh, uh, like it makes more sense. So we'll create an additional table delivery address ID in. And what is it going to have? So it'll have a first name, a last name, a phone number. Uh, yeah. So a user can actually have many delivery addresses. And the data about the, that delivery can be different from the user. So one user can, uh, have one delivered to one username or to another username. So let's say I deliver address will have a first name string, uh, last name string. Then, uh, phone number string, I guess, uh, then street. Then um, house number. Actually this could just be address line, 1, 2, 3, 4, um, entrance number, apartment number, and apartment. Okay. And create an account for easy ordering. So this is already just some controller action. And what else do we have here? So you deliver address has been saved. Yeah, so delivery address belongs to a user. So it'll have user ID int and reference user id. Okay, something like this. Now, it doesn't look very beautiful, but that's, uh, okay, we can move it here, for example. Okay, what do we have next? So we have the same in once again. So this is just the action flows, and we are modeling that database for now. We are not modeling the views and the controllers yet. Okay, here is your order. So, uh, an order can have a delivery address. Uh, a user can have many delivery addresses, and we can tie a specific, uh, delivery address to a specific, uh, order. So we would say, uh, inside our order table, we would have delivery address. Id, ID, okay, something like this. So a deliver address, uh, can actually have many orders. Yeah, it's normal because we can, well order the same, uh, stuff to the same address many times. Okay? Then again, quantity price, total price. And we have something like discount code. Okay? Then we have, uh, some data about the delivery person. So, uh, actually it'll be delivered to, yeah, so it's not about the delivery person, and we can say order now or order for later. This is also interesting. So order for now or order for later. So how do we want to model this? Uh, if it is order for later, then we would, uh, be able to modify the time and the date. So actually it'll be just order time and if it'll be set to time now or if we want to override, we can, uh, change the time. So we would have, uh, something like order time inside our order and it'll be a date time. Okay? Then, uh, what else do we have here? We have a discount code. So, uh, possibly an order. I would be able to have a discount code id, so I would add the discount code id here the end and would belong into a discount code. So we would also possibly need a discount codes, uh, table. Okay? So we're creating a new table, table discount code id, then it would have, um, name, so it would be the one that would be inputted here. It would have something like active or not. So this would be ink string active. It would be a bullying. Uh, then we would have something like how many times it was used, how many times it can be used. So, uh, times used integer max, uh, usages, integer, and uh, discount percent, it would be also integer gas. Now that we have our discount codes, here they are. Okay, now let's actually make it a bit wider. Okay? Something like this. Uh, go next. So we have your order discount code has been applied. Discount code was invalid. Okay? So we have the price without discount code, and we have the price with the, that discount code. So, uh, our order is going to have, uh, the total price and total price, uh, uh, after discount. So to be some like final price, let's say, say price and price with discount integer. And we can also go, no, let's not cope with the discount percent here. We'll just have the discount ID and hopefully it'll not be editable. So usually a discount code can be edited and it cannot be deleted if it has been used any times. Okay? So an order has the price, uh, uh, without discount and with discount, let's say without, okay, something like this. And what else do we have? Okay, click, uh, confirm an order. Okay, let's have a look at our order. So here we have the order, the total price, price for discount, and an order can have status of, uh, arrived. So actually an order will have different statuses and one of them will be arrived. So an order will have a status. Now, can we define the options here? I am not sure how to do it with this synex or this application, but basically it'll have a status of, uh, maybe we can make some comments. Okay? So status will have, uh, options of something like, uh, uh, incomplete. Then it'll also be able to be, um, in delivery, okay? Uh, complete or like ready to process. Let's maybe add it to different lines. So incomplete, uh, ready to process or like ready to start. Then we will have, uh, uh, ready, uh, to deliver then, uh, uh, delivery and complete. Okay, so incomplete, meaning the user hasn't, uh, posted the order yet. Ready to start means the user has posted the order and they can start the, let's say, preparing it ready to deliver is they have prepared it, and the delivery guy can, uh, well, it can be given to the delivery guy or to the beta, then delivery. So delivery in process, it has been delivered and complete. It means it has been given to our customer. Okay? Looks fine. Now, there are so many ways we can go here, so thank you. View all the status. So we should also have, uh, a map with the location of the restaurant, with the location of the client, and, uh, potentially we would maybe want to delivery guide to, um, shahi location, but it's not something urgent, okay? We have other updates. So, uh, This would be already the data of the user. So the user's email and possibly his phone number. Now at the moment, the user has his ID and email, and uh, we can also add his phone number to the user's model, not only to the delivery address, but we can live this for later. I think it's not something urgent, okay? It's an example of page not found. What do we have in this index page? Okay, an example of an error. Okay? So we've done that flow, and here we have another flow with settings. So, um, dropdown menu, past orders, my deliver address, okay? So he will have the users deliver addresses here, he will see his, uh, past orders. So the orders that have the status, uh, either complete or incomplete, and the current orders, well, so the user will see all his orders, not much else here. Then, uh, you can enter his delivery address and see a preview of his location looks fine. Uh, success message when it was saved doesn't matter. Okay? Past orders and the order statuses. Okay? So one is getting prepared, we can actually, uh, rename this to, oh, actually another status so that it is being prepared. So getting prepared, Okay, then we'll have delivered. So delivered, not complete. Okay? And we can see the order items inside each order. And we have the order date, the quantity of items, so we can actually add the o order items count inside the order. So here we will have, uh, order items count in the share here will have some kind of contact caching. I'll go the quantity of items than deliver the order date. So is the order time here? And yeah, we can also actually track, uh, the time when each of these statuses was changed. So when the order was, uh, uh, set to, uh, ready to start when it was set to getting prepared, ready to deliver delivery and delivered. So this would actually be really interesting. Now, we can leave this also for a bit later. So, um, track time for each status change. Okay? And there are a few options. So reorder, okay, we've been here already. Uh, what happens on the click on reorder? We've been here already. Okay, we've seen all these views, uh, already, so looks fine. I think we're done this first, uh, giant sketch, not have this other store menu. Let's have a look at this. Okay. So it's basically just, uh, the way the menu looks. Nothing special, nothing that we haven't seen yet, and again, the languages. Okay? And here we have the start and admin dashboard. So also quite a few screens here. Let's have a look at them. So, um, we have new canceled in preparation ready for pickup. So instead of ready to deliver, we will have ready for pickup. Then, uh, we have in preparation. So in, in preparation, then they'll also have something like canceled new. So new is something like ready to start on the way is, uh, going to be instead of delivery. So on that way, and here we have the estimated delivery time. We should also possibly calculate this somehow. I'll just leave it as a mark for now. So maybe we would want to use some kind of Google maps, uh, API or something like that for the estimated delivery time. Okay, so on the way delivered looks fine. So this is, is the way the, I guess, restaurant, uh, manager or the cooks are going to see their orders. And again, here we have these flavors. So vegetable, milk or sauce or something else. So this is the dashboard for our start income and orders. Okay? So actually they'll need roles for staff and roles for, uh, clients. So I think it doesn't make sense to create two separate user models, but, uh, like for admins and for users, uh, it would be just good to give, uh, some users the role of, uh, delivery guy or role of stuff, or role of uh, uh, customer Or possibly a user could have many roles. Okay? Then depending on whether the price line and price store is filled out, the item will appear in install menu or in online menu about, okay, this is kind of interesting. So the menu online and the menu offline, uh, can have different, uh, prices. So an online price can be one, and a store price can be different, but I think we can, uh, watch this one later. And you see an item can have an image. I actually forgot this. So a menu item has an image. They'll actually have an active storage relationship to an image. So menu item has an image, I think one image is enough, uh, and we are going to store it in, uh, something like active storage, and we'll have just a blo relationship that a menu item has one image. So Then name, price, store price in line. Okay? For now, we'll have just one price for the simplicity, then category description, and here are the options. Okay? So here it becomes, uh, more interesting. So there are options. It has a field, a price, and, uh, some kind of thing that we can click. And the add-ons. So an item can have many options and many add-ons. The difference between an option and add-on is that, uh, an item can have ma many of one of them and one of the other, I'm not sure which one. Okay, we have a preview, doesn't matter yet. So here are some examples, uh, of options and add ons. I still don't understand them well yet, but we can, we can, uh, definitely create an, an additional table where we're going to keep the relationships of menu items and their options. Now, we'll leave this for later because I don't fully understand it yet. And what do we have here? So drag and drop effect doesn't matter. Okay, this is interesting. So product availability, unique options, and a don'ts are listed here and can be displayed globally. Okay? So, uh, basically options and add, dons should, uh, have a, an availability status, Possibly as well as ingredients. And if the disable the availability of an option or a dawn, then all the products, uh, or uh, should not have the option to select this at dawn. So, for example, if you don't have meat, then, uh, they should not be able to select anything with meat, or if you don't have me vegetable milk, they should not be able to select anything with, uh, vegetable milk. So for now, I will just say, uh, ingredient availability, let it be ingredients. So I will say available bull, okay? Uh, individual options and adults can be enabled or disabled for a product. So we can also enable them and disable them on a product level. Okay? We will also leave this in mind for later. Now, what do we have here? So yeah, just drag and drop design again, add new item, item preview. Yeah, just the way we can delete, uh, an attached image. I don't see any difference between these two images. Okay? Then, uh, links to edit and delete a menu item and to make a menu item active or inactive. So actually we can add this active pool to our menu items Like this. So drag and drop effects for formatting menus doesn't matter yet. And I guess that's it. So the only thing that is left is, uh, these, uh, options and, uh, add don'ts. Okay, so let's go back to our landing page and possibly find the if view where we can select these, uh, add don'ts. Yeah, here is an example. So you see, uh, some we can select either hot or cold. So this would be an option. And, uh, this would be an add, don. We can select multiple, add don's and the options and add, Don's can also have their price. Okay, so let's, uh, make some notes about, uh, that. So, uh, option, uh, can be one, like hot or gold and, uh, add. Don can have many like vegetable milk, like, uh, no soy or whatever. Okay? So, uh, a menu item can have many options. And, uh, addons and then selecting an order item, we can select the ones, uh, that we want to add. So let's try to add this. I will say, uh, table, uh, uh, menu item options ID integer, and it'll belong to a menu item. So menu item id, Now that we have the stable, where was it added? Okay, here it is. Menu item options. Okay, so menu item options. And, uh, an option will be able to have a name string, and it'll have a price that can be actually new. Uh, so price integer and, uh, available bullying. So we can, uh, switch on menu item option availability or of, and a menu item can have, uh, how many can have, uh, one option. So it should be not one to many, but one to one. I'm not sure how to say it, that it is one to one. In this case, well, actually a menu item can have many menu item options, but an order item can have just one menu item option. That's the way it should be. So, uh, inside our order item, whether we have it, it is going to have a menu item, option id, integer and referencing menu item, option id. Now, how do I make it more visible? Something like this. So a menu item can have many menu item options, uh, but an order item can have, uh, just one menu item option. It has a name, a price, uh, availability. Actually, if we go deeper, we can also link these ingredients to our menu item options. So, uh, if, uh, an ingredient is not available, then the, the menu item option will not be available, and all the menu items will not be available. And same with the add dolls. So a menu item can have, uh, many addons and an order item will also be able to have many addons. So it'll be something like, uh, whether we have our menu item options, we'll create a table menu item. Dons also name price available billing belongs to menu item. Now that you have this menu item at Don, like this, okay? And it'll have a relationship to our order item. So an order item can have many add dons. So order item, menu item, add don a table that will be named the, that will have a very long name. We can of course think of a bad name later on, but, uh, for now, how should we name it? Actually, we can just, uh, try passing them as a hash. So what if we pass them, uh, as a hash, if we have, uh, a menu item, a don't field, that is a hash inside our order items, and that will have the I ideas and s no, I think it'll be actually easier with the add an additional table. Okay, so they'll have a new table menu order item, adults ID integer, and it'll reference, uh, an order item id. We have the stable, where was it added? Okay, so order item at don, and it'll also reference this menu item at Don, menu, item at Don id and menu item at Don id. Okay, so I think we're done. Let's look at the flow. Basically, uh, a menu item belongs to a menu category. A menu item has a name, a description at default price, uh, availability, and it has many options, and it has, uh, many addons. Okay? Then, uh, then we want to order some menu items. We add the order items to our order, and an order item can have a menu item. It can have, uh, an option, and it can have many addons that are related to this menu item. Then the order itself to have a status. They'll have, uh, uh, the quantity of audit items, the price without discount, with discount, uh, yeah, and actually this price will have to be calculated based on the menu item price plus the price of the options. And a don't, this is important to, to note, okay? Then it has the order items, count price without discount, with discount, the discount code id. Then a user can have many orders, and actually they'll have, uh, a few additional, uh, timestamps. So not only the order time, we'll have a timestamp for each of the statuses. So here we have status string, and for each, uh, of the statuses, we would have, uh, a separate timestamp. So actually we would possibly want to add, uh, all these to our table as attributes. So, uh, uh, we would say like, um, canceled time, date, time. Then, uh, ready to start time, date, time, uh, in preparation time, date, time. I'm just adding time here so that when we develop the application, we always know that these are datetime fields, but not, uh, buildings or integer or whatever. So, um, ready to pick up time, date time, and, uh, so when it was picked up, so on the wait time And, uh, delivered time, And this will be the order time, so they're ready to start time. Okay? And this all actual doesn't, uh, doesn't include, uh, anything connected with money. So, uh, at some point the user would have to pay for the order, and the order would have to have a payment status. So, um, this would, I think, be managed by the, uh, payment provider, but we could add an additional status. So incomplete, uh, then, um, we would have some status for like payment, uh, uh, ready to pay, actually incomplete would be ready to pay. So payment, uh, and ready to start would mean that the payment has been done possibly. Yeah. So I think I won't be adding any additional, uh, statuses now. Uh, just you would be changed, ready to start. And yeah, we have our, Our date fields for each of, uh, the available statuses except of incomplete. For this one, we don't need the, uh, timestamp. Okay, so looks good. And a user can have many deliveries addresses and, uh, for each order, the user specifies a delivery address. Okay. Looks, uh, quite clear to me. Now, one thing that we could also add again in the future is, uh, connect, uh, menu item addons and options to ingredients. So this would be something like a polymorphic table. So it would be like item ingredients and it would, uh, be able to reference item, menu item, or option or add, do. Okay? So I think we are done with modeling this, uh, application, actually, like the experience of using this tool. So have got all our tables, the data types, the relationships, and, uh, we can start, uh, creating an MVP with the draft in this hole inside the, our scaffolds in the rails application, for example. But, um, I guess that's it for now. So, um, thanks for being with me. And I think now, uh, you can also try to, well, you should have an imagination of how to use like entity relationships, diagrams, how to, uh, model, uh, data, uh, into a database. And now you can, uh, start creating scaffolds and, uh, modeling an application for a restaurant, ordering, uh, and delivery service. So thanks for being with me. Cheers. --- # #56 Generate QR codes, Barcodes, use ServiceObjects - URL: https://superails.com/posts/ruby-on-rails-56-generate-qr-codes-barcodes-use-serviceobjects - Published: 2021-10-06 - Duration: 32:17 - Access: free - Topics: image generation Source code for the episode: https://github.com/corsego/56-qrcode-barcode-serviceobject/commit/340e12e7a1e58a5f22a2935cbfc33e6420327262 good random tool to decode barcodes and qrcodes: https://zxing.org/w/decode.jspx ## Transcript Hello. So in this episode we're going to go through creating barcodes and uh, QR codes in the ribbon rails application. Now, QR codes are used usually to, uh, go to a link on the web and barcodes are used to identify a product, and they're going to do both now, and it's quite easy thanks to the GEMS IQR code and the Gem Bobby. Now, here is a case. We have a list of posts and we want to generate, uh, a QR code and a barcode for each post and store them in our application. So we are going to use active storage for that. Now we are going to start with the QR codes. So let's have a look at the documentation. Uh, basically they're going to install the GEM R QR code. So we will say bundle add RQR code. This rate will be added to our GEM file and bundled. Okay? And now we can already, uh, generate a QR code. So let's try, we will go to our console and, uh, say, require a QR code and generate a QR code for this, uh, website, for example. Okay, let's say require a QR code, generate a QR code, let display it, it'll say qr. So, uh, here it is, and we'll, uh, display it as a string. So we'll say, put QR to S. So here's the qr, but it isn't quite readable. Well, this is some basic, uh, QR stuff like these squares on the sides, but again, it's not really readable and it is not an PNG image, for example. It's just a stream that is displayed in a console. So we want to save it, uh, and uh, display it somewhere as a, uh, as an image. How can we do this? Well, um, there is this option as SVG or SPNG. So what if we want to save it as A PNG? Well, for this we would need the, this external dependency, chunky PNG, and I think it actually comes preinstalled with RQR code. Uh, anyway, I will say bundle add the chunky p and g Because, uh, bar doesn't have it, uh, as a dependency by default. So they're going to also say bundle add chunky, P and G. Okay, going out and saying bundle add chunky, P and G. So, uh, it should be also added here. Okay. And now let's try to generate the PNG. So here is the code. We require a QR code, then we generate the QR code, and then we generate a PNG based on this QR code. Okay? So let's try Rails console, generate QR code, then we generate A PNG. Okay, so if it type PNG, well, here it is again, it isn't readable yet. So how do we make it readable? Well, we can save it in our local storage. So we can say something like IO binge write to our temp folder and a name for the file. And the, the file is going to be this P and g to S. Okay, let's see if it works. IO binge write. Now did it work? Let's have a look at our DMP folder. Now it looks like it didn't work. I will, uh, possibly remove this slash and here we've generated a file. Let's have a look at this file. Okay, here is a file looks, uh, good. Now let's, uh, actually see if, uh, it works. I will try to save it, uh, locally. So I will, um, download it and here's some kind of online tool that can, uh, decode, uh, a QR code. I'll just take it. Okay, submit query. And here is the past results. So here is the URL. Okay, it seems to be vCAN. Now let's, uh, try to actually make it VO for an image inside our application. So, uh, let's go, uh, maybe to our app models post B. And uh, yeah, we'll want to save these, uh, images inside our storage as active storage and attach them to our post. So for this, we'll need to use, uh, active storage. So we will say, uh, rails active storage install. Okay, we've created a migration. Now let's go to DP Migrate. Here is the active storage migration. I will say rails, DP migrate. Okay? And now we can say, uh, post has one attached, uh, barcode or actually QR code. Okay. And, uh, they're going to have a method to generate, uh, a QR code. So let's say after create, do, uh, or actually after create, uh, generate QR code and we'll say dev, uh, generate qr. And here they're going to have the code. So the URL should be the URL of this uh, post. So what would the URL or the post be? For this, we would need to use a URL helper. Let's first see if, uh, we can generate a URL that we like, let's say URL four, and we will say controller will be posts. Then, uh, action will be show. Then the ID will be the idea of this post. So self ID And, uh, what else? Uh, do we want to say? We would say only both false, I guess. And we would say that we want to have a base, URL. So host, host will be the name of your main application. So for example, super rails.com okay? And we can actually add some kind of, uh, perms if you like. So, uh, let's say, uh, uh, source would be like, uh, uh, from qr, so it'll be visible in the URL. Then somebody opens, uh, our post show page through our qr. This, uh, from U QR would be added to the URL. Let's see if, uh, this works. So then I, uh, try to just display, generate QR inside our post. Then we should have a link to the post and with a source from qr. Let's see if it works. I'll just go to our posts, start the server use posts index, and somewhere here I will say, uh, uh, post generate qr. And this will just give us, uh, the URL, hopefully. Okay, expecting, uh, closed cap. Let's see. Yeah, I forgot a coma here. Okay, we have an undefined method, URL for it is because inside our models we are not by default, uh, uh, using our URL uh, helper. So we want to use this URL helper in our, uh, model. And for this, we would say something like, uh, include, uh, rails application. Do, uh, roots u RL helpers. Okay, let's try on scan. Okay? And here we have the kind of URL that we will be generating for each post. So we have the, uh, link to the application. Uh, we have the idea of the post, and we have some additional prompts like source from QR so that we can actually track, uh, how many visits have been from the qr. Okay? This is the URL for each of the posts. And, uh, now we want to generate a QR based on this URL. So here's the first step. We generate a URL, let's say QR, URL equals URL four, and so on. Okay? And now how do we generate a QR code? We will say require RQR code. So also here, and here is the code to generate a QR code, for example. Okay? So, uh, next we will have QR code equals RQ QR code new. And here we will have our generated URL, so QR, URL. Okay, so here we generate the QR code. Let's see what happens if we just refresh this page. Okay, here is the kind of generated QR code looks, uh, terrible. Let's, uh, keep on. So next we're going to generate a PNG. Let's generate a PNG. PNG will be this QR code as PNG. And again, they have all these default uh, options. You are free to explore them, change the colors, change the sizes, and so on. So now we generate a PNG. Let's see what happens. Okay, here is the PNG, but it is not, uh, readable yet. So now we are going to try to save this PNG in our, uh, temp storage. So how would we do it? We would say something like IO bin, right? And so on. Let's see how we can do it. I would say IO bin, right? Then TMP and the name of this, uh, particular PNG. So each of them will have a unique, uh, name. Let's, uh, provide a unique name. Let's say image, name equals secure random hacks. And we'll write to the image name as image name, PN, G and PG to S. Okay? So, uh, now in theory when we call, uh, generate qr, they should, uh, generate, uh, an image and write it to the temp folder of our application for each of the both. Now, I think if I refreshed, it'll actually take some time. And, uh, what has happened, you see, we've generated all these, uh, PNG images. So each post has, uh, its unique, uh, QR code. You see that different, okay? And, uh, again, we just see the blob IDs or whatever here, so it's not what we want. We've just generated the QR codes in our temp folder, and now we want to save them inside active storage and assign them to our post. So to assign a, uh, file to our post, we would run something like, uh, self dot generate qr, no self QR code. Yeah, they keep it in the QR code attach, and they're going to, uh, reference one of these files. But, uh, this file has to first be saved in our application storage. So this will be like the storage of the application itself, not the temp storage. You see we have the temp folder and we have that storage folder. Now, the storage folder will usually be your AWS storage, storage or any cloud provider storage that you've got. So first we're going to save this image from our temp to the cloud to our active storage, and then assign the blob to our, uh, post. So we are going to assign a blob, and based on this, we're going to create a blob. So we'll say blob equals active storage, and the, the command would be something like, uh, uh, blob, so active storage blob, uh, create after upload, and inside we would have some, uh, brands. So IO is the file itself. So we would say file open, and we are going to reference one of these files. So it'll be something like, uh, uh, TM P, and then we're going to have the image name. So image name, png. You see this is going to be this, uh, image. Okay? Then we're going to have the file name again, it's going to be the image name. And we're going to have the content type that is going to be BNG. Okay? Something like, uh, this. So they're creating the blob and attaching it. And here, instead they're going to display the QR code. So let's see if it works. I will, uh, refresh. And you see we have this active storage, uh, attached V. So let's see if it's actually attached QR code attached. It'll give us a true or a false, and it says false. Now, why is it so? Because, uh, they're going to run this command generic after create. So let's try to create a new post. Let's say new post, uh, with a QR create post. Now we'll go to posts. And you see here we have tool, so it has something attached. So now we can actually try to display the QR code. We would say an image tag inside. We're going to have the post QR code, and we'll display it if a post has an attached QR code. Okay? Let's see. And here for this post, we have a QR code. Now, let's see if it works. I will, uh, save the image and it would be like for the post number 11, let's, uh, try to decode it. So I'm going to go to browse, I'm going to go to this, uh, QR code, Submit querying, and here it is. It works. So we've managed to generate the, a QR code for this. Uh, particular post looks good, but, uh, not good enough. Again, here we have this giant, uh, method of generating a QR code that is sitting inside our post A B, and, uh, it's not, uh, basically great to have it inside our post a B. So this is like a one gen singular responsibility action that could potentially be used not only for our posts, but for other stuff. So, um, it would make sense to move it somewhere else out of our model. And I think the perfect, uh, place would be a service object. So we can create, uh, basically ruby objects inside the additional folder inside the our app folder. So let's, uh, try to move this logic out of our post model. So I'm going to go out and say, make a directory, app slash services. Here's the new directory. And inside they're going to have, uh, two files. So echo, uh, app slash services slash application service, rb. And another one is going to be something like, uh, generate qr. So generate qr. Okay, here we have the files. And uh, now we are just going to do some setup for these, uh, service objects. So here we're going to say class, application Service. And uh, inside it'll have, uh, def, uh, cell call, then ARGs and uh, block. Now if you want to learn more about service objects and this specific setup by it works like this, you can, uh, have a look at some kind of, uh, Rubicon talks and so on. But basically this application service and this, uh, method that I'm writing now is just saying that, uh, we are going to be able to have a method like dev call inside our services and, uh, inside our post or wherever, I'm just going to say something like, generate QR call and they're going to pass in the object. So it's going to be self okay anyway, going on back new orgs and block and call, okay, I guess something like this. And here inside our, uh, generate your service, we're going to say class generate QR that inherits from application service. And, and we're going to have an editor reader, uh, post and we'll say dev initialize. So post, and they'll say, add post equals post. And, and, and now we can have this, uh, call method inside and, uh, move all our logic from our post RB into this generate QR call method. So let's take, uh, all this logic for getting the QR URL, generating a QR code, generating an image, generating an image name, saving the image to local storage, uh, creating an active storage blob and assigning the blob to our particular post. Let's take all this logic out and put it inside this, uh, column method right here, okay? And we'll also move these helpers like require Q code and require, uh, and include rails application Route URL helper will also move this into this generate qr. So we'll put it somewhere here above our goal method. Okay? So what should happen? You see, we've really clean, leaned up our post RB and move the our generate QR code into a service. And the thing about services that they are kind of executables, so we just pass in some pers and they execute the some logic. And the cool thing there, like DL usage of all services are that they should be like single responsibility. So one service does one thing from your business logic. Okay? So let's, uh, see if it works. Now I will, uh, actually delete everything they have at the moment. So I'll say rails, the B reset. And if I reset the database in theory, whenever the posts are created. So I already have some seeds here. Let's see, I'll go to seeds. RB, you see, I see the post 10 times. So then I see the database. This, uh, after create Kohlberg should fire that generate QR service. And an active storage blog should be assigned to each of the posts. So each post should have a QR code. Let's see, I will say rails to be reset. Okay, we got undefined method id. Let's see, let's uh, see what do we mean by undefined method id? So we say self, uh, id, okay, yeah, we're just going to have replaced the self with the post here. I'll say post it and post your code. We have the self, uh, anywhere else here. No, we don't. Okay. Again, I will say rails will reset, okay? And you see a lot of files were created in our storage and even more are in our temp folder. Now, if I go to the rail server, Let me refresh, okay? I'll first log in, I'll create an account, okay? And you see each of our posts has a unique QR code. So looks like the generation was actually quite successful. Now, each of our posts has a QR code. And what did it do? We've, uh, added this, uh, method generate QR in our post rrb and all the logic, the whole heart of the process is in this service, okay? And we can actually have a look at our active storage, let's say rails concern, oh, rails console and say active storage blob dot count. So you see we have 10, uh, objects saved in our storage and, uh, one for each of our boths. So seems to be working valve. Now let's try to do the same, but for qr, oh, for barcodes. So for barcodes, we have this gem named Barbie. Let's, uh, install the gm. I'll say bundle add Bobby, Okay? And, uh, here's the code. So it would require Bobby and then generate the barcode for, well, any string that you would like. So I know this could be the UL of your post, or it could be, uh, like the idea of the post. Uh, well usually it's some kind of like unique id. So it would be just some kind of, uh, hx, uh, or slug that references, uh, uh, one of the products in your database. So let's, uh, just try to see how it works in the console. First of all, I'll say rails console, okay? Require bar and create the barcode. I'll say barcode. Yeah, so not showing us much yet, and let's try to display it as an image or something. Now, here we have these putters. So let's have a look at these Bobby putters. Okay? So we'll look at PNG. So we'll require Barbie output, PNG output. Next, uh, we will create a blob for the barcode. Here it is. And we can, well open this as a file. So if we run this command file, open barcode, p and g. You see, it was actually saved in the root folder of our application. Now, this is far from perfect, but uh, here we have a barcode, and now we can apply a similar logic as we did for your codes for generating the barcodes. Okay, now let's actually first try leading all this, uh, clutter. So I would say something like rails, uh, TMP clear build it not, or TMP dump. Okay? Anyway, I'm sure that there is a command. I just don't remember how to remove all this, uh, rubbish from our TMP folder. I will now try to just do the same command as we did with our, uh, PNGs, but save it inside the TMP, but not the root fold of our application. I would say rails console. And, uh, go back to Bobby, run these commands, then go to putters, create the blob. Okay, here is the blob, and I will save this blob to our, uh, TMP folder. So I will say image, name, secure and random to hacks, and I will generate an image. But here we had this blob route. So I'm going to use blob two s, okay, here we've generated A PNG. So now we generate in the right folder in our TMP. And based on this, we can create the, uh, barcode. Based on this, we can create, yeah, the barcode. Okay, so, um, let's, uh, well for example, create a new service, uh, to create a barcode. I will go to our services and say new file generate, uh, barcode rb. Okay? Now I'll just copy everything from here. And some lines are going to be, uh, modified. So what will, uh, the buckle code be? It doesn't have to be a URL, it can be, for example, any random string. What if we just put the title of the post as an example? Let's, uh, try that. So I will add this lines require, Bobby will not need to require QR code, and here it'll be generate barcode, okay? And we'll have this barcode equals Bobby, and we'll have some kind of, uh, uh, stream here. So this can be something like, uh, uh, post title for example. Okay? And next we are going to require this output, A PNG output. So we'll put it here and we are going to generate this blob and display this blob. So Here we generate the barcode, then we generate the, uh, let's say image. I'm removing this code, okay? We'll have an image name and we will, uh, put this image here, or we can see just PNG. We have the same kind of name, okay? So we generate the barcode, we generate the PNG from the barcode, we give it a name and we save it inside our team P folder. And then we save it, uh, as a blob and we will then attach it to our post, but not as a QR code, but as a barcode. So we'll say, has one attached to barcode. Okay? So we'll say post barcode, attach blob. Okay? And now let's uh, say that when we create, uh, post, they're going to generate a QR and generate a barcode. Okay? And we'll display both. So image tag, post QR code, and post barcode, just like this. Now let's, uh, restart. So rails to be reset. And now all the posts are going to be recreated and each post should have both QR code and a barcode. Okay? You see out of additional files were added to our storage. Well, actually it was recreated. Let's run the rail server. I'll refresh. Okay, again, I will log in because I've reset the database. And let's see. Now it looks like the images are still loading, but hey, here they are. So each post has a unique QR code and a unique, uh, barcode. So it looks like we have accomplished our mission. Now let's just create a new post, uh, with both QR and barcode. We create a post and you see it was given a QR code and a barcode. So looks great. And Just for the final touch, let's uh, download this barcode and try to decode the name. So I will save it. I'm saving the barcode. Now I'll go to this random online tool. And you see it can, uh, decode QR codes and uh, also barcode. So you see this code one to eight, and the kind of encoding we did for our barcode is this code one to eight. So it should possibly be able to decode it. So I'm gonna take this barcode, submit query, and you see the past result is with both QR and barcode. So it is the post, uh, title, just as they expected. So that's it. And basically in this episode, we went through creating, uh, QR codes, creating barcodes, using service objects, and a bit of active storage. So thanks for being with me and having a great time coding. --- # #19 View Components - URL: https://superails.com/posts/code-along-video-subscription-app-19-view-components - Published: 2021-10-01 - Duration: 34:08 - Access: free Episode source code: https://github.com/yshmarov/superails/commit/9bad0a4ef8a42a0d68b1d173beb13ee59b9be133 Official docs: https://viewcomponent.org/guide/getting-started.html Great article: https://teamhq.app/blog/tech/17-how-rendering-partials-can-slow-your-rails-app-to-a-crawl --- # #18 Rubocop, Ruby Style Guide, Traceroutes - URL: https://superails.com/posts/code-along-video-subscription-app-18-rubocop-ruby-style-guide-traceroutes - Published: 2021-09-27 - Duration: 43:11 - Access: free gem Rubocop: https://github.com/yshmarov/superails/commit/91c25ffd4ef6dcd4bd5692608dc6a76d81f13386 gem Traceroute: https://github.com/yshmarov/superails/commit/ad70bbcc37ba83d6f292ebdf77b2ab6bd7348664 How to use Rubocop: https://blog.corsego.com/install-and-use-rubocop How to use Traceroute: https://blog.corsego.com/gem-traceroute-find-controllers-without-routes --- # #17 TomSelect.js - VanillaJS plugin to select or create tags - URL: https://superails.com/posts/code-along-video-subscription-app-17-tomselect-js-vanillajs-plugin-to-select-or-create-tags - Published: 2021-09-25 - Duration: 21:00 - Access: free add tom select: https://github.com/yshmarov/superails/commit/4f9e22b27505d213c54e7df391b08f1bb5beb5a1 tom select in stimulus without turbolinks: https://github.com/yshmarov/superails/commit/eeb0bddd6cc631c4c0eff22e59fd867324513ce7 --- # #55 Subdomain Multitenancy with Ernest Addae - URL: https://superails.com/posts/ruby-on-rails-55-subdomain-multitenancy-with-ernest-addae - Published: 2021-09-24 - Duration: 55:24 - Access: free Watch Ernest Addae create and troubleshoot an application with Subdomain Multitenancy (like shop1.domain.com, shop2.domain.com). At the end we discuss how to make it work in production with Heroku and SSL. Second episode with some improvements and deployment to Heroku coming soon, so stay tuned! Ernest Addae on Twitter: https://twitter.com/Earnaddae 2023 update: we never managed to meet up and record a QUALITY part 2 :( 0:00 Warming up 2:29 Start create accounts: https://github.com/corsego/55-subdomain-multitenancy/commit/724bdb88aa4d36c1e2c7a3311eeabb3fce9c0c24 seed accounts: https://github.com/corsego/55-subdomain-multitenancy/commit/74da6ca2ea6a1155cd04846775d4865ff4923dc8 redirect to subdomain: https://github.com/corsego/55-subdomain-multitenancy/commit/40040db2f06ca957b29c1a775fa64e6047b0fbb1 accounts module: https://github.com/corsego/55-subdomain-multitenancy/commit/83eac1f04a91c3b1345e70c456118bceba233b86 scaffold posts: https://github.com/corsego/55-subdomain-multitenancy/commit/1dca224f136e159797e560d3d06b18da72e1a636 account has many posts: https://github.com/corsego/55-subdomain-multitenancy/commit/d50cf25f7bfbde6a7a6d3db934e7b9a6a64a7c33 Scoping Posts to Accounts module: https://github.com/corsego/55-subdomain-multitenancy/commit/1827725e15d779644e0d114cf261dbfc2b777538 Constraints: https://github.com/corsego/55-subdomain-multitenancy/commit/906c766118c954577f9f1079f5cc06e08d14ea41 Better seeds: https://github.com/corsego/55-subdomain-multitenancy/commit/bec196da6c1c191545c3b38d04ed3f902ba84abd Readme for LetsEncrypt SSL with Heroku production: https://github.com/corsego/55-subdomain-multitenancy/commit/10e889eb16db68a87d5ae19ce21b954f6e5083bc ## Chapters - 0:00 Warming up - 2:29 Start ## Transcript Yeah, because, uh, from like all the rails implementations of subdomains, uh, I liked yours, uh, the best. I was, uh, really surprised, like it broke really smoothly. So I'll create a new app so that we can work, we can work from it from scratch rather than trying to go through an existing app, several moving parts and stuff like that. Yeah, definitely. Yeah. Hmm. Usually create an, a new rail six app is, uh, like five times longer than create, uh, rails, uh, five new application. Yes, it's true. And, and, and the, the, the fact also the app becomes, already, the app becomes already big, you know, like the size is very big, but I, I tried, so I tried Hanami when they announced the version two launch. Mm-hmm. I think it's, it's pretty cool. It's pretty cool. But it is just that everyone is so much addicted to reels that, uh, is difficult. It's quite difficult. Okay. So I opening a text editor. Okay. So let's see if everything works. Oh, wait, sorry about that. I, I remember I use, I'm using Postgre, so as usual, I have to set it up before, and then let's see if we are on course. Okay. It seems everything is set. Okay. So, uh, so let's say you, uh, I think the whole, the whole premise of uh, a multi-tenant tenancy app is to have a main account, uh, whereby you scope all your resources into that account, right? I mean, that is the idea. Or you can use the apartment gym approach, which I find very difficult and I don't even understand. So I'll not even go there. But, uh, the one that you can use ask as tenants or try to scope tenants, uh, I find it quite the best approach in doing things. So let me start by, lemme generating an account. Okay. Well, I mean, the approach where you have just one database is, uh, always, uh, much, uh, more plea than having different schemas or different databases for each tenant. Yes, yes. Uh, I think that is, uh, I mean, if, uh, like you are running a giant business that has billions of records, uh, maybe it makes sense to use, uh, something like, uh, apartment jam and user separate, uh, schemas and, uh, yeah. Yeah. But the, the downside is scoping each one of them by using the, has many relationship is just that, uh, the downside is you always have to remember that whatever new resources you are creating, you have to scope it to account, sort of. Yeah. I think that's the only thing. So let me create my, uh, the root route. I start the server, I make sure that we are in the roots. Uh, our homepage is the same as as we are so that we can go on. Okay, so normally you create a new account. Yeah. So basically, is this right? So the, normally this is the normal route that we go about it, but now we want an account to have sort of like a subdomain so that when we create an account, we will go to a subdomain and assess the, uh, the account. So, uh, how we, how I did it is by having a subdomain first, right? So that we can use the subdomain as the approach in, uh, as the way of scoping each of the resources we have in the account. So, how I went about it is to first create a, a subdomain. So I, I dunno. Yeah. This is better. Yes. So let me create a subdomain, right? So now we know that we have the sub domain. I'll go to my controllers and then make sure I allow the subdomain, and then I'll make sure I have it also in my, my phone. Okay. So now, now that I have it set up, all I have to do is to go to my controller. And whenever I create, um, whenever I create a new account, I will rather redirect to root URL, but I want to be, uh, cumbersome of the subdomain. Mm-hmm. So I have to do the account and then tell them that, okay, whenever you are doing it, I want you to go to the subdomain as well for me. So when I create an account and it's alright, it's come to the subdomain, and if I not, it's not, so the request, the reels request will, when I create it, we'll just route the request to, uh, that, uh, that subdomain for me. So I will against that server, Uh, you have direct two device in your controller, the version? Yes. Yes. Uh, come again. Uh, you have the vote redirect two, twice in line 28. Yeah. Oh, yes. Thank you. Thank you. Yeah. So redirect to the root route, and then I'll go, I'll choose the subdomain, and I want to go to the of that. So I will come to my app now. So now when I create a new account, uh, another and I go to another, it'll redirect me to the subdomain. So you can see the route. Mm-hmm. Yeah. It has moved from the account, the sub domain name I gave, and then the local host and stuff. But usually developing this on local host, this should work. All right. But the problem I discovered is that even though everything worked in the development environment, when I did it this way, um, when I went to production, I had a little pain with that. So I relent that you can actually use L vh me, something of that, of this. Mm-hmm. Because serve as a subdomain already, because we have L vh me already. So this one is seven at the root domain, so we can add your, uh, subdomain to it. So I just go to, um, then go to app, uh, config application, rb mm-hmm. And whitelist. Yeah. And then whitelist. So normally, uh, I whitelist list this, or I would just set it to nil so that I wouldn't have to be updating the route. Anytime I try to, uh, start a new, uh, I try to go to a new route. So with it, I'll start server again, and then when I go to LVH 3000, the link is available for me there, so mm-hmm. When I create a new account. So just reroute that. So here, this is more representative, so I get my sub domain name, my name, and then the this one. So with this one, what I realized when I did it this way, uh, it worked in production as well as in development. So I learned that the L vh me is actually a pseudo for Looker, who's 3000. Mm-hmm. Yeah. So if, if it's an S to local, who's 3000. So I've heard it's a dn, it's a DNS labeled to local who's 3000. So I, I, I must mention that this is very, very important else, everything working local in development, but then when we go to production, everything is something else. Hmm. Okay. So now that we have the subdomain here, we have to consider another thing, because at the end of the day, our app has to be scoped to a particular resource. So let's say we have, uh, like a post or so whatsoever, right? So let me quickly just say we have a post for each of the, uh, a post for each, uh, or account to have many posts, right? So we can generate the reals, uh, scaffold, uh, lemme call it post has a name And it, and it to an account. Yes. Yes. Oh, yeah. Okay. So now I have it this way. So I can go to my, I'll go to the model account and then pass many posts just in case, and then a post will belong to an account. Right. Okay. I think I forgot something to to mention here. Uh, that we have to make the Yeah, well, they domain Definitely. Yeah. Yeah. So, so now we don't end up generating well, south domain that we showed. Okay. So now on this is set up. Now we know that, um, a post will eventually have many, uh, uh, an account will have many posts and a post will belong to, uh, an account. But then we also want to make sure that once we go into a post, we can have the resources that we want, because we don't always have to be generating the post. So we need a way to make the subdomain be required. Which one that we can click a link and then we can go to the subdomain, right? So we need to set, uh, occurrence, a helper method to track the subdomain for us. Mm-hmm. So, because when we will have, let's say you can have several accounts, but when you click on each account, you want to go to a subdomain of that account that is actually pretty much unique to you. Right? So to do that, I normally, I try to set a base, I don't like using authorization like punit and can, can can match. So I normally try to develop a base, a base controller, and I make my key controller inherit from those base controllers. So, but yeah. But, but anyway, but for the, for simplicity's sake, I think I can, um, let's define our route into, uh, in the application controller so that we can control all. So, um, I generate the help permit the like, parent account, and then I'll minimize is to, uh, account find by, I want to find by the subdomain. So whenever you make the request, I want the request to be routed through the subdomain, and I want it to be a help method as well. Hmm. So with this case, what I do that once I list all the accounts that are available, if I want to find a specific account, I can just quickly go to the subdomain of that account, and then I'll just find it so that I wouldn't have to be declaring it, uh, anywhere that I go. This exactly. Am I making, am I making sense? Yeah, yeah, definitely. Oh, okay. Okay. Okay. Wow. Okay. So now, now, uh, now that we have the accounts set up, we need to be able to scope it. We need to scope the, we need to scope the account so that each resource can go into, uh, each, uh, account. So I use the real scoping. So I scope the module count, and then I try to put the resources in there. Okay. So, for instance, I can make, let's say I can declare the beauty of having scope is that I can be, I cannot declare two root routes for myself as well. So I can say that root route of the subdomain, uh, can route to, let's say, post, uh, index, uh, this way, right? So I can now have, uh, two, uh, scoping mechanism, but then the downside is that now that I have a scope, I have to, I have to create the scope as a folder within the controller. Mm-hmm. So I have to create a new folder for accounts, right? And then within it, I have to, um, create a post controller as well. So in this case, I'll just wrap my model. Yeah. So now I, because I have scope it, I cannot wrap everything within it. So it's really not much of a problem, because all I have to do, I can now come here and copy virtually everything as I have here and put it inside the scope Mm-hmm. And make sure I wrap it. Mm. So when I do it this way, it means that now every route that is in here will be scoped to the post, uh, controller as well as the same as as I have without the scope. However, the downside, the reason why I don't really like declaring all my, this controller within the, within, uh, the application main controller, is that I want to be able to control the things that I do within the scopes that I have. So usually what I will do is that I will create sort of like a base controller. So I would do it, I would, I'll use this as my base controller and then fetch everything that I have here and leave the application controller alone, because it's something I don't want to touch. So usually, yeah. So I used to, I, I've declared this so that I know that whatever happens is limited to my, it's limited to, uh, it's limited to my base controller. Then what I'll do is that I'll make my post to inherit from, sorry, I'll make it to inherit from the base controller, so that Not admin, No, sorry, accounts. Sorry. Yeah. Yeah. So once I do you, you made, You made the typo in the word accounts. Yes. Thank you. Thank you. Per programming. Thank you. Yes, yes. Uhhuh. So now that I have, I don't have to worry again, because now everything I want to do is going to be, is going to be inherit from the base account. So my post is inheriting from the base account, which mean that everything I do within the base account, as long as you are inheriting from the base account, you are solid. So I don't have to worry about my, uh, my, uh, how do they call it? My application controller, uh, account, uh, please. I hope I'm make, I'm, I'm making sense so far. Definitely, please. This is my first pair programming, so I'm quite, I'm quite nervous and also talking to you also making me more nervous. You're doing great. Okay. Thank you. So, uh, with that, Dan, I think that another challenge we have to face, another challenge you face is that, um, if we try to go to, we have a route defined here, right? But, uh, we don't, if we want to do it this way, then it means that whenever we have a a subdomain, we have to be calling the subdomain to always be calling, uh, the, uh, the, uh, to always be calling our resource. Which means that when, uh, the point I'm trying to make is that let me go to my accounts controller, right? So, which means that anytime I go to a new route, I may have to define the subin all over again. Mm-hmm. Even when I have an new resource, I have to be defining. So it's kind of like a waste of time. And you may also do the, you may also have a typo, and you may also forget to do some few things. So, uh, I did was that, uh, rails give us the opportunity to actually do the rails, the race routes constraint, so we can actually constrain whatever routes that we want in one place. So for instance, what I mean is if we can call, let's say, posts, uh, in a, under a subdomain, but at the same time, we can also call it, uh, in another, uh, in another place that is not a subdomain. Mm-hmm. So we have to do the res constraint. So that's where res comes in with the, the ROS constraint thing. So what I did was I went to my lip folder, and then I created a, a folder called, sorry about that constraints. And then within that constraint, I defined a file call subdomain must be required. Mm-hmm. So I define a class called subin required, and then I use, uh, there is, um, real offer, I think self Yeah. Real, I, I, it is something I read from here Mm-hmm. Is something Mm-hmm. Yeah. The H-V-P-H-T-T-P constraints. So actually we can use the request as a hack into going to wherever we want to go. So actually when a request is coming through the, the route, we can reroute it to the subdomain that we want. And the way we do that is by using the matches, uh, keyword Mm-hmm. To us to do the matches keyword. So I came here and then I just have to define the, the match. So, uh, I define a match for, okay, def self matches requests. Okay. So what I'm saying is that if the request, uh, has a subdomain present, right? And then the subdomain, uh, the requests subdomain is not directed to www, right? This is what I want. So I want that, okay. Whenever my request is coming, I want that request to be within the subdomain. As long as this request is not within, this is, it's a subdomain, and the subdomain does not directly point from www dot, this is how I said, this is how I wanted to constrain my route for you, for me. Right. So then from after defining it, I'll come to my, uh, my route where the magic is happening, and then I have to require my constraint. Mm-hmm. So I have to require my constraint, which is the name, uh, sub domains Required. Yeah. Sub domain required. Right? Now, this, this is where, uh, it gets better because, because I've been able to define my constraint, now I can use it. So all I have to do is just use the key word constraint, which is the, uh, real key word for constraint, and then I'll call the class, I define some domain required, and then I will just wrap it in this. Mm-hmm. Yeah. So it means that as long as you are within, I hope I define my class right. So normally that's the, yeah. So what I'm saying that, uh, constrain all rev routes that matches this request. So as long as whatever resource I'm looking for is within this request, I want you to show me this links. Mm-hmm. Yeah. So, um, by the downside of this approach is also that you have to make sure that your resources are underneath the constraint first, so that in case it has to pick, in case it ha some account has to be picked first, it'll not override it. So if there's account here before the constraint, that account to be called first. Mm-hmm. But what I'm interested is I want to make sure that my sub domain fields before you can call any other guy within the, uh, the, how do we call it? Any other guy within the subdomain, I don't know if I'm making sense. Yeah, Yeah, yeah. I making sense. Yes. Okay. Okay. So now that I have done it this way, then all that is left for me to do. Now I hope I define, okay, so now that I have finding this way and I can find the account by their subdomain, right? That is all that is, that is all that is nursery for me to do right now. But because I have scoped the accc, the post, right, I also have to scope it in my views. Doesn't make sense because I have scoped the post controller within the, uh, the account. So I have to also create a folder Both. Yeah. Yeah. So in this way, I have been able to constrain it also so that I can call the post, and I, I don't have to be able, I don't have to necessarily be calling, uh, the, I don't have to you be calling it whenever. So now we can restart my server. Let me, let me remove everything again, and then set it up. Mm-hmm. What? Yeah, something in the roots root. See, if I set, it Might work. Okay. So I think this should work. It doesn't screen Okay. Already in use route. Oh, okay. Yeah. Yeah. I, I think, um, because I scope, I have to, uh, in a way do, um, so when I called this one, it is calling, it's calling everyone, right? So one of the things to, uh, when I, when I did this, because the post is already here and the account is already here, I have to segregate each an account by its, uh, particular resource. So here I can define as account route and C. Mm-hmm. I think this will work. Yeah. Because I have to be specific which of the route I'm trying to use, because I have roots here. I have roots within the constraint, but I can't find any cons. I can't find, so Rios is saying that, okay, you are defining two roots in your race route, but you have to define which 1:00 AM I supposed to use? Am I supposed to use the route with the post index, or am I supposed to use the route with the account index? But what I'm interested that whenever you encounter this one, I want you to use this as the root of my account. Mm-hmm. Yeah. So now when I go to my, yeah, so now that I have my, uh, my, now you can see that, I mean the homepage of my subdomain, but the post is still here because I define it to be within the constraint. And I don't need to necessarily go to, if I had not done this, if I had not done it this way, what would've happened is that whenever I go to my, uh, roots controller, uh, my post controller, and whenever I create a route, I have to direct it to the subdomain where I want to go, which not redirect to route subdomain where I have to go, but because I define my routes and then my constraints, this way I don't necessarily have to be worried about the, the specific, uh, route. So here, if you can observe, I am within my account, uh, my account sub domain, right? So I can just create a post, a moment, so here, and then I'll post. Now you see that it's trying to go to this, and by now it is telling me that the account must be present, right? Yes. It's, it's makes sense because the, it needs the account in order to go and then do it. So all, because I've already defined my current account, I don't have to worry about it because all I have to do is just, uh, go to, uh, my post, my post and then call the current account, right? Mm-hmm. Fantastic. And then from then here, I can then use the, uh, post paras. Mm-hmm. Right? Because I've already defined it direct, I've already, uh, defined its, and in my current account, right? So now Mm-hmm. Uh, Yeah. Sometimes rosein helps. Helps, Yeah. Because I have, I've got nothing to lose here. Yeah. So yeah, I can come and so now I can create my new account, how my new account is created, and we the subdomain, so I can create the test posts, what are we not doing, right? Um, okay, we've defined the relationships in account B and post B, and it must be something here. Uh, I would definitely call in the current account method in, uh, the you, The base account, it's inheriting from it, right? It's help permit, permit current account, fine by the doc sub domain. Uh, try to display the current account in the view J just to see if we have it. Which one? Uh, so in the new view, in the post new view, just the call, uh, current account. Oh, Yes, yes, yes. Um, yeah. So, uh, so we made a mistake. Okay? So I think we are, we have scoped the post to the account, right? But then we Oh, Right, We have, yeah, we have scoped the scope to accounts, so we have to call accounts. Mm-hmm. Uh, because we, we have scoped it to, uh, we are saying it's scoped to the account, but what we are trying to do, we are defining the post within the subdomain, but we are not calling the, where it should be scoped to, right. Because we have defined inheritance. I hope I'm right. We about now, uh, No, Uh, and what do the logs say? Okay, it looks like in the logs it didn't pass the, it didn't pass the account id. Yeah. Yes. It didn't pass the account id. Yes. Um, well, it's either the post controller, um, Yes. Uh, I think, oh, okay. So I think we are making a mistake, right? Because, uh, the problem might be coming from, ah, we Get resources both. Yes. I have not defined our resources within the post. Yeah. So it brings us to the regular, Yeah. Yes, yes, yes. And bring that there. And then he's not finding the account that's supposed, but then we want to, we want you to be within the, um, the, the, because of the constraint we defined it is because it didn't find the constraint. I think it was going through the, the second, uh, resources that we declared, right? Yeah. That one. So let's, yeah, let's see if this one works. I like that. Okay. Yeah. So the form, The problem is time from our form. Uh, maybe the, the other form you have it open, uh, further. Yeah. Uh, accounts both form, try to open it, not, not, uh, this One. Yeah. Uh, this one. This one. Both. Oh, yeah. Yeah. Maybe it should not accounts, but account, Uh, account. Okay. Okay. Let's try. No, not much to lose. Uh, Yeah, it's accounts. I think it's, it's our account, because that's the name of the module we use. Mm-hmm. I'm coming in, let's see. Okay. Yeah. Yeah. So it was the, because because we, we, um, I think what happened was that we were already, we have defined as, we have defined the scope, but the scope is not a namespace, it's actually a race constraint. So we, this, this one would've worked if I had defined in my route that this one is a namespace. If it was, let's say, uh, namespace, uh, maybe accounts, right? Then the route define, then that would've made sense. Because I have to first locate at the namespace where I am before I can go to the resource. But you are not asking me to do that. What you're telling me to do here is that I just have to look for, uh, a subdomain required. Once I get a subdomain required, nothing else matters. So you don't need to, uh, define, uh, my, uh, you don't need to define this one over here because I do need it. You are not scoping to a namespace. Mm-hmm. Because I hope I'm making sense. Absolutely. Yeah. I'm not going to a namespace, so this one wouldn't matter. So then now we can now create our, uh, we can now create our, so now we can know that whatever we create is within the subdomain where we are right now. So let's see if we are not aligned to ourself, let's go to Rios console and see if, uh, Yes. So that this thing we declared is still the post we declared is there? Mm-hmm. Yeah. The current account id, yes. Yes. It's within, it's within there. So we can then, so we can actually even come to now, we can move from a subdomain, uh, yeah. And then create another account, right? Yes. Now we are within your subdomain, right? Mm-hmm. So Well, uh, we didn't add, uh, any validation not to display both, uh, across subdomains. Yeah. Yeah. Yes. So what we did, what we did was that we are displaying all the posts within the, uh, resources, right? So Yeah. And we have to Yeah. Account, Current account of both. Yeah. Yes. Now we are within your scope now. So yeah, I think, yes, yes. So I think now it's, it's settled. It's settled now. So, which means that we can go to all the accounts as well. Yeah. Now I have all the accounts and we know that, so here now you can see that it is moving to another one, you know? So here we have to make sure that on our index page, we have to declare where we have the all the accounts, right? So in the, what is the account index page, right? We have to change the route, right? Yeah. So we have to, We have to change that route. So we have to go to route URL, right? And then subdomain Account. Subdomain. Yeah. Oh, current account. Do we have a current account at the moment? Yes. I think we sub domain equals account subdomain. Uh, We, we don't have, uh, the current account helper at the moment. Yeah, we do using because it's within. Yes, yes. So I think once we do it this way, we can then see, now you can see that the u RL has changed at the bottom of my page. Yeah, I see. So now we can go the respective sub domain and then create our stuff there. Mm-hmm. Yeah. So basically that is that, so the idea is just first, uh, adding a subdomain to your account and then making sure that you dec declare, you declare your subdomain constraints so that whenever the URL request to the subdomain and the subdomain is not directly point to www, you use your, uh, route constraint here. So here you can define all your resources within. Then again, the downside is always that you have, have to make sure that you are calling the current account dot the whatever resource you have defined within. Yes. So that is it for, uh, um, the, the subdomain. So this is how I, uh, this is how I went about implementing my subdomain. Uh, I hope I looks Fantastic. Yeah. So the second challenge would be to push it to production and, uh, make it work in production. Yes. Okay. So now the challenge in production is that, uh, the production, the production way, you know, that, uh, hero Heroku does not accept, um, wild card, right? Mm-hmm. Uh, Heroku does not produce excess l for world caps. So, uh, what you have to do is that I actually use, uh, CloudFlare and then less encrypts. So I use CloudFlare, CloudFlare and then less encrypt, right? So, um, CloudFlare automatically gives you the opportunity to have, uh, SLI mean by default, right? But before you can also get the XSL for your sub domain, you have to be in one of their paid, uh, paid tier, right? But luckily for us when we spoke that I introduced the less encrypt, uh, less encrypt, uh, approach to me. So less encrypts has a way where you can actually add your, you can actually generate SL for your subdomain. So it is actually a script I've, I found online, I've forgotten the post, but it was this post where you have to use less encrypts on your local, uh, app. So let me just, um, show you the script I run rather. Yeah. So, um, so if you're using a sub domain, right? This is the script you have to run. So, so first you have to, uh, install set bot on your machine. Then you have to run, uh, so first you have to, uh, define which kind of challenge your, uh, set your certific, the certificate of XL you are using is defined. And then you tell the server that you want to use less encrypts, API server, and then the directory to use. Then from there, you tell them what domain you want to use. So here I told them that I want to use my word card.tutor.com, which mean however you go, you have to use that, right? So, uh, let me just, uh, for illustration sake, let me just copy this and then paste it here, and then I'll use my, It's uh, a password to vote exactly A password to your terminal, like to your, uh, to your terminal. Yes. So you see, once your password is correct, it'll tell you that, uh, you have to paste this in whatever DNS you are using. Okay? So here, in this case, I'm using CloudFlare, right? So I will just copy this one, go to, uh, CloudFlare here, and then add it to the text. Mm-hmm. So here I have to choose the text one, the TST, and then I'll paste in my link here, and then I'll come back and paste the value over here as well. So I'll paste my value here, and then I'll save it. So it means that what I'm saying now, now what I'm telling CloudFlare is that as long as I'm on a subdomain, I want the SSL certificate to be issued by less encrypt. Mm-hmm. Yeah. Once you save, then you just save it, right? Then once you save it, you have to tell Heroku that you want your, uh, you want your subdomains to be protected by XSL. So you want to give them a certificate, right? And how you do that is, so if you want Heroku to give to you, uh, I don't know the machine you are using, but I think it's pretty much, uh, the same. So now this is how you tell Hero Heroku. So when once you finish, once, uh, CloudFlare accepts your, um, accepts your, uh, lesson grade and certificate, they'll give you two files. This one where they'll tell you where they have saved your certificate. Mm-hmm. Less increase will tell you exactly where they have saved. So here, online 13 here, Online 13, they tell me that this is where my first certificate if I want to go live is right. So if I want to use the certificate, all I have to do is just to tell Heroku that Heroku, I'm using certificate. I want you to add this to the certificate, go to where this file is followed by the second file, which is my private. So you can see that I have two keys here, line 13, I have the full life full change screen, and then line 16 I have the private key as well. Yeah. So here what I'm telling Heroku is that Heroku, I want, I'm adding a new set of certificates, but I want you to find the certificate from this file as long also from this file, you can choose to do it individually. You can actually do it one after the other. So you can say, Heroku had the first one, and then Heroku add the second one. It's also accepted. Yes. Once you do this, whenever you point to a subdomain, let's encrypt will automatically pick the, uh, who add the XSL to it for you, but then Uhhuh. Yeah. Okay. So once again, to clearly understand, uh, where are these let's encrypt, uh, uh, certificates, uh, held at the moment before I run the Commander to add them to Haruku. Oh, okay. So the, when, so, you know, the, the steps we followed, right? Where we copied everything here, right? Yeah. So once you finish, they'll give you, uh, less clicks will tell you, congratulations. Your certificates and chain have been saved at, this is the first piece on your machine. So I think the machine, depending on the, yes, so depending on the, what machine you are using, whether line Windows or Mark, it'll tell you where the file has been saved. So here it is telling me that if I want to look at my chain, it is located in ETC less encrypt folder, live folder. They created the URLI created here for them for my, so they created the folder for the URL as well, and then added the full chain. Mm-hmm. So this is the first certificate for the food chain. And then they added, they also created the certificate for me with the private key. So your key file has also been saved at, uh, less increased folder. And they, so they, they save, they give you two files, both they tell you the location where they have saved it. So all that you are telling Heroku is that it should go on your computer where those files are saved, and then you just copy it. But again, you can decide to go to the directory and copy it and post it in a place where it's easily accessible. So maybe you can post it on your desktop or wherever, and then just tell Heroku to just purchase from wherever it is located. But I found less Encrypts folder Christian quite Okay. So I just use it that way. Hmm Hmm. So that's about it, right? Yes, yes. Basically that's about it. But then, uh, for Heroku at least you have to make sure that you, you are on a paid, you are on a paid tier before you can use XSL. I found it the hard way. I had this up and I, I didn't, I was on the free tier. So you need to have, you need to be at least on the, uh, basic tier before you can use any kind of certificates. And, uh, do you need to manually refresh the certificates from time to time, or no? Okay. Yes. Yes. So Less Encrypts, uh, gives you the certificate for three months. So every three months you have to repeat this process. But I think I was reading a few days ago that they want to extend it to six months, but I'm not sure. But so far it is only last for three months. But when your three months is about to expire, think about a week or so, less than Great will send you an email to remind you that your certificate is expiring. Mm-hmm. So, uh, you should, uh, uh, go about it. Like you should find a way to, uh, renew it. Yeah. Sounds great. So yeah, I'm just looking forward to repeating it and replicating it as fast as possible. Yeah, Yeah, yeah. Yeah, it's, it's, it's, it's pretty awesome. It's pretty awesome. So that is it for the subdomain, uh, declaration. But the key point that I want to drive home is that, one, you need to make sure that your account has a subdomain, and then you have to also ensure that you have defined your constraint very well. I think the bread and butter of it all is this constraint, so that wills will route every request that's not directly followed with www to link to your subdomain. And then you have to make sure that in your, you have to make sure that in your route file, you define the constraint as well, so that, uh, whatever you scope will be within, uh, that space. Yes. Yeah. Great. So, so Yeah, so basically that, mm, so we, with this one, the beauty is that we don't have to necessarily always be defining our route, because here we can just go to this place and then we can go outside the sub domain and declare a post. So maybe if you have users that you want them to declare certain kind of post or something, you can give them that option. And then maybe the users who are on your app and app or something, you have to give them each of the subdomain. Mm. Mm-hmm. Okay. And it would be great if you saved it as a repo and, uh, possibly also those commands, uh, that need to run, uh, for let's Encrypt and for Heroku. And, uh, I am really looking forward to trying to replicate it. Yeah, Sure, sure, sure, sure. That's, that's pretty much, uh, that's pretty much it to me. --- # #16: Fat Model Skinny Controller, Post has many Tags - URL: https://superails.com/posts/code-along-video-subscription-app-16-fat-model-skinny-controller-post-has-many-tags - Published: 2021-09-19 - Duration: 42:08 - Access: free fat model, skinny controller: https://github.com/yshmarov/superails/commit/f5feafb177da05cf8f5f0f0f97330d47a0b50563 merge current_user in comment_params for new action: https://github.com/yshmarov/superails/commit/22e82ba6fff0f5313e2d2db0e32bece73fa98a35 errors patial for form objects: https://github.com/yshmarov/superails/commit/d321e0285872e82bb2606a2205c50c02f31faa04 create tags and taggings: https://github.com/yshmarov/superails/commit/0373d1ff249334edb62f473a119517b7b742cef3 conter_cache comments to comments and posts: https://github.com/yshmarov/superails/commit/762a9c144f3c7f439fb3a97f1fadd08531594e38 --- # #15: Advanced ActsAsVotable Scopes - Bookmarks, Watchlist - URL: https://superails.com/posts/code-along-video-subscription-app-15-advanced-actsasvotable-scopes-bookmarks-watchlist - Published: 2021-09-17 - Duration: 32:57 - Access: free vote_scope for like functionality: https://github.com/yshmarov/superails/commit/e4ea4f2bde0d3b5d9a44948868992fde93f5b18a bookmarks with acts_as_votable: https://github.com/yshmarov/superails/commit/49baa0b9cd0e147d927f9e776656b7999bb0911a add cached like votes to posts: https://github.com/yshmarov/superails/commit/8da73df763dba253f3cf7381daef72879550ea06 add to watchlist, mark as complete: https://github.com/yshmarov/superails/commit/fc78b24336a5ce1f8b5312093d98f6dd217b785b styling new like buttons: https://github.com/yshmarov/superails/commit/abfb659a742c24d732991aa8b94529c0126f253f better way to display upvoters and downvoters of a post: https://github.com/yshmarov/superails/commit/99553d833824e7d73229bae2f24f4c467f718459 --- # #54 Gem VanillaNested by Ariel Juodziukynas (Cocoon successor without jQuery) - URL: https://superails.com/posts/ruby-on-rails-54-gem-vanillanested-by-ariel-juodziukynas-cocoon-successor-without-jquery - Published: 2021-09-13 - Duration: 33:56 - Access: free Gem source code: https://github.com/arielj/vanilla-nested Ariel Juodziukynas on Twitter: https://twitter.com/arieljuod ## Transcript Hello. So today's going to be a special episode. We have a guest star, and this is Ariel, a creator of, uh, uh, new Ribbon Rails gem that should replace the Gem Cocoon. And the gem's name is Vanilla Nested. So Ariel, can you tell something about yourself? Sure. Great. Uh, thanks for having me here. Um, I'm Mariel, I'm from Argentina. I am a race developer since 2010, I think. And I've been doing mostly rub rice over the last 10 or year or 11 years. Um, and I remember at some point during my career, I had to start migrating off from jQuery on the project because we wanted to optimize and request times and those things, and we were using, using the Gem Cocoon. And I was really struggling to remove jQuery because I have all those gems with dependencies. And at that time, I couldn't find any replacement for Cocoon that will use just vanilla JavaScript. And I started just doing some things in-house for that. And over the years I found that it was an interesting idea to create the Gem to actually have that, uh, available for everyone and to also update with the more modern things in Ruby on Rails like I know, using Web Packer. Um, so yeah, I decided to a few years ago to create this gem. It will be like, uh, it's a, a similar gem to cocoon. It helps you add input fields dynamically or removing input fields dynamically for your nested forms. Um, but it uses vanilla JavaScript, so it has no extra dependencies and it's, it has some features that are different than Cocoon, I think more advanced feature, like undoing changes or some different events for the user to, to really, um, connect to different parts of the process to really customize the experience. Um, and yeah, I think a few weeks ago I released this, then I released a new version of the Jam. I fixed some bags that were there for a few, for some time. Uh, but I, I got the time to, to finish fixing those and release, uh, the new version. Yeah, actually I know Cocoon is, uh, one of the gems that I've been using in most of my, uh, applications. And, uh, well, uh, I really love the feature of being able to like dynamically add or remove fields inside, uh, form. And, uh, one thing that has been really holding me back, uh, since Route Six was released, so, uh, uh, now you really try to keep away from using Jake Ferry. And, uh, like when I Google something, I always start to find a solution that doesn't use Jake Ferry. And Kon has this jic very dependency. And, uh, I didn't like the idea of installing the J very just to use this jam. And, uh, like the, I, I remember Cocoon had a few, uh, issues about, uh, like open issues about, uh, uh, rewriting cocoon in, uh, vanilla and, uh, well, um, it's really quite an old gem, so it's really cool that, uh, now you've made some, uh, fresh, uh, vanilla code, uh, and, uh, we wouldn't be needing to use, uh, J vri anymore. So maybe let's have a look at the gem and, uh, how it works. Yeah, sure. Let me share my screen. Um, okay, so the, the gem, like in most modern rails, gem that needs some assets in JavaScript, it consists in two things. One thing is the gem, and there's also an old package for that. So it's all in the same re in the same package here. So, uh, you can use this gem if you are not using web package. You just use the gem, like gem inta, gem nester, and it works with sprockets out of the box. And if you want to use it with web packer, you also need to add the, the note package or using yarn. And right now it's pointing to my GitHub repo. It is not, um, in yarn packages.com. Um, it, and you need, you only need to add the import in your application JavaScript. Um, I think at some point I didn't know how to use to, to set up this thing to Web pack properly and you had to manually copy this, the JavaScript file in your JavaScript folders. But now it's just, uh, it's a hold package. It's work with all the rails version with sprockets or with newer version with, with Web packer. Cool. Um, so the gem consists in two parts. It includes helpers that you can use on your backend, and it includes some JavaScript that will connect your, uh, the, the tags that the helpers created with all the dynamic parts of it with in your client in the client side. Uh, so let me show you the part of the code here. So if you are familiar with Cocoon, it has two helpers. Basically, uh, you have the link to our association and the link to remove association. I had I I ins, I was inspired by that GEM and by the Rails cast, um, from Ryan, I don't remember the name. Yeah. By last name, Ryan Bates. Um, so we have similar method here. You have a link to add nested, and we have also link to remove nested. And there's a lot of things that we can pass here to customize the functionality of the, the customer, the, the style and the functionality. Um, the way this works basically is that it will, you will need a partial with the fields of your, do you want to add or remove dynamically. And it will render the partial as a string and it will add the string with all the HTML content as an data attribute of the, of the tag that we render there in the view. And then we also have the client side part of the gem that is this Java script file with all the, the data connect everything. So it will just, um, when, when the application runs, this is set up so it works with turbo links and without turbo links. So it works in both way. So it just, when the app start is reads all the vanilla nested special classes from those, uh, from those things, and then we connect all the JavaScript. Um, so every time you, you don't need to connect anything, it just, it just works. Uh, but you can connect to a lot of events that happens during the process of the, when you are using the mini, the mini the application. Um, so you even can connect to, I know an event when a field was added, when it was removed, when it was hidden from before undoing, because it, it give you more feature like undo and mm-hmm. Um, it reads, I know, uh, some advanced feature are like you can, it, it detects that you set a limit on your association and will trigger so events, so you can act on that. So I dunno, we can, I think we can, uh, if you want, we can check an application that's using the, the gem and see how it's set up and how it works. Yeah, absolutely. Sure. Okay. So in the same repo, I, I'm going to use the application that we have in the, in the repository, uh, that I'm using for the test of the app. Um, so the first things are that in the gem file, we add the gem. Right now I'm pointing, I'm pointing to the local version because this is the repository, but you, you wouldn't use this part. And then in the package dot Jason, we have the vanilla nested. I'm also pointing to the local file here, but it will point to my, uh, GitHub repo. And then we need, um, different models that we need to associate so we can nest nested forms and we can use this, this, this tool. So one requirement of the, of this two for, for this work is that we need to use the accepted nested attributes for helper that something rails needs. So the fields for are handled properly in a form when you do nested forms and when you submit the form rails will handle this, will handle everything for you. It will set up everything, it will handle the attributes and all the associations and deletion. Um, and we also have, uh, another model that is associated with that. Um, then in this case, we are going to have a user with many pets, for example. Mm-hmm. And you can see here I'm setting a limit of three and that this is a feature of rails, um, that if we validate that you are only that number associations and in pan eselle, when that number is rich, it will trigger an event. So you can react to that, for example, and this the button or show a warning or maybe remove the oldest one. I know you, you can do whatever you want with that. Yeah, I didn't want to add any, the default value. Uh, one option was to disable that when you reach the limit, but I didn't want to force a fear, a fear a behavior. I just want to, I just meet an event and somebody can listen to that and react and do something. Um, so yeah, I actually didn't know that you can set a limit. Oh yeah, it's, uh, I, when I was, uh, doing the research for this gem, I was trying to do to check what are all the features that you can use for accept that use for, and what can I automate for that? And yeah, if you set, if you don't set the limit, you can do anything. But if you set the limit, I know you can say, I want only three pets here. Uh, if you try to save a record with more than three, it will fail. So I wanted to automate that in the gem in the gsu. Um, so this is a test application. So it has many cases use cases. So if you go to the app, it, it has, uh, test for, uh, basic usage is here, but it can also, it, it showcase how to use custom tags by default. It creates, uh, an anchor tag, an A tag, but you can change that to be a button, um, an a span, uh, ad, whatever you want. And it also includes an undo feature. So if you by mistake, delete something, you, you have some time to undo it. Uh, but it's also configurable by default. It doesn't have an feature, but you can set up some time out there. Uh, so let's see. I have this, um, basic form and when I click add pet, it will add more fields to the thing and we don't need to, to, to do a request, right? Um, so that's the, that's the functionality of the gem. When you click DX here, it will remove that. And for these things to work, um, let me inspect this button. So you can see here in this, in the button, we have all the HTML that is being added. Every time you click, it's in the data attribute. So this, this is device area of how it works. Um, let me go back to the code to show you how this is actually set up. So if you have, you have your form, right? It's a basic rails form, and at some point you have to call the helper. That's called link to add nested. And you, you have to set up the, the arguments are the actual form builder yous in here, the name of the association of symbol and the IV of the, of the element where you are going to insert the, the new fields, right? So these are the, the form come from here. This is the name of the association, and this IV is the one I have here in the IV equal parts. Um, this, it's you, you can put any content you want here. You can add a blog if you want to make some complex stuff here, or you can add, um, there, there's a property link text that, uh, an attribute that you can use linked, um, argument that you can do, I know, like add, and you don't need to add a block. So you have the possibility of add more, only a string or more complex, um, content. Mm-hmm. And this is, uh, uh, ba basic rail feature. This is not anything related to the jam, but we need to use fields for the fields for helper. This is connected with accept natural attributes for, uh, we need both working at the same time. So this works, and you just add the name association and it will give you a form builder for the association for one el, each element of association. Of the association. And then we are rendering, um, partial with that, uh, with that object. One thing to consider is that the gem by default will fall back to this, to this name, the name of association under fields. But you can also customize that if you have, let me give you, show you another example. If you have this, uh, a different name for the partial, you'll need to add a partial argument to the helper and you can just use a different name. So you don't, you the by the default is the association in singular under score fields, but you can customize that if it doesn't fit your, your space. Um, we do need the, the partial because it what we use in the helper to, to render this in the data attribute. So we need a partial for that. And the partial will just include your form information, the Oreo form fields, and we need to add this helper to remove the association, right? It is the same, I'm just adding thing here because this is for the test application, but we can also have link text here and say X and that's it. But sometime you want to render an icon, a image style or whatever, and you can customize that here. Um, one important thing is that this helper must be a child of this type because this uses, uh, JavaScript closest method and it take, it, it checks for a parent with some, uh, iv. So it needs to be a children of that element. If you put this outside, it won't work, but you don't need in, it doesn't need to be a direct children. It can be a, a nested children and it will work anyway. Um, so that's the basic setup. We have the, the form rendering a partial, and in those, in these two places, we need to add the, to, to call the helper, the add to nested to create the add button and then remove for link to remove nested to create the X button. Um, okay, so after that's, that's connected, we have, for example, here, when I click this button, it will read the HTML from the data attribute, and it will inject that in the inside this, um, inside this, the element with that id, in this case it's ID pets. So I do that, I add more things, and when I get to the limit that it was reported by in, it was set up in the model, it will trigger an event. Um, by default it's just triggering the event. So I am in this test application. I connected, I'm connected to that event to render this message. So I can show you how to connect to events so you can react and do whatever you need to. Right? So if you go to your JavaScript, I am importing the vanilla nester jam. And then in the document object, you can listen to all the events that vanilla nester is triggering, and it, every, every event will be, will start with this namespace pan nested colon, and then the name of the event. And for each event, you will have information that you can use in, if you have your event ob your event object here, you can inspect that and we'll give you the name of the, the, the actual element that was added or removal, the what thing you click, any information related to that event. Um, there are many events that we can listen to. So for example, let's go back to the rhythmic, no, this is not the rhythm, this is the, so let's see in GI Hub because it looks better. Um, so here in the remi we have all those things that I show you is how to set up the accept nest attributes for the oh one thing. One important thing is with any rails nest cell form, you have to, um, permit the name of the association in plural attributes. This is rails, nested forms, um, a requirement for them and also for with the, you need to add the underscore destroy. Um, you need to allow this, if you want to remove things from your form, then it shows you how to use the, the, the links helpers. And then we have all the, the options we can pass to the, uh, to the helpers. We can set up a different text classes. We can say if we want to add the, the new fields at the top or at the bottom of the list. So we have the instrumental, we can do prepend or append, we can change the motor partial, we can, um, if in our partial the variable is called FF instead of form or whatever name, you can just change that in the, uh, also as a variable because sometimes you need to adapt to a form parcel that's already there. So you can customize the name of the parcel and the name of the variable that that holds the form builder. This is somewhat new feature, uh, even the name of the to render, yeah, you need to always render an A tag. You can render a span, you can render a button, you can render any tag. And I have a, an idea of a next feature that you can also pass attributes for that tag. So if you want to, I know if you have a, a tag and you want to pass, I know, um, a tag index or if you have an, if you are have a button and you want to provide a name attribute, you can do that here, but it's not, it's not there yet. Um, and you can also add content as a block and not as a string. If you need to add more complex stuff like, uh, uh, like tags just text. And you have similar things for the, uh, link to remove nested. So we have the same link text or block classes. Um, oh, sorry, my dog is barking. Yeah. Um, that's nice if you, if if you want by, by default, the way this works is it will, it will use the name of the association underscore fields to detect what's the con the wrapper of your fields. But you can also customize that maybe if you depend on a form, uh, if of a par on a partial that's already built. You don't you want to say, okay, this is the, uh, the selector I want to use, for example mm-hmm. And not add hashtag i some id. Mm-hmm. Um, then let me see, uh, it also has a, a tag, and this is, uh, I think it's a interesting feature that you can undo the deletion. So if you set up the undo link timeout and the link text, you can actually undo the, the removal before that time, time happens. So let me go back to the, to examples here. So there's an example with an undo. Yeah, so when I click here, it will give me an undo. Oh, it's, it's too fast. Um, right now it's set up, let me change that. So it's, uh, with undo, it's too fast right now. So let's do five seconds. So if I open the up and I click here, I have five seconds to undo this thing, and when I click it, it goes back. So if you click by mistake this button, you can, you can, you can revert that, it's not a problem. Uh, you can also connect to the events to prevent the click if you don't want, if you want to show a warning or an alert or whatever. Um, and you can also, you can customize the time and you can customize this string. By default it says undo, but mm-hmm. You can, the, you, you can use an icon, I dunno, a recycle icon or whatever. Um, and what else? Let me see. Oh, and then we have all the events. So for each event that happens during the use of the, of the tool, you will have different events on the document object. So you can have, um, when something is added, you will have this event and with this information, so you can get the name, the container, the, the link that you click. So sometime you may have different, uh, forms in the same page. So you, you want to know which one was the one that you clicked. Um, you also have, uh, fields limit reached. So if you get to the limit, it will send the event. It won't do anything more than send the event. So if you click more times, it will add more fields and, and emit more events. So this is not limiting you, it's just sending an event the same you have for fields removed. So when you click the X, it will trigger this event and you have the information of the thing that was removed. And if you set up an end undo time, it will first tie the element, so you have an, um, event for that. And when that gets removed, it will call this event too. So you can connect to those two steps during the, the removal. Uh, if you undo the hiding, you can also have this event. So if you click undo, you also have an event for that and you can redo something that you did when you click the removal. Um, yeah, I think those are all the features for the, for the Gem. Yeah, actually, um, there is one of, uh, my main applications that, uh, is still running Rails five. And, uh, like I went to the Gem file and uh, uh, made kind of a plan of things that needs to be, um, changed to use, uh, the Gem with Rail six. And uh, some things were like, uh, the Gem was using Bootstrap three and uh, I wanted to use Bootstrap, uh, uh, five and it requires like, uh, some different gems and uh, then, uh, it wouldn't require Jake theory. Also, uh, uh, I didn't want to use Cocoon and uh, like keeping Jake Fury inside the application because of, uh, cocoon was also kind of holding me, me back. So I mean, I'm really looking forward, uh, to trying to replace Cocoon in an existing application with, uh, vanilla Nest and seeing how it goes. Yeah. Hopefully the change is not that different because I am, I was inspired on that gem. On that gem. So it's using similar helpers and the basic idea of having partials nested forms certain strategy for is the same in both. So hopefully it's just replacing the gem adapting a little bit of the, of the code, but it shouldn't be that, that hard. Yeah, I mean, uh, I like the idea that, uh, it's, uh, a new gem. It, uh, was built based on, uh, the existing knowledge. And it doesn't use Jake very, so Oh, and, uh, and I love the and Undo feature. Yeah, that was something I was, I, I had to implement at that point using Cocoon, and I had to listen to the events that Cocoon was triggering to add some thing, but I had to do some hacky stuff, like listen to the, and undo say that information somewhere, then delete set information, and then adding that thing on my CI wanted to have that in this jam because I think it's super related to that, right? It's, uh, it's something you would expect on this thing to handle because you are removing that and you want everything there. Uh, but it's optional. It by default it's not doing that. So, um, it's, it's more versatile. Yeah. And, uh, is your first gem or have you created gems before? Um, I did some gems in the past where I were mainly hacky stuff that I needed for work, maybe extracted something to, uh, weird gem that I only knew how that worked and shared that with my other project. There was no documentation, no anything. Uh, this is the first one that is actually made to be a gem for the public to use with real research on how to do use it, how to do it properly, how to connect with different rail version, how to support different things. And it's uploaded to Rabbit Gems. Um, so this is the, the first time I do that. Um, I'm currently working on a different gem. It's not related to this thing, but it's uh, it's like a, we start, uh, interactive rails, new command that lets you add a lot of tools. Um, I know you can run this command and it will ask you, I know you want to use some, um, template handler and you say, I want RBM handle or slim, do you want to use, um, I dunno, r spec or mini test, do you want to use simple code, yes or no? So it asks you a lot of questions of different popular tools, and it will create a new risk application with all those tools set up for you. This is it, it's working, it's there in, in my GitHub account. Uh, I haven't published that yet in Ruby Gems or like pub on Twitter or anywhere because it's something I want more people to test before doing a real announcement. But yeah, I think it's, um, it's like a better, a better version of the jump. Yeah, actually, I just remember like, uh, a year and a half ago or so I was reading, uh, uh, forum post on the, uh, Ruby Forum and there was this discussion about making the rails new command, uh, more interactive. So I mean, by default you can just say, uh, uh, the name of the app, the name of the database, and uh, uh, which, uh, uh, like if you want to include, uh, or don't want to include some kind of like action mailbox or action text or something like that. So I, I guess that were like a few tools that was spun out of that, uh, forum. Like there was, uh, rails, Neo io then, uh, that there's this jam, uh, boring generators that, uh, just has some templates on, like, you just run the name of template and it installs device or something like that. And, uh, then like Rubidium and, uh, rails, bytes, so kind of couple of, uh, public places where you can host your, um, template scripts and run them in an application. So, uh, your tool would be like, uh, uh, similar, but uh, with the rails new command. Yeah, it's, um, It's, it, it, the idea of the gym is to give you all the functionalities of the, to set up the flags for the rails new command, but also give you more things that are outside of the basic rails new command. Uh, let me show you right now. It's so better that I don't even have a name. It's called Raise New app. Um, so right now it's, it gives you all these different things. So if you want to set up different gems, different advertis, this is just setting the flag from the raise new command. But then I know if you want to use our spec, simple Code, factory, boat, and faker, it will also add your, those gems for you. So it will ask you during the wizard step by step what do you want to add and what do you want we don't need. And these are all the things that it's already supporting. So you can set up different tools for your test, different template tensions, different form helpers, different pagination, he jams. This is also setting just one flag for, uh, for the raise, new command. Uh, you can add authorization with Pan or can, can can, you can add authentication with device. You can set up different links. And one thing that I always have to do when I start re application is I have to add a remote that I have on GitHub. And I, I usually like to change, use main, like we have on GI A right now, that's the default. So I added this thing that will also make a, your first new commit, change the name of the branch and add the remote for you. So you don't need to do those state manually. It will just ask you during the, the process, you just press enter at the end and you see doing a bunch of stuff, adding gems, doing everything, and you are there with your unit application with all these tools set up for you is the basic set up. Then you have to customize and add more things, but at least you have the bare bones working. Yeah, Azure, like this, uh, idea of, uh, like, um, setting flags, uh, then join your rails new command, but not, uh, I mean not, not starting with some kind of uh, uh, just template, uh, that you've created, uh, a year or two years ago. Uh, or some kind of random template from the internet or let's say, uh, uh, just, just finding some kind of a script on, uh, how somebody wants installed device. So I mean, looks, uh, kind of promising to me. I mean, uh, it's got, uh, tests, it's got, uh, what, what do I need most? I would need, uh, like a Rubin, so like robocop, ivid, guess, then pec, uh, uh, I guess stimulus. And, uh, I, I like the last one. So GI at remote, at branch name and so on. Yeah. And some things I want to add to this is, for example, to already set up your Desire CI file, I know, or Adobe a container. So you don't need to create a new, I know GitHub actions file configuration. It will read your setup and will just add the commands you want on GitHub on ci. Yeah, I shall, I like That for GitHub actions. Yeah. So will like all this ven, when, uh, I install robocop, uh, the next step is, uh, setting up GitHub actions for robocop. So it, uh, I mean, uh, I think it's a must have. Yeah. Uh, but yeah, this is a better thing if people is interested into trying this and give some feedback. That's great. Um, for me, I've used that and it's working, but I haven't tested that in too many places and I developed this on Linux, so I don't, I haven't tested that on Windows, for example. So yeah, if people is interested on this, that's great. Uh, you can see that in my, in my GitHub repo. Cool. Uh, Val. Okay, Ariel, uh, thanks a lot for sharing, uh, IL and nested with me and, uh, with me today and with the world on GitHub. And, uh, yeah, I'm really looking forward to trying to implement it, uh, locally and trying to replace, uh, cocoon in running application with vanilla and nested. Great. Thank you. Thank you for having me here. Yeah. Um, I, I love to talk about all these things. So I think it's really directing to, to be able to show the code and see the reason why I did something. Because, uh, sometimes it's, I, I like this idea of when I read, when I see a new gem, I sometimes I don't understand what's going on, I just use the gem. But I think it's interesting to know why the gem works and how it's done. I mean, the, like, I, I could just, uh, do it on my own. I mean, I would just open the jam, look at the rhythm, try to implement it, uh, locally and I know record a video. But, uh, I mean it's, uh, in my opinion, 1000 times more valuable if like, I have the opportunity to speak to you and, you know, well the way you wrote the code better than anybody, and then you go through and explain it and the features of the jump. So thanks for that. Yeah, that's great. That's a really good idea to have, uh, developers talking about their, their creations. Yeah. Okay. So then cheers. Great, thank you. --- # #14: Infinite Nested Comments, Embed Vimeo Videos - URL: https://superails.com/posts/code-along-video-subscription-app-14-infinite-nested-comments-embed-vimeo-videos - Published: 2021-09-10 - Duration: 23:49 - Access: free Fix all bugs with nested comments: https://github.com/yshmarov/superails/commit/9a567ce5a9a9a709345f86757c5ab69b3a4009e6 Embedding Vimeo videos: https://github.com/yshmarov/superails/commit/e347bc85aaca2708a39fbcd84e83710b050eb0d1 --- # #13: Polymorphic Comments, Nested Resources - URL: https://superails.com/posts/code-along-video-subscription-app-13-polymorphic-comments-nested-resources - Published: 2021-09-09 - Duration: 59:12 - Access: free basic polymorphic comments: https://github.com/yshmarov/superails/commit/1dcc3181c2027b73bb1d239b2535a37d1320c5da css for comments: https://github.com/yshmarov/superails/commit/76c3fe0007bc019783e0fca9b136189a827fe908 reply to comments: https://github.com/yshmarov/superails/commit/632cb9ad4711b596813eebf3cf991589e978e639 --- # #12: Vote stats, gem Meta-Tags, Friendly ID: history & finders - URL: https://superails.com/posts/code-along-video-subscription-app-12-vote-stats-gem-meta-tags-friendly-id-history-finders - Published: 2021-09-08 - Duration: 51:40 - Access: free create posts through current_user association: https://github.com/yshmarov/superails/commit/cca5fd8a0d0d6a4f00ee67c5a160ae7489e5360b vote search scopes for users and posts: https://github.com/yshmarov/superails/commit/d79c121c815726b29171da8c8fdc19b7dcc98bc7 gem friendly_id finders and histroy: https://github.com/yshmarov/superails/commit/2d47ffb1ddb5f8b7014de50464fa8fe64addabf2 dont display zero votes count in progress bar: https://github.com/yshmarov/superails/commit/79f3dd776445e23e3ab69b623f4444ee4d66e99c gem meta-tags: https://github.com/yshmarov/superails/commit/d489756cc1f1b181e90f86c909d5ba9ce113ff1b --- # #53 Gem Public Activity - add an Activity Feed to your app - URL: https://superails.com/posts/ruby-on-rails-53-gem-public-activity-add-an-activity-feed-to-your-app - Published: 2021-09-07 - Duration: 21:27 - Access: free Gem source: https://github.com/chaps-io/public_activity Episode source code: https://github.com/corsego/53-gem-public-activity/commit/e598b93ae8942479c62a092c01299493ae2cc3da ## Transcript Hello. So in this episode we are going to implement the gem named public activity. The gem lets us have total surveillance of everything that happens inside our application. So imagine we're going to have a separate database table inside our application where we're going to keep track of everything that happened to our post our users or to whatever we have all the great, uh, update and, uh, distractions that happen inside our applications can be tracked, uh, by public activity. And we can have an activity feed where we will see who did vote inside our application and not miss anything. So here's the gem and here's an example feed. So we can see who created the post, who removed, who edited the post, and so on. And we can have this not only for our post, but for users and for any models inside the application. So let's implement the gem and see how it works. Now we see it's again, a wildly popular gem, really used by, by a lot of, uh, repositories. So let's install it and uh, try to see it in action. I'm going to add this gem to our gem file. Okay. And now we are going to run bundle and next we're going to run a migration rails, generate public activity migration. Now again, I run bundle and run rails, generate public activity migration. Okay, let's have a look at this migration. I'm opening DB migrate Great activities and what do we have here? So you have end new table named activities and it has trackable, well, they're going to track either a post or a user or whatever. So it's going to be polymorphic, meaning these are going to be two attributes. It's going to be either user or post. And it's going to be the idea of the user or post. Then they're going to have an owner. This can for example, be their user that, uh, did this activity. So it can be, usually the current user will be saved here. Then they're going to have the key. It'll be something like post update or user dot create. Then parameters. It can be anything you that you can want to add on your own. So let's try to see it in action. I will run rails to be migrate. And uh, next we are going to need to add this to lines inside our model. So we are going to go to our app model mod post RB and we will add, include public activity model and tracked. And now we can actually see if it is working. I will start the Rails console and I'm going to check if we have any activities. Now how can I check if we have any activities? If I just try activity dot count, we get nothing because we have no, uh, constant as activity. So we need to kind of initialize the activities and uh, uh, have a way to view the activities. Now to do this, we are going to run public activity Activity dot count. Okay? And now we have zero activities. So now we are going to create a new post. I will say post create, uh, title, uh, with an activity. So we have created a post and let's count the public activities. You see we have one activity. Let's say we want to have a look at this activity. I'll say public activity lost. And we see, so the checkable type is post the ID is 13. Uh, we don't have any owner sign. The key is post do grade, and we have the time when the post was created. So it was say inside the activities, uh, database table. And uh, that's basically the basic usage of public activity. Now, let's actually have a place where they're going to display all the activities inside our application. So, uh, let's, uh, maybe create a new view for this or it can be just inside our home view. So I'm going to go to the controllers to application controller, no actual to dashboard controller. Yeah, and say public activity activity. And we are going to say add activities equals public activity. Activity Do order. We'll order them by the creation date created at the send in. Okay. And let's display these activities. So I'm going to go to views to our dashboard and inside index I'll try to display the activities. I will say, uh, at activities each do activity And I'll try to display something from the activity. I will say equals activity. Do what do we have here, do to trackable? Okay, let's see if this works. So here we have, uh, well the trackable is the post. That's why we have uh, the name, that title of the post. Let's just display some additional information. Let's see when the activity has been created. So activity do created at then, uh, they're going to have some additional information. What else would we want to have? So can actually be a link to the trackable. So, uh, link two, activity trackable in the title and activity, trackable in the description. And you see it now it is a link to the post. Now why do we have it as a link in the PO to the post? We didn't define that. We want to have the post title. It is because here in post, uh, I defined that two string would be titled If I remove this two string, then you see we just have the post object. 'cause the application doesn't know that we want to get the post, uh, title. So, uh, let's actually remove the two string from here and say a checkable title. And now we declare that we want to see the post title. Even this is a post. But what if you don't want to have, uh, activities only for post? What if you want to have activities for users too? Well, we would do also include this public activity into our user model. Let's do this. I'm going to just include public activity into our user model and let's, uh, actually destroy the current user. I will cancel the account and now log in as another user. Okay, I will sign up and you see we get this error. So undefining method, method, title for user. Well, because the user doesn't have a title. So, uh, if the activity is the post, then uh, if the checkable is the post, then we want to have the title. And if the checkable is not the post, but the user, we don't have maybe the user email address. So we would basically say if activity trackable type is a post, let's see if this works. And if the activity trackable type is the post, then we have this link. And let's also add some kind of row, some kind of break. And if the type, uh, is not post, but uh, user, we would have a link to the user's email. Or actually we don't have a user part at all. So we don't need any link, uh, if it is the user part. Anyway, let's have a look now. So we already have three different activities. Some of for posts, some are for user. Let's just declare that we have, uh, let's just declare what kind of activities these are. Let's say Activ key. And here we see user create, user destroy and post create. Now they are not in the best order. Let's order them by ascending. So looks fine. And let's just, uh, go back to the styling and say that we want to see the key before we see what kind of activity it was. So we have the time, we see what has happened post Great User Destroy. And uh, in the cases of post, we're also going to display the title of the post. Looks good. Now what if we try to update the current user? I will go and update my password. So I'm going to input a new password and update and see the user update has also been saved. And what if you don't want to save the user update? Also, let's go and change the order. Have you done it? Now? It is ascending. Was should be descended maybe. Let's see. Yeah, descendant looks better. So the new activities are on top. Yeah, so what if you don't want to save then a user has been updated. Well, we can save only particular activities in trackable if we want. So going to our user rb, we can say track and we would say that we want to track, uh, only rate and uh, destroy. So now we're not going to track when a user updated his password or updated his account in general. Let's try once again. So now we have just one user update. Uh, okay, it gives us an error. So a syntax error. So there shouldn't be a coma here. Okay, I will go and update my account once again and you see no additional activity has been saved. And also what if you want to see which specific user updated or created a post. Now I go to posts, I go to create a new post and uh, a new activity has been saved. Here's the new activity, but we didn't save any owner for it. So let's try to find the owner activity owner and well, there is no owner. So let's assign the current user as the owner of this activity. How do we do it? Well, we would need to add to lines to our application, uh, controller and to our post rb. If we go to the wiki of this public activity, we can seek quite a few useful, uh, options. If we find user, yeah, let's set the activity owner. So we would include this, include public activity, toe control inside our application controller. And it would let us kind of get the current user information inside our model for the public activity. So let's go to our application controller and I'm going to add include public activity store controller. And now I'm going to add this track to owner inside our post rb. So we'll have not just checked, but we're also going to assign the owner. And by this line they're just saying that we are going to save the current user as the owner of this activity. Let's try once again. So I'm going to, let's say edit the same post update. I go to home and we see that the post has been edited and the user has been saved as the owner. Now we can also have a link to the user's account. So let's say link to activity owner, activity owner and undefined method, user path. Ah, yeah, of course we don't have, uh, a defined user. So let's say just, uh, activity owner do email And uh, some of the activities don't have an owner and don't have an email. So we will say activity owner. And if you have an owner and the owner has an email, then they're going to display the email. So looks quite good. Okay, and what if we want to track the owner for, uh, the user? So here they're going to also assign the owner. It would say, uh, owner, it'll be itself. Now with this public activity, we need to write not just sell, but itself in this case. So let me, let's say, uh, delete my account. I cancel my account and I'm going to create a new account, Okay? And go here this see, uh, have user destroy and user create. And we have the email of the user that, uh, has been created. Save the, the, is it save the, yeah, it is saved as the owner. So looks good. This way we can, uh, actually, uh, save the record itself as the owner of this activity, but not, uh, the user. So not all this the user, the current user has to be an owner of a specific, uh, activity. And what if you want to, let's say run something in our console now. So let's say Rails console and I will say post great, uh, title, uh, with activity tool. Okay? And here we get a rollback, it doesn't work. And define method, current user. Why doesn't it work? Because here when creating this activity for the post, we try to find the current user and we don't have a current user. So we would like to disable public activity when we are running, uh, seeds, uh, or when we're working in the console. So we would say something like Public activity dot enabled equals false. And now we can run this command and it works and it is not saved in public activity. Now this way we can, uh, disable public activity then enabled through enable public activity totally globally around our application. But we can also do it, uh, on uh, Uh, lower level. So we can do it uh, just for a class like post, uh, public activity off or post public activity do on. So looks, uh, quite fine. And what if we want to get, uh, all the activities for a specific post, for example. So what, uh, if we go to our posts index and we want to see how many activities and uh, get some information about all the activities for a specific post. So the easiest way and the most beautiful one would be to have a relationship. We would add a relationship. So we would say that uh, a post has many activities as trackable because we are going to see the activities that disposed is trackable. Then uh, the clause name will be public activity activity like this clause name. Okay? And actually if just in case you want to destroy activities associated with the post, when a post is deleted, you can also add dependent Destroy right here. So let's see if it works. I will uh, try to list how many activities each of the posts has by just getting post.activities.com. So going into our post index, I will, uh, add an activities column And I would say post activities dot count and we see how many activities are associated with the specific post and we can get their IDs. So block id, so like this we can, uh, access the associated, uh, public activities to the specific post. So looks quite nice. Okay, and one more thing that we can do is run and add some pub, some public activities that are totally custom that I associated not just with uh, uh, create destroy or update. So we can say something like, uh, post dot, uh, has been updated or post not status updated or whatever. So let's try adding a custom activity and we would do it somewhere in our post controller. So let's imagine that uh, when we have update, we are going to create a custom activity. So let's, uh, if post has been updated, add a custom activity for this. We would say add post grade activity. Then we would say the activity name. It would be, uh, so like title updated or whatever. And uh, we can actually add some kind of parameters. So parameters and inside the parameters we would say something like, well, we can add anything. We can say, uh, time zone. Now let's see if this works. So I'm going to update a post. Okay, synex error, let's say, uh, time zone. Okay, yeah. So we have to have a key and a value in this parameters and let's try to update some kind of post. So I'm going to go to edit, add some content Post was successfully updated. I go home and have a, see two activities have been created. So one has the key title updated and the other one has just the key update. And we've also saved parameters. So let's add this parameters into our view. We would say, uh, activity and dot parameters. So here you see for the other activities, we don't have any parameters, but here we've, uh, well we've decided to save the time zone, so looks good. Let's, uh, go back and actually disable this post update so that we have only this post of title updated activity when run. The update action to do this would go back to our post controller and here would say post, uh, this, uh, public activity off. Then we would run update, and then again we would say post public activity on, let's see if it works. I will, uh, go and edit the post. Once again, you see just two activities were added at the same time. We don't need that. I update the post And you see the post update activity doesn't work anymore. It is overwritten. We disabled it and instead we run this activity post title updated. Now again, you can, you don't have to do it here, you can just disable it inside our post rb, the same way we did it inside our user rb, they said that we don't want to track anything except Create and Destroy. And here we can also say check only, uh, create and Destroy. So that's basically it. You see, we can have, uh, a database table with all the activities then going to our posts. We can get all the activities for a specific post. We can get all activities that were done by a specific user. And, uh, looks nice. Yeah, and that's basically it. So thanks for bringing me and have fun implementing this feature in your next application. --- # #52 gem Friendly ID: The Complete guide. Generate readable URLs like a PRO - URL: https://superails.com/posts/ruby-on-rails-52-gem-friendly-id-the-complete-guide-generate-readable-urls-like-a-pro - Published: 2021-09-01 - Duration: 18:43 - Access: free episode source code: https://github.com/corsego/52-friendly-id/commit/00989cb4dc0a14409e067220707e24498208bd4e gem source: https://github.com/norman/friendly_id official docs: http://norman.github.io/friendly_id/file.Guide.html ## Transcript Hello. Today we're going to go through a really interesting problem, rub rails. So if I go to my posts and create a new post, whatever the title is, here it is, you see everybody sees the ID or the post by default. Instead of seeing an SEO and human readable friendly, uh, URL to the post, everybody sees just the id and the person can also know how many posts they actually have inside our application. So this way, by default, we show the ID of uh, each record in our database and we don't want to show the id. Why should they tell everybody in the world how many records we have in our database? Instead, we want our URLs to be SEO and human readable friendly. So to do this, we're going to use the most popular solution in ribbon rails for this, the gem friendly id. Now there are actually a few different gems for obfuscating uh, IDs and uh, showing human readable URLs. But this one is the most popular and uh, widely understood solution. So you can see that there are 30,000 uh, public applications that are using. So it is extremely wildly popular. Now, how do we install frontland D and use it? So here's again an example. Instead of showing the like state's just ID or whatever, we can show a human readable one. So going here to the gem, we are going to add this gem to our gem file. Okay, and what is next? Yeah, of course we'll run bundle and then they're going to add a migration, add SL to well to posts. So the slog is uh, the human readable idea that the post is going to have. I will say, uh, I'll stop the server and say, rails generate migration. Add the slog to posts and the slog should be unique. Now slog is the database attribute inside the table where they going to keep our human readable. URL. Let's have a look at this migration migrate at slot to post. Here, you see they're adding in the field and they're saying that it uh, should have a unique index. Okay? And next it tells us that we can run rails, generate friendly id. Let's see what happens if we're on this command. So you see it creates an initializer and it creates an additional migration. And what does this migration say? So it suggests us to create an additional database table with friendly ID logs. Now they're going to come back to the Slater. We don't need to try to be, so I'm just going to remove this migration. We're going to go first of all with a very lean approach to install friendly id. If you don't need this additional complexity with an additional database stable in our application yet they're just going to add the slog to our post. And also I can remove print the ID from our initializers because we are not overriding it yet. So, okay, we can run rails to be migrated and let's do that. And it says, so we can add the friendly ID to our model to say that we want to actually use it. So we would add something like this to our posts. Let's go to app models post. And here we would add something like extend friendly id, friendly id, uh, well friendly ID would be taken outta the title. We don't actually need this here. So we are telling the application that inside the post model we are using the extension friendly id and that that slog, this friendly QRL should be taken out of the title. So it should be generated based on the title of the post. Let's see if it works Real server. Now I will go to post and create a new post. I'll say how to use friendly id, some kind of content and create a post. And uh, it says couldn't find post with id, how to use friendly id. Okay, maybe I should restart the server. Re ah, no I shouldn't. Okay, here is the problem. Couldn't find post with id, how to use friendly id because we should actually tell our controller that we should find the post, not by the regular ID but by the friendly id. So we would need to go to our controller and add this friendly to our pres. So I would go to controllers post controller, and here inside post we would say Post friendly, find PREMs id and if I refresh and if I also start the server, you see now we have a friendly URL. Let's try with remote post, let's say installing Ruby on rails. And you see that friendly URL has been generated. So it works. Now if you don't want to add the this friendly to your controller, instead you can try saying something like, uh, I'll just command this out now. So you see I've removed this friendly from the controller and inside outpost a B we can say that we are using frontlined for title use slog and also use finders. Now I will refresh and here when we add this use finders, it'll automatically kind of add this uh, friendly to our set post. So we don't have to do anything inside our controller. We just, uh, added this friendly through our model. And you see everything works. Now what if we check some kind of old record? So here are some records that have been previously created. I open the record and here you see, we see the idea of the post, but not the friendly URL. Now to update all records, we will drawn recommend like, uh, uh, user overall post find each and save. Let's try this Rails console post, find each and save. Okay? And let's check in the console P equals post find with the ID one. And we can say P, do friendly id. And here see the friendly ID of the first post. Let's say P equals post find 13 P friendly id. So here you see, uh, the friendly IDs for all the old posts have been generated based on the post titles. So seems to work well. Yes. And actually we can also find the post not only by the ID now, but we can find the post by the friendly id. Let's try post dot find. And inside I will add, uh, this. Okay, and here you see we found the post by the friendly id. Now if, uh, what if they remove this, find us from our friendly ID inside the model, let's see, I will say rails console once again. And I will say post find install rails. You see it doesn't work because we've removed finders from our model, but if you type post friendly, do find you see it works. So you should have this friendly inside your console. If you don't have it in your model, you should have it in your uh, con controller if you don't want to use, uh, finders inside your model. And that's basically it for this finder method. Okay, going back, let's, uh, exit the console and see what else friendly ID can offer us. Now here you can see in the description that it says that friendly ID is as Swiss Army bulldozer for using the, for making the URLs friendly and ol because it has a lot of different extensions. Now we've covered just the basic usage. And now let's try something a bit more, uh, complicated. So let's see, let's uh, see two posts have the same title. What will happen? You have, uh, let's create two posts. So same title, this post, and let's create the post. And I will create one more post for the same title. So you see we've created the post and it has the same title and afterwards it has an additional automatically generated hash. So this way it allows us to have multiple posts for the same title, but new post would have some kind of additional hash afterwards. And uh, what if you want don't want to have some kind of additional hash afterwards. Well, what if you want to have some kind of part of the content of the post afterwards? Let's, uh, try. Now if we go into the deeper documentation of friendly, let's go to Vicky and go to this guide. There is such a thing as slug, uh, candidates. So candidates basically, if the name is already taken, then we can also use the name and city and so on. Let's try to use slug candidates and uh, see if we can use the title and name, uh, the title and content of the post if the title is already taken. So we would add something like deaf slug candidates And we would have the title. And afterwards we'll have the title and the content. Okay, let's see if it works. So once again, I'll just in case restart the server and I will create a new post with the same title and add some kind of content, some content. Okay, I press create post and let's see. Okay, it didn't seem to work. So we don't have the content inside our post title. Let's, uh, try once again. Okay, it didn't seem to work either. Let's see, maybe they're missing something. Possibly the second part should also be a stream. Okay, I understood what my problem was. So instead of using title for friend lady, I should actually use the Slack candidates right here. So let's try on scam and see if it works. I will go back and create an additional post with the same title. And let's see, I create the post and this time I'm using Slack candidates inside Friendly id. And let's see. Okay, it still didn't seem to work. Now did I definitely say Slack candidates? Here I'll try it once again. And it still didn't work. Now I didn't talk this time because the content was blank, so that's why it was generating just a random slide later on. Once again, I will go and create a post of the same title and some magic content and create the post. Let's have a look at the URL. So see here we have both the title and the content. So this is how you can use this option for slug candidates. Looks School doesn't it. Now what if we have a case, then we create a post. So the post has a title and we want to change the title of the post. What will happen? I go to edit, same title, one to three, update the post and does the slack change? No, it doesn't. You see the slack hasn't changed. But what if we actually want the slack to change, then we change the title of the post. Well, we would do add another option. We would add an option as uh, should generate new friendly id. And yeah, we can say that it should be either true or false or conditionally true. So we would say that yes, it should uh, generate new friendly id. For example, if the title has changed, let's uh, see if it works. I will, uh, go and edit the post and let's say 1, 2, 3 A, B, CI update the post. And let's have a look at the title of the post. You see it has changed to same title of 1, 2, 3, A, B, C. So this way we've updated the title of the post. And if you go just the same title, one to three. You see we have nothing. There is no such record, but there can be a problem. What if, uh, a title of the post changes and uh, there were previous links in different places to the previous title of the post. So people would have, uh, 4 0 4, uh, no such URL error. Well, we don't want people to be redirect to our posts and uh, just get an error that something is not found. We would want, uh, old URLs to redirect to new URLs. And this is where would use the friendly ID option named History. Now this is a really powerful option and uh, this is where we are going to need this additional database stable. So they're going to keep all the past friendly ideas of all the posts and all other records inside our database and link them to the existing URLs. Now let's see how it works. I'm going to actually run this migration rails, generate friendly id. Let's do that. And here you see it's adding this great friendly ID logs. So new database table where we keep all the history, all the previous uh, URLs to all our posts, to all our users and all our other records that are using friendly id. So they're gonna run Rails to be migrate and they're going to say that we will also use History. Now we can also see this option inside our official guides. So again, it's avoidable poles and SLS change. So we are going to add history and let's see if it works. So I will start the server rails S okay, I'll restart the console rails s go to some kind of post. I'll create actually a new post. Let's name it with history. Okay? And now I will change the, I will go to this. I will go and change the URL. So I will edit the post with history A, B, C, I update the post. You see that URL has changed and now I'm going to the previous URL just with history. I refresh the page and it works. So see that all your elder, the post works though, it is the same post And this is fantastic. So here is an example of using the history and actually we can also access, uh, all these methods, uh, inside the console. Let's just try Rails console. Uh, let's say B one equals post Do great title. It'll be, uh, using friendly id, let's say P one friendly id. Here it is, let's create another post with the same title. And let's also add some content. So content will be whatever. And let's check the front lady of the second post. I will go and say bit two friendly id. And here you see we have the title and the content because we're using Slack candidates. So here's a perfect implementation of, uh, friendly id and that's basically it. You see it, uh, has a lot of options and uh, you don't always need to use history. You don't always need to use Slack candidates. And in many cases you can just have one URL that is generated on the creation of the post and you don't need to re generate it. And uh, again, you don't always need to add this additional database, uh, table. You don't always need to add this additional history or complexity based on your application. So that's basically it. That's how you can use friendly ideas, this, um, knife for generating URLs in Rubian Rails application. So thanks for being with me and uh, have a nice day. --- # #51 array of checkboxes in a Rails app - URL: https://superails.com/posts/ruby-on-rails-51-array-of-checkboxes-in-a-rails-app - Published: 2021-08-27 - Duration: 13:58 - Access: free episode source code: https://github.com/corsego/51-checkbox-array/commit/dc7183f680bc48184e6481c458611adae0706505 ## Transcript Hello. In this episode, we are going to go through a really interesting problem. What if we need to have, uh, a lot of different check boxes in the same model? Well, we wouldn't want to really add a lot of bullying, uh, fields into our database. We could just keep it all inside one, uh, column and have an array of, uh, different check boxes. So that's what they're going to go through. They're going to add an array of check boxes. Now, here in the draft application, we have a model of posts, and we can create a post with a title and some kind of content, and they're going to add a few check boxes. Well, for example, the check boxes can be different GDPR, uh, agreements or tags or uh, whatever. So let's try to add some check boxes. Now, first of all, they're going to run a migration. Let's say rails generate migration. Add the tags, two posts, and the texts. Oh, the, yeah, the tags are going to be, uh, a text field. Okay, now let's go to DB Migrate. And they're also going to say that it is going to be an array. So array true. And by default it is going to be, uh, just, uh, square braces. So just a sign of, uh, this being an array. Okay, let's run the migration rails, the beam migrate. And, uh, what would be next? Let's try to play with this array. So if we go to our schema, uh, let's reload. We can see that our posts have, uh, a text field, uh, with text that is an array and that has, uh, uh, square braces, uh, by default. Let's play around with it. Let's go in and say post dot count, no post. Oh, actually we do have on post, let's say post do first, and here it is and has an empty array of tags. So let's say post first tags and run any tags. Let's add the A tag. Let's say, uh, post equals post first post tags, and let's add some tags. Now, there are a few different phase to add tags. We can say like this. So it's uh, an error function. So we would be adding a tag named, for example, Ruby. Okay, let's say post tags and have Ruby and let's say post save exclamation mark to actually persist These changes, you see it has been added. So again, we have post tags and it says Ruby. Now there are actually a few base to add the values to n array. We can say either this, uh, thing or they can say post tags, push. And we would add something like rails. Okay? And let's say post save. And uh, now the post has two different tags. Okay, let's add one more tag with another way. Let's say post do tags plus equals, uh, let's say, uh, ruby on rails. Okay, no implicit. Yeah, we should actually put this in square braces in this case. And let's say post save. So there are three, uh, there's are basically three ways of adding, uh, uh, value into an array. And now let's actually check if this, uh, post has a specific, uh, uh, value in this array. Let's say post, uh, dot tags, dot include. And let's see if it includes something like full. Now it doesn't, let's say if it includes Ruby, yes, it does. Let's say if it see, if it includes just something like rule, uh, no it doesn't. So you see it looks for full, uh, values. And this way we can look for a post that includes something. And now let's try some scopes. So let's say post there tags and where tags has something like Ruby and it doesn't give us anything. Why doesn't it give us anything? Because we are looking for something really direct here. They're looking not for a part of an array, but for a complete array. So if we looked for a post that contains the whole array like this, then you see we find this post and be able to find a post that doesn't, uh, contain the whole array, but contains just something we can write a slightly different back laws. We can say something like, uh, tags and fashion mark, uh, and we would say something like, Ruby, uh, I didn't close the gaps and close this gap. Okay, let's try once again. So a post that includes ruby close gap and it works. Now let's try adding just one more post for an example. I will say post great title will be second and content will be second. Any saved created by not post. And this post doesn't have any tags. Let's just add some kind of tags. Let's say B equals post lost P dot tags, and we'll add some like ruby and p save. And once again, let's look for all the posts that have the, the tag, Ruby, let's say post dot where tags contains ruby. And we have to post, and let's see the post that contains ruby and rails. Oh, actually it looks for all. So here we have again, post dot count. We have both because one post contains, uh, ruby, the other contains rails. So we can all use this all inside the, our, uh, rails application, not only in the console of course. Now let's try to use it not in the console, but inside our application. So from the user side, let's start the server and let's, uh, have a look. So here we have a post and we want to make it possible to see that text. First of all, we'll see the existing text that we have just added in the console. Let's go to our app views posts index. Let's try to display the text. So post tags, okay, so here we have Ruby, and here we have the other three tags. Let's try to create a new post without any tags and see what it shows us. Going back to post, you see it is an empty array and it doesn't look really friendly and human readable. So we can do something different. We can say post text, two sentence. And here you see we have blank. Here we have ruby. And here we have coma, coma. And the last console rub on rails. Or we can say something like post dot tags. Dot map. And inside map we would say something like tag And tag dot capitalize. And here we would have it slightly differently so we have them capitalized and we can say some, again, two sentence, you see. So we are just playing around with this, uh, string here, or we can actually go through each of them. We can say post tags, dot each do tag and display the tag. Let's see if it ox well. Yeah, so okay, now we can display our tags and let's actually make it so that we can add new tags, uh, from our views. So first of all, we'll go to our controllers and add tags as an array in our strong programs. So I'm going to say tags, tick files, and empty array. So now we've right listed them. And now let's see which tags are going to be available. So see in this case, uh, it is cool, it makes sense to use an, uh, array inside the both table if, uh, we have, uh, finite. So, and not an endless number of possibilities. So for example, in our application, there can be only three different texts or 15 different texts, and if we don't tend to add the, any new ones from the client side, so that's, then it makes sense to, uh, have this kind of check boxes that we're going to add. So let's go to our posts, uh, form for example. And here we're going to add an array of check boxes. So, uh, first of all, we'll define the options, the tags that are going to be available in our application. Let's go to our post RB and let's say tags equals uh, uh, let's say Includes ruby rails, ruby on rails, JavaScript, hot wire, and stimulus. Okay, and let's, uh, say something like, uh, e uh, uh, post tags. H do, uh, key. So they're going to have them as keys. Okay? Now they'll have an end statement. And here we're going to, let's say just display all the keys, let's say equals form dot label, uh, key, let's see if the keys. So all the tags get displayed and here are the tags. So we have the tags, and now we want to have a checkbox next to each of them. So we would say form dot checkbox, uh, checkbox. And here we would have, uh, the tags and then we would say multiple at, uh, is true. And, uh, let's see what it gives us. It would also say key and nil to make it work. Let's see. So we have all these tags and check boxes. Let's see if it works. So, uh, with check boxes and we'll say it'll have rails, stimulus, And uh, that's it. I'll just say it in the content and I will check box, rails and stimulus. I press create post. Let's go to our posts. And yes, it has rails and stimulus kind of works. Let's go to edit. And you see none of the check boxes are checked. So if I update the post, what will change? Uh, nothing has changed. If I go back and just try to put Ruby, let's see if anything changes. Yeah, so it sets Ruby, so it is not kind of cool. You see that check boxes that are actually selected are not checked. And to make them checked, they'll need to add the something additional here. So I will say checked. And the ones that are checked will be form object. Do uh, tags and if they run any include uh, key E two s. Okay. And now it'll try to find any texts that are already checked and check them. Let's see if it works. Yes, it seems to work. So let's go to another post. I'm going to the post that we have through different tags in. So here we have through tags, it works. I will add hot fire and JavaScript. Let's see if it gets updated and works. So you see now we can actually have an array of check boxes and it looks really cool. And, uh, now it can be used not only for tags, it can be used for, uh, a lot of different things like GDPR stuff or, uh, different things that characterize your, uh, modal. And it's really cool that you don't have to add the, an additional complexity, but adding, uh, new database table and making relationships like a post has many tags or many G-G-D-P-R bulls, whatever. And you don't have to have a lot of bulls inside your, uh, post. So it's a great way to leverage a raise inside your ribbon rails application. And you see it's really easy. All they've done is we've done this, uh, migration. So text, data, text, text because it can be quite long, uh, array default square braces. Then we went to our, uh, post and said that we are by listing text and array in the form. We've done this, uh, uh, check boxes for each tag. And in the post we've said which tags are available. And that's basically it. So thanks for being with me and, uh, have fun learning. --- # #50 ActiveRecord Enum - when and why to use enums? - URL: https://superails.com/posts/ruby-on-rails-50-activerecord-enum-when-and-why-to-use-enums - Published: 2021-08-24 - Duration: 22:24 - Access: free without enums: https://github.com/corsego/50-rails-enums/commit/c8d725750760884216c429d10c5edcb815ccd5b9 enums as integers: https://github.com/corsego/50-rails-enums/commit/4ca2195316931eac1cfae8b12ebd1d91ddc83ce3 enums as strings: https://github.com/corsego/50-rails-enums/commit/496c143c2c00945aa9beab0a8054fb1540ffecfb official guide: https://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html ## Transcript Hello. So in this episode we are going to go through how and when to use ANUMs in the rub rails application. But to really understand when we need to use them, they're going to first try to build a feature without using ANUMs. So here we have a real life scenario, we have Boths and each post has a title and content. But what if a post should also have a status like banned or draft or published or whatever. So we would need to add the status to our database table inside our post. So let's say rails generate migration, add status to post status would be a stream. And actually we would go to the migration would also say that it should be by default a draft. And now we can run the migration rails to be migrate and start the server. Okay, now we can actually display the post status somewhere. So let's go to our posts in our views, we'll go to views, posts, index and uh, display the post, do status here and here. Okay. And you see by default it is set to be draft. And if you create a new post, we would also want to be able to input the status. So they're going to our posts form, they're going to add one more field. It would be, for example, a text field For status. Let's have a look. So by default it is trough and we would want to be able to change it. So we'll go to our controller, post controller and add it to the strong brands like this. Okay. Now we can change the status. Let's go to one of the existing posts and change it from draft to draft one and update the post. And it looks like it felt, you see it was changed to draft one. Okay. But we don't want be able to just write in the status. We want to have a collection of statuses. So we will go to our post form and we would say not form text field. We would say form.select status. And the options are going to be draft and let's say published. Okay, Going back, you see we have a select field and we can set it to be published. Now, let's go back and you see it was set to published, but we can still go and, uh, change it to something different. Let's say I will go to the HTML and change the selected one to be published. 1, 2, 3, 4, 5. And now press update, post and go into posts. We see that the status is published 1, 2, 3, 4, 5. So this way we kind of hacked our application. We changed the status, though it wasn't available inside our form. So we should add some validations and we should actually not keep our collection inside our view. We should keep it somewhere in our model. So let's go to our models post RB and uh, the, and here we will say, uh, statuses equals uh, uh, draft published and let's say banned. Okay? And inside the form we will say that the available statuses, statuses are going to be post statuses. Let's see if it works. So here we have three different statuses. Let's change it to band, update the post, go back to posts. And it seems to be working, but we would definitely want to add the, some validation of inclusion. So we would say validate Status, inclusion to be in post statuses. And now it should, uh, possibly work. Let's just try once again, I'm going to go and edit. Let's see if I still can access the available values. So going to the select, I will change the value to be banned. One to three, go to update the post. And uh, okay, Yeah, I had a typo with letter E. Let's try once again, I will resend. So resubmit the form. And you see we have, this status is not included in the list. So our validation of, uh, inclusion seems to be working and everything will be fine. But what we want to see all the posts that have a status of, uh, draft, for example, well, let's say, I'll say rails console and say post dot draft, and we don't have a collection. So let's create a scope. We would create a scope to see all the posts that have the status draft. So we would say scope, draft lambda there, status equals draft and close this gap. And let's see, once again, I will restart the rails console and if a type post draft would get an array of all the draft posts, but we don't have any at the moment. So we would also maybe want to have the same kind of scopes for published and banned. So we'll have scope published and scope band, and now go into the console. We can say post band and post published. So this kind of works, but what if you want to check if a specific post is banned or published over whatever. What if you say post do first dot banned, we get no method error. So would need to create a method for this, let's say would create a method dev band. And we would say, uh, status equals band. So it would give us a true or a false. And now if we say rails console and search for post first band, you see we get true. And if we check for post first, uh, uh, draft, we get no method error. So we would need to create the same methods for draft and for published. And you see this is quite a lot of code. And uh, instead of writing all the code, you can just use ribbon rails anonymous. So let's see how they work. Now. First I'm going to just save all the changes that we just did. Get status, get, add all, get commit main, and I'll say without Anos. Okay? And now we're going to actually try to use anos. So they're going to remove all of this. And, uh, going back to our migration. So by default, what are anos? You would, uh, have an integer saved in your database and you would have a few different, uh, kind of statuses or array of, uh, anything that you want in your database that can be, uh, searched as by a method and uh, with a scope. So whatever you have in an A norm can be searched for, uh, with a method and a scope. So you don't need to write, uh, all the code for scopes for methods. You don't need to write this validation. All this is included automatically. So this is really handy. Let's try and see how it works. Now, by default, you would want to use, uh, an num with an integer. Now, what is the trigger thing? You kind of have a list of strings, but they are persisted in the database as integers. Let's just see how it works. I will, uh, first of all, uh, run a rails, the be rolled back, okay? And now I will say at column post status integer, now rails, the be migrate, and I'm going to add an A number of statuses. Now, the latest, uh, edge guides of Rubion rails at the moment, uh, uh, say that this should be the correct syntax, but I'm using rails 6.1 0.4, and you see the synex is slightly, slightly different. So I would say something like ano, okay, let's just copy it. Ano status, uh, draft and published. Okay, so we have two statuses and let's, uh, slide them in our form. So I'll go to the rail server. And you see previously we had both statuses, and now we'll want to have, uh, it slightly differently. We want to have the ones that are in the anon. So going to edit, we must have an error that there are no post statuses like that. We would just say post statuses. Let's see if it works. Yeah, so we have JT and published, let's save this post as, uh, published. I'll press update post. And you see it gives us one is not valid status. Now where do we have this error from? Basically, these are saved as integer in the database, but we have them as strings in inside our model. So here is something I will, uh, open and separate fin the the rails console and say post statuses. And you see we have a hash of keys and values. So I can say post statuses, keys and do values. So seeing the draft has the value that'll be set in the database as zero. It is like the index and publish would have a one. And if it had something like, uh, band, it would have the index of two. So zero one tool. And we are trying to send one, but not, uh, a but not a key. So we're trying to set the value, but not the key inside our form. So here we would say post statuses, keys in the collection. Let's go back to edit once again. And here you see again, we have the same keys. Nothing has changed. So I'll just say post, uh, status band or whatever, and update the post. And it seems to be working. So you see the status is banned. Now what if, uh, I add another status. Let's say I'll add a status as, uh, in review, I added the status. And now look attentively. I'm refreshing the page. And you see the status has changed. Now why has it changed? Because the index has changed. So we try to find, uh, an element with the index of, uh, 0 1, 2 of two inside our view. And the two is published. So you see, you shouldn't add anything new on the beginning of the string. You should add this, uh, I review or whatever you want in the end of this, uh, anon. And then it's not going to break, uh, your lineup. So this is not kind of really nice. I don't like the idea of, uh, having to have a specific order of, uh, values here. And I don't like the idea of, uh, them being mapped, uh, just, uh, to specific inte, like 0, 1, 2, 3, 4, 5. So instead we can use something different. We can say, uh, draft is, uh, zero. Published is one band is 13 in review is 534. And now let's try once again, I'm going to refresh and we get the must be other hash an array. Okay, I'll try it like this and it should work. So I'm going back and I'm going to edit this And I'll change the status to in review, update post. And it seems to be broken. So going to post both you see is in review. And if we remove a norm, let's say, we'll comment it out, the database value should be 534. Let's see. And it did is is 534. And let's play a bit more. So let's, uh, create one more post and try to do some scopes and some kind of methods. So second post, it should be a draft, okay? Or let's make it published some content and create post. And let's go back to our rails console. I will restart it now, exit and rails console. And let's see, post dot count. We have two posts. Let's say post band, we don't have any, let's say post published. We have one post that is published. Now, let's say post fer published. And you see we have this method. So it give us a true or false, let's say post second published. It is true. And let's actually make the first post published, we'll just say published, but with an exclamation mark. And you see we've updated it to be published. So, so let's say post published Dot count. And now we have all the posts, all of them should be published. So we see if we can do a lot of, uh, different stuff using nus. We can check if a specific post is, uh, published. We can, uh, check, uh, uh, we can force it to become, uh, uh, of a specific status. We can, uh, have all our, uh, scopes by default. And uh, it's really cool, but, uh, I still don't like it a lot. So you see here have, uh, this na status and the hot code in the strings to integer. It's, it's not really cool in my opinion, but if they use, uh, uh, encoding strings to strings, that would make, uh, kind of a bit more sense. So, uh, let's try that. Let's just change, uh, our, uh, save our changes get at all. Oh, nevermind. Let's, uh, say we will roll back rails, DB roll back. And once again, the status is going to be not an integer, but a stream. So status would be stream, okay, rails db, migrate. Okay, we've migrated and they're going to encode draft to draft published to published, banned to banned and all we don't need in review anymore. And let's see if, uh, it works. So I'm going to refresh the page. Now the status should be blank because we've done a rollback and let's try to edit will it work? So let's go and change the status to published and update the post and going to edit. Will it be published? Yes, it is published. So looks fine. Let's make it band. And going back to edit. Yeah. So it was changed to band and it should be also saved correctly inside our database. So even if they remove the ano on The status should still be set to band. So this kind of, uh, is a bit more human readable, okay? And the more thing, what if they want to have some default values? Now in the previous sales versions, you would, uh, just set the default value inside your database on the database level. But what if you want to change the default value later on, you don't. You would have to run some kind of migrations. And now you can actually set default values inside the anon. So actually the synex is quite different in the rail. 6 1 4 here is the synex for setting default. And in the edge guys, it's also already different. It's slightly improved. So I'm using 6 1 4 now. So I will say, uh, default it would be, uh, let's say band for example. And let's create a new post. And you see, by default it'll set the band, let's make it published B by default. And now it is published by default. Okay, so looks fine. And actually we can also access this default value inside our console. It will just need to say something like, uh, post new status, and here we have this value, let's change it from published to draft And post new status would be, okay, I would need to restart the console and now post new status would be dropped. So it kind of works and we can access, uh, that default value through the console. And now let's see if it works inside our form. So I will, uh, restart the server. Okay, not the console, but the server rails s okay. And now going to our posts. So here have, uh, in your post status is trout by default. Let's see if it also works. If we have in include blank, let's say include blank would be true. So let's go to post to new post. So by default it is draft, even if they include blank. And let's create it. So let's make it, uh, published, create post. And if we go back, it is saved as published. So everything works file. Okay, and just a few things that you might need in the future. For example, if you have two omes that have, uh, something similar, so let's say you would have a non, uh, state and that would also be an option of being published. Well, then you would be able to use something like, uh, prefixes and subes. And you can also disable scopes for different anos. So you see, you can disable a scope to be false. And uh, you can also, uh, here are the su fixes and pres. So you can add a Sufi to be active status or whatever. And similarly with pres, well, and this is basically it. So the most important things about Enos that you can use them as saving in the database as strings as an in also with intes or strings. Yeah. And uh, I usually prefer strings because it's slightly more human readable than it is better to set the defaults on the modal level, but not on the database level. And, uh, well that's basically it. So the cool thing is also that you can run all the scopes and all the presence methods, uh, by default and you don't have to write the, a lot of extra code. So that's it, and I hope you liked that episode. Goodbye. --- # #49 gem Pundit for Authorization - Complete Guide - URL: https://superails.com/posts/ruby-on-rails-49-gem-pundit-for-authorization-complete-guide - Published: 2021-08-05 - Duration: 17:18 - Access: free Restrict access to different views and actions in your application based on users roles. Episode source code: https://github.com/corsego/49-gem-pundit/commit/3e3c65d8b6deaf382dea5ce554c732e85731d6d2 My text notes for this episode: https://blog.corsego.com/complete-guide-to-gem-pundit Gem pundit source code: https://github.com/varvet/pundit 0:00 Authorization without a gem 2:20 Install gem Pundit, basic usage 7:10 User Policy - only admin can edit 10:22 Create Post Policy 11:50 Pundit Scopes 14:10 Authorization conditions in views 16:34 Summary ## Chapters - 0:00 Authorization without a gem - 2:20 Install gem Pundit, basic usage - 7:10 User Policy - only admin can edit - 10:22 Create Post Policy - 11:50 Pundit Scopes - 14:10 Authorization conditions in views - 16:34 Summary --- # #48 Gem Rolify for assigning user roles - Complete Guide - URL: https://superails.com/posts/ruby-on-rails-48-gem-rolify-for-assigning-user-roles-complete-guide - Published: 2021-07-30 - Duration: 29:24 - Access: free episode source code: https://github.com/corsego/48-gem-rolify/commit/929b6646efac8467f2e502266cf2b2d8b8683aa0 gem rolify source code: https://github.com/RolifyCommunity/rolify/wiki/Usage --- # #47 TIP: create a list of buttons to change status - URL: https://superails.com/posts/ruby-on-rails-47-tip-create-a-list-of-buttons-to-change-status - Published: 2021-07-25 - Duration: 12:28 - Access: free episode source code: https://github.com/corsego/47-button-to-update-status/commit/3ce355748bf192956c1da7b0017c04e018383d03 --- # #46 gem invisible_captcha - pure Ruby reCaptcha alternative. No more bots in your app! - URL: https://superails.com/posts/ruby-on-rails-46-gem-invisible_captcha-pure-ruby-recaptcha-alternative-no-more-bots-in-your-app - Published: 2021-07-20 - Duration: 12:44 - Access: free - Topics: security episode source code: https://github.com/corsego/46-invisible_captcha/commit/8f1cfc329df995c5b7d633f260a5d3f414d4c837 gem invisible_captcha docs: https://github.com/markets/invisible_captcha --- # #45 config_for Settings.yml - add default settings to your app - URL: https://superails.com/posts/ruby-on-rails-45-config_for-settings-yml-add-default-settings-to-your-app - Published: 2021-07-17 - Duration: 12:21 - Access: free episode source code: https://github.com/corsego/45-config_for_settings/commit/0cf11eccafccc196968cdcd1dc76b75ccec2911d official docs: https://api.rubyonrails.org/classes/Rails/Application.html#method-i-config_for --- # #44 Rails Counter Cache: Count associated child records CORRECTLY - URL: https://superails.com/posts/ruby-on-rails-44-rails-counter-cache-count-associated-child-records-correctly - Published: 2021-07-16 - Duration: 16:48 - Access: free episode source code: https://github.com/corsego/44-counter_cache/commit/b543ff789d0ff47c570ca0247f6dd3ddd638d04f official docs: https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache --- # #11: gem Rouge, gem InvisibleCaptcha, highlight search results - URL: https://superails.com/posts/code-along-video-subscription-app-11-gem-rouge-gem-invisiblecaptcha-highlight-search-results - Published: 2021-07-07 - Duration: 44:58 - Access: free In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ redcarpet + rouge = good markdown https://github.com/yshmarov/superails/commit/db16d1ca11da2d242099e46fa9b3016cb8576ccb highlight search results https://github.com/yshmarov/superails/commit/7eabd2fe276ce1ec19a7d47228b10376b2732abd assign name if not provided by social login https://github.com/yshmarov/superails/commit/f8ac3d59d7e188870bac3d5671682197e304a1f5 progess bar update after vote https://github.com/yshmarov/superails/commit/fe129b06560419f4872e49cf0316a27fedfd86d9 gem invisible_captcha - fewer bot signups https://github.com/yshmarov/superails/commit/c86918a838a9aa6bb1895269c18235ea1886d035 LEGACY LINKS: assign name if not provided by social login https://github.com/yshmarov/superails/commit/a4f0855d23013ff52aef83102ba862c86bf87910 progess bar update after vote https://github.com/yshmarov/superails/commit/e9e3619822cc56001e2a91f83c93a735058fca59 gem invisible_captcha - fewer bot signups https://github.com/yshmarov/superails/commit/3ff2fadc3ca8173427fd4c637b3b0f7024c8d51a --- # #43 Gem Devise Invitable - the correct way to create or invite users to your app - URL: https://superails.com/posts/ruby-on-rails-43-gem-devise-invitable-the-correct-way-to-create-or-invite-users-to-your-app - Published: 2021-07-03 - Duration: 18:53 - Access: free - Topics: devise - Playlists: Devise Authentication (Login) Gem source: https://github.com/scambra/devise_invitable Episode code: https://github.com/corsego/40-devise-everything/commit/6a408b0ee0991749d32321a130b28184efbbff21 Intro. Creating users manually Installing the gem Inviting users Button to resend invitation --- # #10: Bootstrap, Friendly ID, ExceptionNotification - URL: https://superails.com/posts/code-along-video-subscription-app-10-bootstrap-friendly-id-exceptionnotification - Published: 2021-06-26 - Duration: 53:20 - Access: free In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ bootstrap styling https://github.com/yshmarov/superails/commit/ea27f3e693e91cce190b62ba10f657f7e3819b60 progress bar for up down votes https://github.com/yshmarov/superails/commit/ea8d098ead28aa6bb17d25240006ac01a8b36398 gem frienldy id https://github.com/yshmarov/superails/commit/b1c67b16a0326c68013f8638c5f18319455b98fc gem exception_notification https://github.com/yshmarov/superails/commit/e5c4bc1da4d9783cb3b23f278f5f198154cd6ec5 --- # #42 Devise Lockable. Ban or Unban users as you please. - URL: https://superails.com/posts/ruby-on-rails-42-devise-lockable-ban-or-unban-users-as-you-please - Published: 2021-06-25 - Duration: 13:03 - Access: free - Topics: devise - Playlists: Devise Authentication (Login) Official Devise Lockable Docs: https://github.com/heartcombo/devise/wiki/How-To:-Add-:lockable-to-Users devise lockable if password wrong 3 times https://github.com/corsego/40-devise-everything/commit/e72151d4c57043a0607b5bb558748ad9621539b4 ban or unban users https://github.com/corsego/40-devise-everything/commit/ce1bd0ee4567a0a2f08238da1402bfda4dfd96bc --- # #9: Stimulus Components CharacterCount, AutoGrow; Post Description - URL: https://superails.com/posts/code-along-video-subscription-app-9-stimulus-components-charactercount-autogrow-post-description - Published: 2021-06-24 - Duration: 40:44 - Access: free In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ add description to posts https://github.com/yshmarov/superails/commit/85d1c1ef9809be9a801133bb4be88c5512d9903c validate length of posts https://github.com/yshmarov/superails/commit/06f9415a840fe30d5e655410fa40aed3578ad2a0 add post description to seeds https://github.com/yshmarov/superails/commit/10d6fb9a4000e9d07a1b29f709626278e9b68c08 better use of partials https://github.com/yshmarov/superails/commit/25fc7546e4d5c67b81b4cb0b26cdd3a4add21bac better ransack https://github.com/yshmarov/superails/commit/b9de4d1cb2d69af318dcc96e08974e64c751b16d added an about page, navbar and footer styling, voting links inline https://github.com/yshmarov/superails/commit/36c1b58f0b229b01a53404555b79e7e307ad9549 install stimulus js https://github.com/yshmarov/superails/commit/7737f0ef25597c1427758268fec0bc4120a2e90e Stimulus Textarea Autogrow https://github.com/yshmarov/superails/commit/f20107cbd9917d9d88d115d89728cb8e3a97cc4b Stimulus Character Counter https://github.com/yshmarov/superails/commit/2e829e13c96925afba987a11907e20f30bb2b191 --- # #41 Devise Confirmable - no more users with fake emails! - URL: https://superails.com/posts/ruby-on-rails-41-devise-confirmable-no-more-users-with-fake-emails - Published: 2021-06-23 - Duration: 12:51 - Access: free - Topics: devise - Playlists: Devise Authentication (Login) Devise Confirmable: https://github.com/corsego/40-devise-everything/commit/f5e519ebb7c22150024a6027186f05eb651f81f8 Official Wiki: https://github.com/heartcombo/devise/wiki/How-To:-Add-:confirmable-to-Users --- # #8: Posts access, AJAX voting, pagination, search, styling - URL: https://superails.com/posts/code-along-video-subscription-app-8-posts-access-ajax-voting-pagination-search-styling - Published: 2021-06-22 - Duration: 39:34 - Access: free Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ basic bootstrap styling for posts and users, post view validations vote for posts with gem acts_as_votable posts pagination and ransack search ransack sorting for posts basic bootstrap styling for posts and users, post view validations https://github.com/yshmarov/superails/commit/ab372f106f104461cd8e93fc3e52a59fed43d8e7 vote for posts with gem acts_as_votable https://github.com/yshmarov/superails/commit/69fba2e3cdad33c47a05e62545c7e0a0f9187696 posts pagination and ransack search https://github.com/yshmarov/superails/commit/8988ac2b7004e9d26e065a7db8abf07a7b694d84 ransack sorting for posts https://github.com/yshmarov/superails/commit/9b1317b5fbc6df02099e49e79c070d7d64c8d246 --- # #40 Devise Trackable, Users Index page - URL: https://superails.com/posts/ruby-on-rails-40-devise-trackable-users-index-page - Published: 2021-06-21 - Duration: 06:07 - Access: free - Topics: devise - Playlists: Devise Authentication (Login) devise trackable: https://github.com/corsego/40-devise-everything/commit/3eb1c4fbbd67af2a8f7be217ab56550c1d91f9ee users index view: https://github.com/corsego/40-devise-everything/commit/c7d05b3fe2941046f8bb84a07416bff47bcc1f2c Official Wiki: https://github.com/heartcombo/devise/wiki/How-To:-Add-:trackable-to-Users --- # #7: FINISH Stripe Subscription Payments, Billing Management - URL: https://superails.com/posts/code-along-video-subscription-app-7-finish-stripe-subscription-payments-billing-management - Published: 2021-06-20 - Duration: 40:33 - Access: free HELP NEEDED: While watching, can You add timecodes to the video? In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ add stripe to users, pricing pages, subscription webhooks stripe billing portal premium posts available for active subscribers add stripe to users, pricing pages, subscription webhooks https://github.com/yshmarov/superails/commit/ff9b36d1c11b40ac672dfff93befc9a3a448cc0f stripe billing portal https://github.com/yshmarov/superails/commit/ff9b36d1c11b40ac672dfff93befc9a3a448cc0f premium posts available for active subscribers https://github.com/yshmarov/superails/commit/e9f31e5c547df1a4c678e1dfdc15591aa5528e4a --- # #39 Devise Authentication from ZERO - URL: https://superails.com/posts/ruby-on-rails-39-devise-authentication-from-zero - Published: 2021-06-19 - Duration: 21:39 - Access: free - Topics: devise - Playlists: Devise Authentication (Login) Episode covering user log-in functionality for for entry-level developers. Source code: https://github.com/corsego/39-devise-basics Gem devise: https://github.com/heartcombo/devise#getting-started 0:00 Introduction 1:20 Static Pages. Hello world 5:35 Install Gem Devise 11:25 Url for user to sign in 12:25 see if user is logged in 13:47 LogOut button 15:11 see user data only if logged in 15:51 conditionally display user data or LogOut 16:25 conditionally LogIn or LogOut 18:39 user-only access ## Chapters - 0:00 Introduction - 1:20 Static Pages. Hello world - 5:35 Install Gem Devise - 11:25 Url for user to sign in - 12:25 see if user is logged in - 13:47 LogOut button - 15:11 see user data only if logged in - 15:51 conditionally display user data or LogOut - 16:25 conditionally LogIn or LogOut - 18:39 user-only access --- # #6: premium posts, stripe gem, stripe webhooks - URL: https://superails.com/posts/code-along-video-subscription-app-6-premium-posts-stripe-gem-stripe-webhooks - Published: 2021-06-18 - Duration: 37:16 - Access: free In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ seeds for posts, markdown for posts partial add premium boolean to posts gem stripe, add stripe customer id to users stripe webhooks update path to webhook credential seeds for posts, markdown for posts partial https://github.com/yshmarov/superails/commit/1a611551db70833cddb4843cdf83d1fea2bbabee add premium boolean to posts https://github.com/yshmarov/superails/commit/edb7c1f6376b4ee3efd432bbc0854270aeb11ca3 gem stripe, add stripe customer id to users https://github.com/yshmarov/superails/commit/2030893c5eddf87aff61682504e9f315ff4ecb90 stripe webhooks https://github.com/yshmarov/superails/commit/d4cc78fe2fa345dca025f0f5b47579ac2e284cc4 update path to webhook credential https://github.com/yshmarov/superails/commit/a550eeb9c7fcf37b33cc354e151d71122363bc0b --- # #5: Helpers, Devise Invitable, Posts, Markdown, CounterCache - URL: https://superails.com/posts/code-along-video-subscription-app-5-helpers-devise-invitable-posts-markdown-countercache - Published: 2021-06-17 - Duration: 55:47 - Access: free In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ update mailer emails active link to helpers dont display pagination if 1 page helper to see boolean color gem devise_invitable scaffold posts posts basic styling, relationships counter_cache posts to users add markdown update mailer emails https://github.com/yshmarov/superails/commit/18202be0a5566674119870a47e864474fe7e0507 active link to helpers https://github.com/yshmarov/superails/commit/a429845a48102bd80905dd57d68c503bffa0635c dont display pagination if 1 page https://github.com/yshmarov/superails/commit/50f387d422815060b8c10d91f8b3db574a8d9272 helper to see boolean color https://github.com/yshmarov/superails/commit/9e1b282261403f925fe55a0d0ab7ee26f3836d41 gem devise_invitable https://github.com/yshmarov/superails/commit/94a597e6fd51057835028a20e5037a13ad0085cb scaffold posts https://github.com/yshmarov/superails/commit/c1339bff389403d59813eadbe3ee6c0058f53d26 posts basic styling, relationships https://github.com/yshmarov/superails/commit/81b447f73d565748d43dda3771a0dcbd30294076 counter_cache posts to users https://github.com/yshmarov/superails/commit/03854eb11964fa2c636c45f1cde6064755820479 add markdown https://github.com/yshmarov/superails/commit/1ca4e6d32155651d29995a87ec4cca21a1bfcf1f --- # #4: seeds, faker, pagy, ransack, strftime - URL: https://superails.com/posts/code-along-video-subscription-app-4-seeds-faker-pagy-ransack-strftime - Published: 2021-06-16 - Duration: 28:01 - Access: free In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ seeds.rb and gem faker https://github.com/yshmarov/superails/commit/6aab4781b681f0256313b95a226f2f2342dc0e7a gem pagy for pagination https://github.com/yshmarov/superails/commit/cb5dad78bb9ee7fabb7e0c3099b445eef0a7edde gem ransack to search and filter users gem ransack to search and filter users user created_at strftime https://github.com/yshmarov/superails/commit/bd5eaee19881e4e59f7777fcc19b78d6fd68a70a --- # #3: Notifications, Devise Confirmable, AWS SES, LetterOpener - URL: https://superails.com/posts/code-along-video-subscription-app-3-notifications-devise-confirmable-aws-ses-letteropener - Published: 2021-06-15 - Duration: 48:59 - Access: free - Topics: Aws In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ 0:00 Intro 0:26 styling for notifications 10:20 devise confirmable 17:40 confirm account from console 18:52 auto sign in after confirmation 21:50 autoconfirm omniauth email 24:30 Sending Emails in development and production 29:35 Amazon SES + your domapn (namecheap) 33:33 Get Amazon SES API keys 39:08 Amazon SES email address not verified 41:09 default outbound email addresses 43:22 gem letter opener 48:38 I AM TIRED styling for notifications https://github.com/yshmarov/superails/commit/306be3598f057801683ba691914fba24feb06283 devise confirmable https://github.com/yshmarov/superails/commit/254d5209ac1c553e7687ddc0ad204c32ac10554d send emails in production with aws ses https://github.com/yshmarov/superails/commit/6bca0dd421db86262d91a85e312bceb27b719ee1 gem letter opener for development https://github.com/yshmarov/superails/commit/ed4d91dd5ef8705340c51f483516b278d491ccff ## Chapters - 0:00 Intro - 0:26 styling for notifications - 10:20 devise confirmable - 17:40 confirm account from console - 18:52 auto sign in after confirmation - 21:50 autoconfirm omniauth email - 24:30 Sending Emails in development and production - 29:35 Amazon SES + your domapn (namecheap) - 33:33 Get Amazon SES API keys - 39:08 Amazon SES email address not verified - 41:09 default outbound email addresses - 43:22 gem letter opener - 48:38 I AM TIRED --- # #2: Thumbnail, Logo, UsersController, Devise Trackable - URL: https://superails.com/posts/code-along-video-subscription-app-2-thumbnail-logo-userscontroller-devise-trackable - Published: 2021-06-14 - Duration: 30:16 - Access: free In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ 0:00 introduction 0:42 boring footer styling - responsiveness 1:57 boring word-break styling - responsiveness 3:34 add thumbnail and logo, footer layout, word break 8:17 omniauth callback failure 8:56 split username 10:08 responsive header dropdown 11:51 users index and show 22:00 Boilerplate?! 22:30 Link to user profile 23:37 after_sign_in_path user_path(user) 25:30 devise trackable add thumbnail and logo, footer layout, word break https://github.com/yshmarov/superails/commit/9fa76bdf60ce7510399a8d8bfbe0ee4c3fd9e9aa omniauth callback failure, split username, responsive header dropdown https://github.com/yshmarov/superails/commit/77add270ea4ec2d10c9b4b4ad18e7b46d5b6d1cd users index and show https://github.com/yshmarov/superails/commit/50bf47f856e784fecc5f9938d436e2226af25fee after_sign_in_path user_path(user) https://github.com/yshmarov/superails/commit/b4a02acc94a687d7726c34469fa4be44ec17a5f2 devise trackable https://github.com/yshmarov/superails/commit/1cbfebafd7e15dfe09a7b9e96a3ae960fa2a3d1a ## Chapters - 0:00 introduction - 0:42 boring footer styling - responsiveness - 1:57 boring word-break styling - responsiveness - 3:34 add thumbnail and logo, footer layout, word break - 8:17 omniauth callback failure - 8:56 split username - 10:08 responsive header dropdown - 11:51 users index and show - 22:00 Boilerplate?! - 22:30 Link to user profile - 23:37 after_sign_in_path user_path(user) - 25:30 devise trackable --- # #1: Devise Omniauth, Heroku SSL, Bootstrap, Navbar, Footer - URL: https://superails.com/posts/code-along-video-subscription-app-1-devise-omniauth-heroku-ssl-bootstrap-navbar-footer - Published: 2021-06-14 - Duration: 01:22:57 - Access: free In this Code-Along video series we'll be building a new home for all the videos - SupeRails.com. Github repo: https://github.com/yshmarov/superails Live demo: https://superails.com/ 0:00 introduction 1:30 railsnew.io 2:33 initialize app 4:30 create github repo 5:12 static public controller with pages 9:27 create heroku app 11:20 autodeploy git repo to heroku 11:59 push to heroku 12:25 connect real domain 14:33 paid heroku plan for SSL 15:58 add bootstrap 22:02 SSL HTTPS by default 23:52 FontAwesome icons 26:37 install gem devise 31:12 devise navigation and partials, no auth required to view static public 36:23 bootstrap devise fontawesome navbar 44:50 add a footer 51:51 omniauth google 01:04:03 add omniauth fields to users 01:11:26 gem omniauth github COMMITS: initialize app https://github.com/yshmarov/superails/commit/54eb8cabaac6293b6b3f2a7c0833d991e6a05edd static public controller with pages https://github.com/yshmarov/superails/commit/079ad18e8a343e9f02a07534cf39e4c449dc823b add bootstrap https://github.com/yshmarov/superails/commit/2f2bca92db1d2f6c8293c84edf0f0f039c8afb7e force ssl https://github.com/yshmarov/superails/commit/f4a6bc80f993b66f26f7fad78fd958e7bdaca77c install fontawesome https://github.com/yshmarov/superails/commit/2dc0b766d1c305af30271100c0354403c3bf56a9 install gem devise https://github.com/yshmarov/superails/commit/033f25af1591190fabf61af39ddc57205486d293 devise navigation and partials, no auth required to view static public https://github.com/yshmarov/superails/commit/9cb71ff0b208c7b56040a02b3c4d6c921f1853f3 bootstrap devise fontawesome navbar https://github.com/yshmarov/superails/commit/8f77879a320ea80388c57e429130eae05aec4a03 add a footer https://github.com/yshmarov/superails/commit/66b03129cdd4e7f519cc88babc0e6b7b8136978f omniauth google https://github.com/yshmarov/superails/commit/b45ee253bda5dd2376a660969c4062a3ec2bb5a4 add omniauth fields to users https://github.com/yshmarov/superails/commit/2b9c6e907ce23f917f779d2a832ac7e6843c5db8 gem omniauth github https://github.com/yshmarov/superails/commit/a0a3ce1599be461278e5e81059a5138d8ee6dea3 ## Chapters - 0:00 introduction - 1:30 railsnew.io - 2:33 initialize app - 4:30 create github repo - 5:12 static public controller with pages - 9:27 create heroku app - 11:20 autodeploy git repo to heroku - 11:59 push to heroku - 12:25 connect real domain - 14:33 paid heroku plan for SSL - 15:58 add bootstrap - 22:02 SSL HTTPS by default - 23:52 FontAwesome icons - 26:37 install gem devise - 31:12 devise navigation and partials, no auth required to view static public - 36:23 bootstrap devise fontawesome navbar - 44:50 add a footer - 51:51 omniauth google - 01:04:03 add omniauth fields to users - 01:11:26 gem omniauth github --- # #38 Install Fontawesome 5 with Rails 6, yarn and webpacker - URL: https://superails.com/posts/ruby-on-rails-38-install-fontawesome-5-with-rails-6-yarn-and-webpacker - Published: 2021-06-13 - Duration: 04:20 - Access: free Blogpost covering the topic: https://blog.corsego.com/rails-6-install-fontawesome-with-webpacker Source code: https://github.com/corsego/38-fontawesome5 --- # #37 Install Bootstrap 5 with Rails 6, Yarn and Webpacker - URL: https://superails.com/posts/ruby-on-rails-37-install-bootstrap-5-with-rails-6-yarn-and-webpacker - Published: 2021-06-12 - Duration: 06:42 - Access: free Blogpost covering the topic: https://blog.corsego.com/rails-bootstrap-5-yarn Source code: https://github.com/corsego/37-bootstrap5/commit/3a8df15c9fa1351f3bb853219ec854ec1e644ced --- # #36 Action Text - Rich text editing with Trix - URL: https://superails.com/posts/ruby-on-rails-36-action-text-rich-text-editing-with-trix - Published: 2021-06-10 - Duration: 10:43 - Access: free episode source code: https://github.com/corsego/36-actiontext official guide: https://edgeguides.rubyonrails.org/action_text_overview.html --- # #35 Google API Console, oAuth Consent Screen - URL: https://superails.com/posts/ruby-on-rails-35-google-api-console-oauth-consent-screen - Published: 2021-06-07 - Duration: 08:41 - Access: free Just in case you have issues getting the API keys to work --- # #34 Github oAuth in Development and Production - URL: https://superails.com/posts/ruby-on-rails-34-github-oauth-in-development-and-production - Published: 2021-06-07 - Duration: 03:40 - Access: free Just in case you have issues, in this episode I try to go through of running a github oAuth app both in development & production --- # #33 Gem Dotenv - alternative to Rails credentials - URL: https://superails.com/posts/ruby-on-rails-33-gem-dotenv-alternative-to-rails-credentials - Published: 2021-06-03 - Duration: 08:23 - Access: free Source Code of the episode: https://github.com/corsego/29-omniauth/commit/2505525092333dc4c436c7df19c0bac6b159192c The gem: https://github.com/bkeepers/dotenv --- # #32 gem omniauth google (social log in with Google) - URL: https://superails.com/posts/ruby-on-rails-32-gem-omniauth-google-social-log-in-with-google - Published: 2021-05-31 - Duration: 05:28 - Access: free The gem: https://github.com/zquestz/omniauth-google-oauth2 get an API key: https://console.cloud.google.com/apis/dashboard gem omniauth google: https://github.com/corsego/29-omniauth/commit/24d09b9537b95a3c9fa56f8c336ab26bce3e5c9f --- # #31 Save Personal Data from Social Login to your app - URL: https://superails.com/posts/ruby-on-rails-31-save-personal-data-from-social-login-to-your-app - Published: 2021-05-26 - Duration: 08:29 - Access: free The auth hash: https://github.com/zquestz/omniauth-google-oauth2#auth-hash Source code: https://github.com/corsego/29-omniauth/commit/8c63c0f96957a7b05003961679855423e4bea99f --- # #30 Social log in with Github (omniauth 2.0) - URL: https://superails.com/posts/ruby-on-rails-30-social-log-in-with-github-omniauth-2-0 - Published: 2021-05-25 - Duration: 10:58 - Access: free gem omniauth-github: https://github.com/omniauth/omniauth-github Devise + Omniauth integration - basic docs: https://github.com/zquestz/omniauth-google-oauth2#devise Gem agains "Authenticity error": https://github.com/cookpad/omniauth-rails_csrf_protection Ultimate text guide: https://blog.corsego.com/devise-omniauth-github Source code: https://github.com/corsego/29-omniauth/commit/dc435261173dd3b8bac22971f4ab9daf86842dd9 --- # #29 Action Mailer: Send emails with Amazon Simple Email Service in production - URL: https://superails.com/posts/ruby-on-rails-29-action-mailer-send-emails-with-amazon-simple-email-service-in-production - Published: 2021-05-23 - Duration: 12:57 - Access: free - Topics: Aws - Playlists: Action Mailer The problem with Letter Opener: https://github.com/ryanb/letter_opener#remote-alternatives Test SES emails: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-simulator.html Episode source code: https://github.com/corsego/26-action-mailer/commit/ac0fbc1dc40aab72526e37880450431fe41eb630 --- # #28 Action Mailer: Gmail SMTP - send emails in production for free - URL: https://superails.com/posts/ruby-on-rails-28-action-mailer-gmail-smtp-send-emails-in-production-for-free - Published: 2021-05-20 - Duration: 11:12 - Access: free - Topics: Google Cloud Console - Playlists: Action Mailer Source Code: https://github.com/corsego/26-action-mailer/commit/04e3f81a5318382183c8fe6c7fc4abc481b15292 Docs: https://guides.rubyonrails.org/action_mailer_basics.html#example-action-mailer-configuration Google URL to set password: https://myaccount.google.com/apppasswords --- # #27 Gem Letter Opener - best way to preview emails in development - URL: https://superails.com/posts/ruby-on-rails-27-gem-letter-opener-best-way-to-preview-emails-in-development - Published: 2021-05-19 - Duration: 04:42 - Access: free - Playlists: Action Mailer gem letter_opener: https://github.com/ryanb/letter_opener Source Code for the Episode: https://github.com/corsego/26-action-mailer/commit/24fb10065fb5c4502b15ea75d651aec8e61413e0 To fix Launchy error - run these commands in console: export BROWSER=/dev/null export LAUNCHY_DRY_RUN=true --- # #26 send emails with Action Mailer - Complete Guide - URL: https://superails.com/posts/ruby-on-rails-26-send-emails-with-action-mailer-complete-guide - Published: 2021-05-18 - Duration: 17:16 - Access: free - Playlists: Action Mailer In this episode we will use ActionMailer to send the User an email after he creates a Post. We will also explore different options and ways to customise our emails. Action Mailer docs: https://guides.rubyonrails.org/action_mailer_basics.html Source Code for the Episode: https://github.com/corsego/26-action-mailer/commit/6519734de30c87b39d90e726ebb602a23ff0fd40 My text notes for this episode: https://blog.corsego.com/action-mailer-basics 0:00 Intro 0:47 Generate a new Mailer 3:30 Preview the mailer 4:00 Override default FROM address 4:40 Override TO, CC, BCC email address 6:10 Override email SUBJECT 7:34 Send email from controller 9:20 custom email params (current_user, post) 11:34 add sender name to default FROM address 13:45 Attach image 15:15 Display attached image inside email body 16:12 Update signature for all emails ## Chapters - 0:00 Intro - 0:47 Generate a new Mailer - 3:30 Preview the mailer - 4:00 Override default FROM address - 4:40 Override TO, CC, BCC email address - 6:10 Override email SUBJECT - 7:34 Send email from controller - 9:20 custom email params (current_user, post) - 11:34 add sender name to default FROM address - 13:45 Attach image - 15:15 Display attached image inside email body - 16:12 Update signature for all emails --- # #25 gem Active Storage Validations - URL: https://superails.com/posts/ruby-on-rails-25-gem-active-storage-validations - Published: 2021-05-17 - Duration: 08:23 - Access: free - Playlists: Active Storage install gem active_storage_validations: https://github.com/corsego/20-active_storage/commit/32ebf91ec378423c8cd1f9a2230843b870fe2734 gem active_storage_validations: https://github.com/igorkasyanchuk/active_storage_validations --- # #24 Active Storage - Deleting Attachments (Ultimate approach) - URL: https://superails.com/posts/ruby-on-rails-24-active-storage-deleting-attachments-ultimate-approach - Published: 2021-05-16 - Duration: 08:19 - Access: free - Playlists: Active Storage action to delete post avatar: https://github.com/corsego/20-active_storage/commit/f091a54c006183f4a86e226050ff7028493b400d delete any attachment object: https://github.com/corsego/20-active_storage/commit/c350b033fcdcd9ec6e3a92d6eb8881d58b3e56f9 --- # #23 Active Storage - Display Audio, Video, PDF attachments - URL: https://superails.com/posts/ruby-on-rails-23-active-storage-display-audio-video-pdf-attachments - Published: 2021-05-15 - Duration: 07:58 - Access: free - Playlists: Active Storage display different attachment types: https://github.com/corsego/20-active_storage/commit/a932b8e4ce861827da9cc4e86830b1e73ff55565 HTML5 audio and video tags: https://www.html5tutorial.info/html5-video.php Displaying Images, Videos, and PDFs: https://edgeguides.rubyonrails.org/active_storage_overview.html#displaying-images-videos-and-pdfs --- # #22 ActiveStorage Direct Uploads, AWS S3 CORS - URL: https://superails.com/posts/ruby-on-rails-22-activestorage-direct-uploads-aws-s3-cors - Published: 2021-05-12 - Duration: 06:40 - Access: free - Topics: Aws - Playlists: Active Storage Source Code: https://github.com/corsego/20-active_storage/ Active Storage Direct Uploads Docs: https://edgeguides.rubyonrails.org/active_storage_overview.html#direct-uploads 0:00 The Mission 0:15 Add Direct Uploads 2:00 CORS problem in Direct Uploads + Cloud Storage 3:30 add CORS to S3 4:47 works now! 5:43 summary ## Chapters - 0:00 The Mission - 0:15 Add Direct Uploads - 2:00 CORS problem in Direct Uploads + Cloud Storage - 3:30 add CORS to S3 - 4:47 works now! - 5:43 summary --- # #21 Active Storage With Amazon S3 Cloud Storage - URL: https://superails.com/posts/ruby-on-rails-21-active-storage-with-amazon-s3-cloud-storage - Published: 2021-05-11 - Duration: 16:11 - Access: free - Topics: Aws - Playlists: Active Storage aws s3 for file storage: https://github.com/corsego/20-active_storage/commit/ea323d39058add4aa46868b7baf4c2d91550e60d ActiveStorage docs: https://edgeguides.rubyonrails.org/active_storage_overview.html How to use Credentials in Ruby on Rails 6? TLDR: https://blog.corsego.com/ruby-on-rails-6-credentials-tldr 0:00 The Problem 02:01 Install AWS S3 cloud storage 05:19 AWS IAM 08:35 Local storage = S3 09:13 gem aws-sdk-s3 11:05 Production 15:42 Summary ## Chapters - 0:00 The Problem - 02:01 Install AWS S3 cloud storage - 05:19 AWS IAM - 08:35 Local storage = S3 - 09:13 gem aws-sdk-s3 - 11:05 Production - 15:42 Summary --- # #20 ActiveStorage - attach avatar, attach images - URL: https://superails.com/posts/ruby-on-rails-20-activestorage-attach-avatar-attach-images - Published: 2021-05-10 - Duration: 15:19 - Access: free Source code: https://github.com/corsego/20-active_storage/ Docs: https://edgeguides.rubyonrails.org/active_storage_overview.html install active_storage, post has one avatar: https://github.com/corsego/20-active_storage/commit/51a249ff58512284f492f5703da22f1f068ec1f9 post has many images: https://github.com/corsego/20-active_storage/commit/f1775239f9c7471b0577fccd6e944964de383f10 0:00 The Problem 1:12 Install ActiveStorage 1:56 Has One Attached 4:30 Where are files stored? 06:00 Displaying images 08:19 Summary 1 09:59 Editing=Replacing Attachments 10:17 Has Many Attached 15:04 Next Steps ## Chapters - 0:00 The Problem - 1:12 Install ActiveStorage - 1:56 Has One Attached - 4:30 Where are files stored? - 06:00 Displaying images - 08:19 Summary 1 - 09:59 Editing=Replacing Attachments - 10:17 Has Many Attached - 15:04 Next Steps --- # #19 Gem Pagy - Ultimate Guide to the best pagination gem - URL: https://superails.com/posts/ruby-on-rails-19-gem-pagy-ultimate-guide-to-the-best-pagination-gem - Published: 2021-05-05 - Duration: 15:02 - Access: free Pagy Source code: https://github.com/ddnexus/pagy Pagy Homepage: https://ddnexus.github.io/pagy/how-to#gsc.tab=0 Source code: https://github.com/corsego/19-pagy Install Bootstrap 5 on Rails 6: https://blog.corsego.com/rails-bootstrap-5-yarn 0:00 Introduction 0:48 Pagy Backend 2:18 Pagy Frontent 3:26 5 posts per page 3:45 Pagy Initializer - items 5:25 Pagy Initializer - page 5:45 Pagy Initializer - cycle 6:17 Pagy Initializer - page_param 6:57 Pagy Initializer - size 8:21 Pagy Initializer - Bootstrap 10:17 Pagy Initializer - i18n 10:46 Pagy Initializer - overflow 12:20 Pagy Initializer - user items per page 14:43 Outro ## Chapters - 0:00 Introduction - 0:48 Pagy Backend - 2:18 Pagy Frontent - 3:26 5 posts per page - 3:45 Pagy Initializer - items - 5:25 Pagy Initializer - page - 5:45 Pagy Initializer - cycle - 6:17 Pagy Initializer - page_param - 6:57 Pagy Initializer - size - 8:21 Pagy Initializer - Bootstrap - 10:17 Pagy Initializer - i18n - 10:46 Pagy Initializer - overflow - 12:20 Pagy Initializer - user items per page - 14:43 Outro --- # #18 Encrypted Credentials and Global Variables (+ VIM basics) - URL: https://superails.com/posts/ruby-on-rails-18-encrypted-credentials-and-global-variables-vim-basics - Published: 2021-05-02 - Duration: 18:17 - Access: free Credentials Cheat Sheet: https://blog.corsego.com/ruby-on-rails-6-credentials-tldr Official Docs: https://edgeguides.rubyonrails.org/security.html#custom-credentials 0:00 The Problem 01:25 editing and displaying credentials 09:25 different environment credentials 12:32 credentials in production (heroku) 15:12 CHEAT SHEET 16:59 credential files for different environments ## Chapters - 0:00 The Problem - 01:25 editing and displaying credentials - 09:25 different environment credentials - 12:32 credentials in production (heroku) - 15:12 CHEAT SHEET - 16:59 credential files for different environments --- # #17 Gem Wicked PDF - generate, save, and send PDFs - URL: https://superails.com/posts/ruby-on-rails-17-gem-wicked-pdf-generate-save-and-send-pdfs - Published: 2021-04-25 - Duration: 20:11 - Access: free - Topics: pdf, html2pdf, wickedpdf gem wicked_pdf: https://github.com/mileszs/wicked_pdf source code: https://github.com/corsego/17-wicked_pdf blogpost "Complete guide to generating PDFs with gem wicked_pdf": https://blog.corsego.com/gem-wicked-pdf install gem wicked pdf: https://github.com/corsego/howto-wicked_pdf/commit/c09d4f7140c1772115f639a3fb000cfc76dc1399 send PDF as attachment: https://github.com/corsego/howto-wicked_pdf/commit/4f458597d6bc8d9480ea1b12ba9b2c4bcfafa8c3 make it work with heroku: https://github.com/corsego/howto-wicked_pdf/commit/794dc350499aa5e5421a9bf5d6c4e9657f7924c0 0:00 Installation, Basic usecases 14:05 Send PDF as attachment 18:35 wicked_pdf on heroku ## Chapters - 0:00 Installation, Basic usecases - 14:05 Send PDF as attachment - 18:35 wicked_pdf on heroku --- # #16 Gem ActsAsVotable - upvote and downvote with AJAX - URL: https://superails.com/posts/ruby-on-rails-16-gem-actsasvotable-upvote-and-downvote-with-ajax - Published: 2021-04-13 - Duration: 25:04 - Access: free Gem source: https://github.com/ryanto/acts_as_votable Source code: https://github.com/corsego/16-acts_as_votable Blogpost - install gem: https://blog.corsego.com/gem-acts-as-votable Blogpost - best ajax voting: https://blog.corsego.com/gem-acts-as-votable-2 0:00 Intro 1:08 gem acts_as_votable 4:10 upvote, unvote posts 14:30 summary 15:34 see votes count 20:26 sort posts by rating 20:57 summary of adding votes count 21:40 downvote posts 24:10 final demo ## Chapters - 0:00 Intro - 1:08 gem acts_as_votable - 4:10 upvote, unvote posts - 14:30 summary - 15:34 see votes count - 20:26 sort posts by rating - 20:57 summary of adding votes count - 21:40 downvote posts - 24:10 final demo --- # #15 Stripe API - SaaS blog - Webhooks to update Subscription Status - URL: https://superails.com/posts/ruby-on-rails-15-stripe-api-saas-blog-webhooks-to-update-subscription-status - Published: 2021-04-09 - Duration: 16:43 - Access: free - Playlists: Stripe SaaS Subscriptions app Github Repo: https://github.com/corsego/shoplify Heroku Demo: https://shoplify.herokuapp.com/ add stripe webhooks https://github.com/corsego/saasblog/commit/6fc9c4eb8c9ac158d0092dff32a0487708218dda add customer.created event to webhooks https://github.com/corsego/saasblog/commit/bcae4eadedbc81fb3cff3dc1c463380c833b0c36 fix typo in webhooks customer.created https://github.com/corsego/saasblog/commit/6bb5a61a1b6fc4f47a421fae51e9c044a561b5c8 update webhooks for subscriptions https://github.com/corsego/saasblog/commit/de5e26500a27eb34f8d6a9aaefb15318ba8ad10c better general styling https://github.com/corsego/saasblog/commit/8e8d345eb992eb1a4398466152bafb91cb41625d 0:00 Introduction 1:50 webhooks controller and routes 4:55 stripe dashboard webhooks endpoint 5:41 add webhook API to credentials 7:00 IT WORKS! Subscription status is updated by webhook! 10:37 create new webhook - stripe.customer.created 15:37 Final message, Thank you ## Chapters - 0:00 Introduction - 1:50 webhooks controller and routes - 4:55 stripe dashboard webhooks endpoint - 5:41 add webhook API to credentials - 7:00 IT WORKS! Subscription status is updated by webhook! - 10:37 create new webhook - stripe.customer.created - 15:37 Final message, Thank you --- # #14 Stripe API - SaaS blog - Stripe Checkout Session, Billing Portal - URL: https://superails.com/posts/ruby-on-rails-14-stripe-api-saas-blog-stripe-checkout-session-billing-portal - Published: 2021-04-08 - Duration: 17:07 - Access: free - Playlists: Stripe SaaS Subscriptions app Github Repo: https://github.com/corsego/saasblog Heroku Demo: https://saasblog.herokuapp.com/​ link to stripe checkout session https://github.com/corsego/saasblog/commit/30e3ba7155d1f206eba3520b8de35d8506b9a0d8 current_user can select any available plan https://github.com/corsego/saasblog/commit/044311dc77bb099f3f9aedeb54217ee2573e0981 stripe billing portal https://github.com/corsego/saasblog/commit/b0d52e5b5d6254e8d7f903689455abbb3a91a1fc 0:00 Basic Stripe Checkout Session 5:59 Select Plan for Checkout Session 9:54 Stripe Billing Portal ## Chapters - 0:00 Basic Stripe Checkout Session - 5:59 Select Plan for Checkout Session - 9:54 Stripe Billing Portal --- # #13 Stripe API - SaaS blog - gem Stripe + gem Devise, display Stripe pricing via API - URL: https://superails.com/posts/ruby-on-rails-13-stripe-api-saas-blog-gem-stripe-gem-devise-display-stripe-pricing-via-api - Published: 2021-04-07 - Duration: 25:29 - Access: free - Playlists: Stripe SaaS Subscriptions app Github Repo: https://github.com/corsego/saasblog Heroku Demo: https://saasblog.herokuapp.com/​ install gem stripe https://github.com/corsego/saasblog/commit/93b6bcd69b0bad5427fe43f044a256cbd03ae52c assign stripe_customer_id to newly registered users https://github.com/corsego/saasblog/commit/7dcba4d57debd4b4880b4584fc8413bfc19aedfc display stripe pricing on static page https://github.com/corsego/saasblog/commit/7fcc8386ec9858adb57b006b35b0eab5994b1c71 0:00 Introduction 0:47 install gem Stripe 3:16 Rails Credentials 6:28 User has one Stripe Customer Id 10:38 Stripe Products and Prices 15:04 Pricing page, display prices via Stripe API ## Chapters - 0:00 Introduction - 0:47 install gem Stripe - 3:16 Rails Credentials - 6:28 User has one Stripe Customer Id - 10:38 Stripe Products and Prices - 15:04 Pricing page, display prices via Stripe API --- # #12 Stripe API - SaaS blog - Build the MVP - URL: https://superails.com/posts/ruby-on-rails-12-stripe-api-saas-blog-build-the-mvp - Published: 2021-04-06 - Duration: 23:06 - Access: free - Playlists: Stripe SaaS Subscriptions app Github Repo: https://github.com/corsego/saasblog Heroku Demo: https://saasblog.herokuapp.com/​ Devise navigation: https://blog.corsego.com/navigation-for-devise Better scaffolds: https://blog.corsego.com/skinny-rails-generators scaffold posts https://github.com/corsego/saasblog/commit/e7a689cfe307e170f287a5cfd512321a86e52276 posts validations and navigation https://github.com/corsego/saasblog/commit/ebb38dbfd9fe5ef5b867bfabc890fc368314f6c7 gem faker to generate fake posts https://github.com/corsego/saasblog/commit/35ee68e04dbd8e3b3b6a8fee5d9f9ed42450c8d3 install gem devise https://github.com/corsego/saasblog/commit/40684e37c3aa71ef754f57e84148a99e54f13bb7 add subscription fields to users, only active subscribers can view premium posts https://github.com/corsego/saasblog/commit/d49efa0ad65f9e59259e8b7df4a649410707ec20 0:00 Introduction 1:36 Scaffold Posts 3:39 Post validations, basic navigation 5:43 gem Faker, seeds for Posts 9:34 gem Devise, devise navigation 14:24 only users with active subscription can see premium posts ## Chapters - 0:00 Introduction - 1:36 Scaffold Posts - 3:39 Post validations, basic navigation - 5:43 gem Faker, seeds for Posts - 9:34 gem Devise, devise navigation - 14:24 only users with active subscription can see premium posts --- # #11 Stripe API - Coupons, Currencies, gem money rails - URL: https://superails.com/posts/ruby-on-rails-11-stripe-api-coupons-currencies-gem-money-rails - Published: 2021-03-31 - Duration: 16:28 - Access: free - Playlists: Stripe Ecommerce App Github Repo: https://github.com/corsego/shoplify Heroku Demo: https://shoplify.herokuapp.com/​ allow_promotion_codes for checkout https://github.com/corsego/shoplify/commit/a094810ff35e8a9f14843fe5dbad2ec93a6b6d50 product can have prices with different currencies https://github.com/corsego/shoplify/commit/4870b0175c62df9c53ce155faafb909d51d6fcab gem money-rails https://github.com/corsego/shoplify/commit/ecce80910b73451691a1ff6f847172deec87807c 0:00 Stripe Coupons and Promocodes 3:32 Currencies 12:29 gem money-rails ## Chapters - 0:00 Stripe Coupons and Promocodes - 3:32 Currencies - 12:29 gem money-rails --- # #10 Stripe API - Clear Cart after Checkout, edit Stripe Price - URL: https://superails.com/posts/ruby-on-rails-10-stripe-api-clear-cart-after-checkout-edit-stripe-price - Published: 2021-03-30 - Duration: 15:34 - Access: free - Playlists: Stripe Ecommerce App Github Repo: https://github.com/corsego/shoplify Heroku Demo: https://shoplify.herokuapp.com/​ if product price is updated - create new stripe price and assing to product https://github.com/corsego/shoplify/commit/a43e7c88661becede21d76eb2f4af400cf8d64f9 empty cart after successful transaction https://github.com/corsego/shoplify/commit/c19566ef5b1c4c0a2eaf99475c5e688fb08f302b 0:00 clear Shopping Cart after Checkout 2:19 display checkout success only if session_id is present 4:11 display items purchased on success page 6:58 when local product price changes - create new Stripe Price and associate it with local product ## Chapters - 0:00 clear Shopping Cart after Checkout - 2:19 display checkout success only if session_id is present - 4:11 display items purchased on success page - 6:58 when local product price changes - create new Stripe Price and associate it with local product --- # #09 Stripe API - Shopping Cart, Checkout Multiple Products - URL: https://superails.com/posts/ruby-on-rails-9-stripe-api-shopping-cart-checkout-multiple-products - Published: 2021-03-29 - Duration: 15:43 - Access: free - Playlists: Stripe Ecommerce App Github Repo: https://github.com/corsego/shoplify Heroku Demo: https://shoplify.herokuapp.com/​ session-based Shopping Cart https://github.com/corsego/shoplify/commit/a34883db97424125ad2dcf366fce9ed151738690 Stripe Checkout for all items in Shopping Cart https://github.com/corsego/shoplify/commit/6e30df91fde4a5d29b1547b2975d91134c84ffc0 0:00 session-based Shopping Cart 7:15 Stripe Checkout for all items in Shopping Cart 14:38 Final Demo ## Chapters - 0:00 session-based Shopping Cart - 7:15 Stripe Checkout for all items in Shopping Cart - 14:38 Final Demo --- # #08 Stripe API - Update Webhooks and Success URL - URL: https://superails.com/posts/ruby-on-rails-8-stripe-api-update-webhooks-and-success-url - Published: 2021-03-26 - Duration: 13:49 - Access: free - Playlists: Stripe Ecommerce App Github Repo: https://github.com/corsego/shoplify Heroku Demo: https://shoplify.herokuapp.com/​ move logic from checkout#success to webhooks https://github.com/corsego/shoplify/commit/aa726bcfd492033f4c051f2251a76884d72d2ffd ui element to disable checkout button https://github.com/corsego/shoplify/commit/61f5dc74135322ab826bf273b96f937f2829a34c beautify data display on checkout success page https://github.com/corsego/shoplify/commit/5a5d636901bf62f5500e0cbda87067eb9c69eb07 0:00 move logic from checkout#success to webhooks 7:51 ui element to disable checkout button 9:28 beautify data display on checkout success page ## Chapters - 0:00 move logic from checkout#success to webhooks - 7:51 ui element to disable checkout button - 9:28 beautify data display on checkout success page --- # #07 Stripe API - Hijack Stripe Checkout Session, Payment Success URL - URL: https://superails.com/posts/ruby-on-rails-7-stripe-api-hijack-stripe-checkout-session-payment-success-url - Published: 2021-03-25 - Duration: 26:14 - Access: free - Playlists: Stripe Ecommerce App Github Repo: https://github.com/corsego/shoplify Heroku Demo: https://shoplify.herokuapp.com/​ Retrieve Checkout Session in view, play with Stripe API JSON https://github.com/corsego/shoplify/commit/9c7223de3687d45f965cae91424d31d0717f3eea Success and Cancel paths for stripe checkout https://github.com/corsego/shoplify/commit/487acbfbf395e9c51727ca9542cab5cc07c8f1f9 After Successful Checkout increment sales count WITHOUT Webhooks https://github.com/corsego/shoplify/commit/71eb646ad8d23a5803b63d4d25f9eb5f81e14289 0:00 Retrieve Checkout Session in view, play with Stripe API JSON 14:40 Success and Cancel paths for stripe checkout 18:40 After Successful Checkout increment sales count WITHOUT Webhooks ## Chapters - 0:00 Retrieve Checkout Session in view, play with Stripe API JSON - 14:40 Success and Cancel paths for stripe checkout - 18:40 After Successful Checkout increment sales count WITHOUT Webhooks --- # #06 Stripe API - Stripe with Devise, Create Stripe API records from Rails - URL: https://superails.com/posts/ruby-on-rails-6-stripe-api-stripe-with-devise-create-stripe-api-records-from-rails - Published: 2021-03-24 - Duration: 39:07 - Access: free - Playlists: Stripe Ecommerce App Github Repo: https://github.com/corsego/shoplify Heroku Demo: https://shoplify.herokuapp.com/ install gem devise https://github.com/corsego/shoplify/commit/895542c432e38a30270cdefb7c92ab6fbcdc6481 user is assigned stripe_customer_id after create https://github.com/corsego/shoplify/commit/5ede7fd7080ae5f68efcfcce47b50acaed289d15 assign checkout to current_user.stripe_customer_id https://github.com/corsego/shoplify/commit/5f847af0a9fdad8a022a56df2b03f5ca9ee460d9 add stripe_product_id to products https://github.com/corsego/shoplify/commit/8d9fa78420534c71fc3255d496637d35615fd114 add stripe_price_id to products, checkout session for specific product https://github.com/corsego/shoplify/commit/5e786e2e2e1f079907c0f749362932b25f976457 skip devise authentication for webhooks controller https://github.com/corsego/shoplify/commit/35a98719a848b54465482094cb89f460d22c626a 0:00 Introduction 4:22 Install Gem Devise 8:02 Associate User to Stripe Customer 14:15 Associate User to Stripe Checkout Session 17:50 Associate Products with Stripe Products, 28:08 Associate Products with Stripe Prices, Checkout for specific Price 36:13 Update Webhooks to work with Devise ## Chapters - 0:00 Introduction - 4:22 Install Gem Devise - 8:02 Associate User to Stripe Customer - 14:15 Associate User to Stripe Checkout Session - 17:50 Associate Products with Stripe Products, - 28:08 Associate Products with Stripe Prices, Checkout for specific Price - 36:13 Update Webhooks to work with Devise --- # #05 Stripe API - Pay Now Button with Stripe Checkout API - URL: https://superails.com/posts/ruby-on-rails-5-stripe-api-pay-now-button-with-stripe-checkout-api - Published: 2021-03-23 - Duration: 47:21 - Access: free - Topics: rubyonrails - Playlists: Stripe Ecommerce App Github Repo: https://github.com/corsego/shoplify Heroku Demo: https://shoplify.herokuapp.com/ Stripe Dashboard: https://dashboard.stripe.com/ Stripe Checkout: https://stripe.com/docs/payments/checkout/migration initialize app https://github.com/corsego/shoplify/commit/32240856ab97fd0d34ee7b09a05710762a9e4414 scaffold products https://github.com/corsego/shoplify/commit/c1cea7f118e2d311826852bcaa5ae5dafd08c874 product validations https://github.com/corsego/shoplify/commit/f78f741e3c5a1b444db37e412ace0f6eac680c76 install gem stripe https://github.com/corsego/shoplify/commit/32338a2a5a4c21318b1576544a1a4f955e89100b stripe checkout session https://github.com/corsego/shoplify/commit/0011a727b1272fb96ae0d8b5618b67975fdf11a8 add_sales_count_to_products https://github.com/corsego/shoplify/commit/67625c3d2297bc926e3abd4f864e32ada1100cc8 add stripe webhooks https://github.com/corsego/shoplify/commit/e874c279e60df36662da691f965a545e6289c8ec 0:00 Introduction 1:56 Create new Rails app, push to github 4:14 Create Products table, validations, basic navigation 9:43 Gem Stripe, make API calls to Stripe 12:45 Rails Credentials - hide API keys 16:47 Stripe Checkout button 30:41 Recap on integrating Stripe Checkout button 32:07 Add sales_count to Products 34:52 Push application to Heroku 36:41 Webhook to update sales_count when Stripe successfully processes payment 44:35 Final DEMO ## Chapters - 0:00 Introduction - 1:56 Create new Rails app, push to github - 4:14 Create Products table, validations, basic navigation - 9:43 Gem Stripe, make API calls to Stripe - 12:45 Rails Credentials - hide API keys - 16:47 Stripe Checkout button - 30:41 Recap on integrating Stripe Checkout button - 32:07 Add sales_count to Products - 34:52 Push application to Heroku - 36:41 Webhook to update sales_count when Stripe successfully processes payment - 44:35 Final DEMO --- # #04 Telegram API - omniauth without a gem - URL: https://superails.com/posts/ruby-on-rails-04-telegram-api-omniauth-without-a-gem - Published: 2021-03-18 - Duration: 22:30 - Access: free - Playlists: Telegram API Github repo: https://github.com/corsego/02-03-04-telegrails Live demo: https://telegrails.herokuapp.com Useful resources: https://core.telegram.org/widgets/login 0:00 Intro 0:27 Login widget 7:37 Save current user in Session 14:18 Log out (delete data from Session) 18:30 TelegramMailer to send private messages ## Chapters - 0:00 Intro - 0:27 Login widget - 7:37 Save current user in Session - 14:18 Log out (delete data from Session) - 18:30 TelegramMailer to send private messages --- # #03 Telegram API - notifications for CRUD operations - URL: https://superails.com/posts/ruby-on-rails-03-telegram-api-notifications-for-crud-operations - Published: 2021-03-17 - Duration: 15:18 - Access: free - Playlists: Telegram API Github repo: https://github.com/corsego/02-03-04-telegrails Live demo: https://telegrails.herokuapp.com 0:00 Intro 0:38 Scaffold Posts 2:52 TelegramMailer for CRUD actions 9:13 Public form to publish a telegram message ## Chapters - 0:00 Intro - 0:38 Scaffold Posts - 2:52 TelegramMailer for CRUD actions - 9:13 Public form to publish a telegram message --- # #02 Telegram API - Create Bot, use API, gem HTTParty - URL: https://superails.com/posts/ruby-on-rails-02-telegram-api-create-bot-use-api-gem-httparty - Published: 2021-03-16 - Duration: 28:09 - Access: free - Playlists: Telegram API Github repo: https://github.com/corsego/02-03-04-telegrails Useful resources: https://web.telegram.org/ https://github.com/jnunemaker/httparty Heroku demo: http://telegrails.herokuapp.com/ 0:00 Intro 1:37 Create Bot 03:54 API - get bot ID 05:02 API - get user ID, messages 07:01 create group, get group ID 8:14 API - send message 10:32 - Rails link to API request 14:22 - HTTParty, API request via controller 20:41 - set global TELEGRAM api key 25:39 - improve HTTParty method ## Chapters - 0:00 Intro - 1:37 Create Bot - 03:54 API - get bot ID - 05:02 API - get user ID, messages - 07:01 create group, get group ID - 8:14 API - send message - 10:32 - Rails link to API request - 14:22 - HTTParty, API request via controller - 20:41 - set global TELEGRAM api key - 25:39 - improve HTTParty method --- # #01 Gem Boring Generators - 10x your initial development speed - URL: https://superails.com/posts/ruby-on-rails-01-gem-boring-generators-10x-your-initial-development-speed - Published: 2021-03-15 - Duration: 12:40 - Access: free Gem source: https://github.com/abhaynikam/boring_generators Github repo: https://github.com/corsego/01-boring_generators 0:00 introduction 0:55 home index page 1:42 add gem boring_generators 2:04 add bootstrap 04:50 add fontawesome 06:25 add devise 08:26 add omniauth-github 12:16 final thoughts ## Chapters - 0:00 introduction - 0:55 home index page - 1:42 add gem boring_generators - 2:04 add bootstrap - 04:50 add fontawesome - 06:25 add devise - 08:26 add omniauth-github - 12:16 final thoughts