<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Given Ncube</title>
    <description>The latest articles on DEV Community by Given Ncube (@slimgee).</description>
    <link>https://dev.to/slimgee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F430053%2F2a4a05c2-28e0-4a86-b60f-fcb1fa4a7225.png</url>
      <title>DEV Community: Given Ncube</title>
      <link>https://dev.to/slimgee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/slimgee"/>
    <language>en</language>
    <item>
      <title>Why You Shouldn't Hire Me</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Tue, 20 Jan 2026 19:37:59 +0000</pubDate>
      <link>https://dev.to/slimgee/why-you-shouldnt-hire-me-1969</link>
      <guid>https://dev.to/slimgee/why-you-shouldnt-hire-me-1969</guid>
      <description>&lt;p&gt;Let me be completely honest with you. I'm about to tell you exactly why you shouldn't hire me as a software engineer.&lt;/p&gt;

&lt;p&gt;Unless, of course, you actually want software that &lt;em&gt;works&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Look, I get it. You're probably looking for someone who can seamlessly blend into your existing team culture of shipping fast, moving faster, and asking questions never. Someone who won't rock the boat. Someone who treats "it works on my machine" as a valid deployment strategy.&lt;/p&gt;

&lt;p&gt;That's not me. And here's why you should probably keep scrolling past my resume.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Write Hand-Artisanal Code
&lt;/h2&gt;

&lt;p&gt;While your other candidates are churning out code like a factory farm produces chicken nuggets, I'm over here hand-crafting each function like the fate of the multiverse depends on it.&lt;/p&gt;

&lt;p&gt;I know, I know. It's 2026. We're supposed to be using AI to generate code faster than we can say "technical debt." But here I am, actually &lt;em&gt;thinking&lt;/em&gt; about variable names. Writing comments that explain not just &lt;em&gt;what&lt;/em&gt; the code does, but &lt;em&gt;why&lt;/em&gt; it exists. Structuring things so that when some poor soul opens this file at 2 AM in three years, they don't immediately start updating their LinkedIn profile.&lt;/p&gt;

&lt;p&gt;My code comes with a warning label: &lt;strong&gt;May actually be readable by humans.&lt;/strong&gt; It might even, brace yourself, stand the test of time.&lt;/p&gt;

&lt;p&gt;Now, I understand this is deeply suspicious in an industry where "legacy code" means "anything written last quarter." But I can't help myself. I was raised to believe that code should be a love letter to your future self, not a passive-aggressive sticky note that just says "good luck lol."&lt;/p&gt;

&lt;h2&gt;
  
  
  I have strong opinions
&lt;/h2&gt;

&lt;p&gt;Here's the thing that really makes me unemployable: software has become &lt;em&gt;terrible&lt;/em&gt; in recent years, and I aim to rectify that one line of code at a time.&lt;/p&gt;

&lt;p&gt;Remember when apps didn't require 8GB of RAM just to display a to-do list? Remember when updates actually fixed things instead of breaking three unrelated features? Remember when software loaded in less time than it takes to brew coffee?&lt;/p&gt;

&lt;p&gt;I do. And I can't forget it.&lt;/p&gt;

&lt;p&gt;I'm on a personal crusade to make software that doesn't suck. Is this quixotic? Absolutely. Is it marketable? Questionable. Will I bore you at the team lunch with rants about how Slack shouldn't use more memory than Photoshop? &lt;em&gt;Definitely&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If I can only make one piece of software great and bug-free, like they used to do back in the 90s when Netscape Navigator was considered cutting-edge technology and software shipped on physical disks so you &lt;em&gt;had&lt;/em&gt; to get it right, it won't be curing cancer, It probably won't even be that interesting. But I'd rest knowing I made a difference to the world. Somewhere, someone will open an application I worked on, it will launch in under two seconds, and they will experience a brief moment of joy in our dystopian hellscape of spinning loading wheels.&lt;/p&gt;

&lt;p&gt;This makes me dangerously passionate about my work. I might actually suggest we fix bugs instead of marking them as "won't fix" or "works as intended." I could potentially recommend we spend an extra day making something robust instead of shipping it and dealing with the consequences when it inevitably crashes in production during the Super Bowl.&lt;/p&gt;

&lt;p&gt;Revolutionary, I know.&lt;/p&gt;

&lt;h2&gt;
  
  
  I'm Actually Competent (In This Economy?)
&lt;/h2&gt;

&lt;p&gt;Now that everyone is chasing abstraction layers like they're Pokémon cards, I've become that weird person who's &lt;em&gt;actually competent&lt;/em&gt; at the fundamentals.&lt;/p&gt;

&lt;p&gt;I learned to code before "vibe coding" was a thing. Before you could just describe what you wanted to ChatGPT and pray. I actually understand what's happening beneath the seventeen layers of frameworks, abstractions, and middleware that modern development has become.&lt;/p&gt;

&lt;p&gt;Can I write a for-loop without Googling the syntax? Yes, and I'm not afraid to admit it.&lt;/p&gt;

&lt;p&gt;Do I know what a pointer is? Disturbingly, yes.&lt;/p&gt;

&lt;p&gt;Can I debug an issue without immediately opening a new tab to ask an AI what's wrong? I mean, I'll &lt;em&gt;eventually&lt;/em&gt; ask the AI, but I'll at least try to figure it out first like some kind of cave person.&lt;/p&gt;

&lt;p&gt;I realize this makes me sound like I'm a thousand years old and about to start a sentence with "back in my day." But here's the truth: understanding the fundamentals means I actually know &lt;em&gt;why&lt;/em&gt; something isn't working, not just &lt;em&gt;that&lt;/em&gt; it isn't working. It means when someone suggests adding another framework to our already-tottering Jenga tower of dependencies, I might, and I'm sorry in advance, ask if we really need it.&lt;/p&gt;

&lt;p&gt;I know. Awkward.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Believe In Testing (The Audacity)
&lt;/h2&gt;

&lt;p&gt;I write tests. Not because someone makes me. Not because it's in the ticket. But because I have this wild idea that code should do what we think it does.&lt;/p&gt;

&lt;p&gt;Automated tests. Unit tests. Integration tests. The whole embarrassing suite.&lt;/p&gt;

&lt;p&gt;"But the code is self-documenting!" you cry. Cool. The tests are self-validating. We can both be right.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Actually Read Documentation
&lt;/h2&gt;

&lt;p&gt;For &lt;em&gt;fun&lt;/em&gt;. I know that makes me sound like a serial killer, but it's true.&lt;/p&gt;

&lt;p&gt;I read release notes. I peruse changelogs. I have opinions about semantic versioning. When a library updates, I don't just blindly upgrade and hope for the best—I actually check what changed.&lt;/p&gt;

&lt;p&gt;I'm basically a liability at this point.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Will Actually Clean Up The Vibe Slop Your Team Has Been Churning Out
&lt;/h2&gt;

&lt;p&gt;Let's address the elephant in the room: that codebase you've been building for the last four years.&lt;/p&gt;

&lt;p&gt;You know the one. The "we'll clean it up later" one. The "it was a hackathon project that somehow became production" one. The one where every file starts with &lt;code&gt;// TODO: refactor this&lt;/code&gt; and ends with &lt;code&gt;// I'm so sorry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The codebase where half the functions are named &lt;code&gt;doTheThingV2Final_actuallyFinal_USE_THIS_ONE()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'm not here to judge. Well, okay, I'm a &lt;em&gt;little&lt;/em&gt; here to judge. But mostly I'm here to &lt;em&gt;fix it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Because here's what happened: for the last four years, your team has been vibing. They've been in the flow state. They've been shipping features faster than you can say "technical debt." And bless their hearts, they've created what can only be described as "vibe slop"—code that works (mostly) but reads like it was written by a random word generator having a fever dream.&lt;/p&gt;

&lt;p&gt;I get it. Deadlines were tight. The CEO wanted that feature yesterday. "We'll refactor it later" seemed like a reasonable compromise at 11 PM on a Thursday.&lt;/p&gt;

&lt;p&gt;But now "later" is here, and that code is still there, lurking in your repository like a cursed artifact that no one wants to touch.&lt;/p&gt;

&lt;p&gt;Here's my pitch: I will roll up my sleeves and venture into that haunted codebase. I will decipher the variable names that seem to be inside jokes from 2021. I will untangle the functions that are 800 lines long because "it's all related, technically." I will find out what &lt;code&gt;utilsHelperFinalV3.ts&lt;/code&gt; actually does (spoiler: probably way too much).&lt;/p&gt;

&lt;p&gt;And then, and this is the dangerous part, I will &lt;em&gt;clean it up&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Not a full rewrite. I'm not a monster. But a steady, methodical campaign of making things better. Extracting functions. Adding types. Writing those tests that everyone agreed were important but nobody had time for. Removing the commented-out code from 2022 that's been sitting there "just in case."&lt;/p&gt;

&lt;p&gt;Will this make me popular? Probably not. Will I have to have awkward conversations about why we have three different date formatting utilities? Absolutely. Will someone say "but it works fine, why change it?" every single standup? You bet.&lt;/p&gt;

&lt;p&gt;But six months from now, when a new feature takes days instead of weeks because the code is actually maintainable, when bugs stop appearing in "completely unrelated" parts of the system, when new hires don't immediately start looking for new jobs, you'll thank me.&lt;/p&gt;

&lt;p&gt;Or you'll fire me. It's really a coin flip.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Think "Move Fast and Break Things" Should Only Apply to Your Fitness Routine
&lt;/h2&gt;

&lt;p&gt;Look, I appreciate the hustle culture as much as the next anxiety-ridden millennial. But maybe—and hear me out—we could move at a &lt;em&gt;reasonable pace&lt;/em&gt; and break &lt;em&gt;fewer&lt;/em&gt; things?&lt;/p&gt;

&lt;p&gt;I know this makes me sound like a dinosaur. Next I'll be suggesting we have meetings about meetings and require three levels of approval to change a button color. But there's a middle ground between "cowboy coding directly to production" and "six-month approval process for a typo fix."&lt;/p&gt;

&lt;p&gt;That middle ground is called "having literally any process at all," and I'm a big fan.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Here's The Deal
&lt;/h2&gt;

&lt;p&gt;If you've made it this far and you're thinking "this person sounds insufferable," you're probably right. We wouldn't be a good fit.&lt;/p&gt;

&lt;p&gt;But if you're tired of software that crashes more often than it runs, if you've ever stayed up at night wondering why a simple app needs permission to access your contacts and your firstborn child, if you've ever whispered to yourself "there has to be a better way"—then maybe, just maybe, my particular brand of insufferable is exactly what you need.&lt;/p&gt;

&lt;p&gt;I'm not promising to revolutionize your codebase overnight. I'm not claiming to be a 10x engineer (though I am definitely a 1x engineer, which in this market might actually be impressive). I won't rewrite everything in Rust or suggest we migrate to microservices for your blog.&lt;/p&gt;

&lt;p&gt;What I will do is write code that works. Code that's maintainable. Code that won't make the next developer curse your name and mine. Code that might—if we're very lucky—still be running in five years without requiring a full-time archaeologist to maintain it.&lt;/p&gt;

&lt;p&gt;I'll make software a little less terrible, one semicolon at a time.&lt;/p&gt;

&lt;p&gt;And if that sounds like a problem to you, well, there are plenty of other candidates out there who are perfectly happy shipping fast and apologizing later.&lt;/p&gt;

&lt;p&gt;But if you want to build something that actually &lt;em&gt;lasts&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Let's talk. &lt;a href="mailto:given@flixtechs.co.zw"&gt;given@flixtechs.co.zw&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;P.S. — Yes, I know semicolons are optional in some languages. That's not the point. The point is I care about the details. See? Already insufferable.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Deploying Rails Apps to a Caprover Instance</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Tue, 08 Oct 2024 12:43:45 +0000</pubDate>
      <link>https://dev.to/slimgee/deploying-rails-apps-to-a-caprover-instance-1de1</link>
      <guid>https://dev.to/slimgee/deploying-rails-apps-to-a-caprover-instance-1de1</guid>
      <description>&lt;p&gt;A few days ago I wrote an article on deploying rails &lt;a href="https://givenis.me/deploying-multiple-rails-apps-on-the-same-server-with-puma-nginx" rel="noopener noreferrer"&gt;apps with nginx + puma + mina&lt;/a&gt;. Some people in the comments suggested I try Caprover, Dokku and other open source PaaS software. Dokku didn’t cut it for me, it’s a good piece of software it just wasn’t for me.&lt;/p&gt;

&lt;p&gt;I’m aware of kamal and it’s also a great piece of software especially since it’s a first class rails citizen, however as always competition can’t hurt. (I also didn’t like how kamal is full of configuration) I mean, rails at it’s core is convention over configuration and kamal in it’s current state is the opposite of that, which I totally understand it’s only on version 2 and writing software takes a lot of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Caprover
&lt;/h2&gt;

&lt;p&gt;Back to this article, the first thing you need is to have caprover installed on your vps/server. If you don’t have one, I recommend getting one from &lt;a href="https://www.hetzner.com/" rel="noopener noreferrer"&gt;Hertzner&lt;/a&gt;. After login and setting up your ssh and firewall, we now move to install caprover&lt;/p&gt;

&lt;p&gt;Caprover uses docker to deploy apps and caprover itself runs on docker, our first dependency is docker&lt;/p&gt;

&lt;p&gt;Follow instructions on this link to install docker&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.docker.com/engine/install/debian/" rel="noopener noreferrer"&gt;https://docs.docker.com/engine/install/debian/&lt;/a&gt; I prefer to use debian for server, you can select your target OS from the left sidebar&lt;/p&gt;

&lt;p&gt;After docker is fully installed, install caprover by following instructions on this link&lt;/p&gt;

&lt;p&gt;&lt;a href="https://caprover.com/docs/get-started.html" rel="noopener noreferrer"&gt;https://caprover.com/docs/get-started.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Setup
&lt;/h2&gt;

&lt;p&gt;After finishing all the caprover setup it’s time to install our databases. I prefer to use mysql, some of you prefer postgres&lt;/p&gt;

&lt;p&gt;Visit your caprover captain dashboard, navigate to apps and click on “one click install” apps&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728388628440%2F6b2c7e28-1ff7-4469-810c-c532060b59a5.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728388628440%2F6b2c7e28-1ff7-4469-810c-c532060b59a5.png%2520align%3D" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next page search for “mariadb”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728388655277%2F4bb1fae6-7549-4add-91dc-af7afa042813.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728388655277%2F4bb1fae6-7549-4add-91dc-af7afa042813.png%2520align%3D" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select mariadb and set the parameter slike name, root password etc&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728388683322%2Fa4053170-f05e-45f7-aaf0-2e1a588962d2.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728388683322%2Fa4053170-f05e-45f7-aaf0-2e1a588962d2.png%2520align%3D" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you’re done editing click deploy.&lt;/p&gt;

&lt;p&gt;Next set allow this database to be accessible from the host machine on port 3306 or whatever port postgres uses. This allows you to login to the database shell as root to create users, databases etc, from the host machine&lt;/p&gt;

&lt;p&gt;Select the newly created database app and go to app configs and add port mappings&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728388833813%2F74627146-f830-43f7-b987-1ca5b37406bc.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728388833813%2F74627146-f830-43f7-b987-1ca5b37406bc.png%2520align%3D" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up database users
&lt;/h2&gt;

&lt;p&gt;This part is entirely up to you, this how I did it when I was deploying &lt;a href="https://pulse.welodge.co.zw" rel="noopener noreferrer"&gt;Pulse&lt;/a&gt; on caprover&lt;/p&gt;

&lt;p&gt;SSH to your host machine and connect to the database using the root password you created in the previous step&lt;/p&gt;

&lt;p&gt;Create a database user, eg, &lt;code&gt;rails&lt;/code&gt;, do not include hostname&lt;/p&gt;

&lt;p&gt;Grant that user permissions on all &lt;code&gt;%_%&lt;/code&gt; databases which follows the format of database names created by rails eg, for pulse it will be &lt;code&gt;pulse_production&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After this you’re done with the server, it’s time to deploy your rails app&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy rails apps
&lt;/h2&gt;

&lt;p&gt;In the caprover dash, create an app, tick the “has persistance” flag&lt;/p&gt;

&lt;p&gt;After creating go to apps config and add env variables, I prefer to use rails credentials,&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;RAILS_MASTER_KEY&lt;/code&gt; variable with the value from your &lt;code&gt;config/master.key&lt;/code&gt; or &lt;code&gt;config/credentials/production.key&lt;/code&gt;. If you’re using credentials instead of env vars this is everything&lt;/p&gt;

&lt;p&gt;If you’re using active storage with local driver, add persistent path mapping to map the &lt;code&gt;storage&lt;/code&gt; directory to actual path on the host machine&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728389360108%2F3d31d4c9-1f6f-455a-8b00-34c93c3e33f6.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1728389360108%2F3d31d4c9-1f6f-455a-8b00-34c93c3e33f6.png%2520align%3D" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the database configuration to use the user you created in the previous step, since this database is in a docker network, get the hostname from caprover dash.&lt;/p&gt;

&lt;p&gt;Like I said at the start of this article, caprover uses docker, the dockerfile that comes default with rails will suffice without any changes&lt;/p&gt;

&lt;p&gt;Navigate to your project directory and run &lt;code&gt;caprover deploy&lt;/code&gt; follow the prompts to select app, etc, and that’s it your app is live&lt;/p&gt;

&lt;p&gt;Let me know what you think about caprover, I personally think it’s awesome, follow me here on Hashnode for more articles on rails and web apps development&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>devops</category>
    </item>
    <item>
      <title>Deploying Multiple Rails Apps on The Same Server with Puma + Nginx</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Tue, 01 Oct 2024 17:03:36 +0000</pubDate>
      <link>https://dev.to/slimgee/deploying-multiple-rails-apps-on-the-same-server-with-puma-nginx-2pjk</link>
      <guid>https://dev.to/slimgee/deploying-multiple-rails-apps-on-the-same-server-with-puma-nginx-2pjk</guid>
      <description>&lt;p&gt;&lt;strong&gt;If Kamal is not for you then this article is for you. There’s a lot of my opinions in this article, feel free to jump to the setup section&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After coming back to rails from Laravel I found that it wasn’t clear how to run multiple rails apps on the same server. If you google “deploy rails” right now you see something about deploying to heroku which was the thing when I started using rails in rails 5.&lt;/p&gt;

&lt;p&gt;With PHP-FPM + Nginx I could run an infinite number of apps on a $5/month server. When it was time to deploy &lt;a href="https://pulse.welodge.co.zw" rel="noopener noreferrer"&gt;Pulse&lt;/a&gt;, as always I did a deep dive, most of the articles on the internet assumed I wanted to run 1 rails app on 1 vm, my first attempt worked, I ran puma, I don’t even remember how, it worked but puma was listening on port 300, I just imagined multiple apps each on it’s own port would be very confusing&lt;/p&gt;

&lt;p&gt;To deploy multiple rails apps on the same sever we need the following&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Nginx&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ruby (of course)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puma (bundled with rails)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mina&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how it works, we tell nginx that if it sees a given host name say &lt;code&gt;pulse.welodge.co.zw&lt;/code&gt; forward that http request to this http server. This server, we will call “upstream” from now on can be listening on a port or unix socket, which is not technically a file but for this article it’s just a file. If we have 3 apps each could be listening on&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/var/www/app1/tmp/app1.sock&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/var/www/app2/tmp/app2.sock&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;etc&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes way more sense than opening several local ports which maybe hard to keep track of as you add more apps to the server&lt;/p&gt;

&lt;h3&gt;
  
  
  Talk is cheap, show me the code
&lt;/h3&gt;

&lt;p&gt;To follow along this tutorial you will need a Linux server. I recommend getting one from &lt;a href="https://www.hetzner.com/" rel="noopener noreferrer"&gt;Hertzner&lt;/a&gt;, they have very good pricing with really good performance. Use Ubuntu or Debian, for servers I would recommend Debian because we generally want our servers to be stable. Any Linux distro would do by the way&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial setup
&lt;/h2&gt;

&lt;p&gt;After getting your server, ssh into the server as root&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@&amp;lt;your-server-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the required software&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;nginx mariadb-server git build-essential zip unzip curl neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just installed nginx, a database, I prefer mysql you can install postgres if you use postgres git to clone our repos build-essential for build tools, zip utils and curl and finally neovim***[I refuse to use nano more than once per computer]***&lt;/p&gt;

&lt;p&gt;Enable nginx auto start&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be everything we need. Next create 2 user accounts, 1 is yours as the admin of this server the other is deployment user which is zero privileges, this will be the user our apps are going to run as.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;useradd given &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a user called &lt;code&gt;given&lt;/code&gt; the &lt;code&gt;-m&lt;/code&gt; flag creates a home directory in &lt;code&gt;/home/given&lt;/code&gt; &lt;code&gt;-G&lt;/code&gt; adds the user to sudo group so we can run &lt;code&gt;sudo&lt;/code&gt; commands &lt;code&gt;-U&lt;/code&gt; creates a user group with name &lt;code&gt;given&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now set a password for this user&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;passwd given
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open another terminal window and add ssh keys to this newly created account&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id &amp;lt;newuser&amp;gt;@&amp;lt;your-server-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the password you just created, if it fails go back to the root terminal window and restart ssh&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create our deployment user&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; deploy &lt;span class="nt"&gt;-U&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this set a password and repeat the process of adding ssh keys.&lt;/p&gt;

&lt;p&gt;It’s not advisable to run a linux machine as root, we need to disable root login on this machine, disable password authentication. Edit the &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvim /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find an entry that says &lt;code&gt;PermitRootLogin&lt;/code&gt; and change that to &lt;code&gt;no&lt;/code&gt; &lt;code&gt;PasswordAuthentication&lt;/code&gt; change to &lt;code&gt;no&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Save the file and restart ssh. Before we leave this root account, setup a firewall, using the uncomplicated firewall&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;ufw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allow only port 80, 443, and 22&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw allow 22
ufw allow 80
ufw allow 443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the firewall and exit the root account&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now ssh to this server with the deploy account and install ruby and node&lt;/p&gt;

&lt;p&gt;Follow this tutorial to install ruby &lt;a href="https://gorails.com/setup/ubuntu/24.04" rel="noopener noreferrer"&gt;https://gorails.com/setup/ubuntu/24.04&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install node with nvm: &lt;a href="https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating" rel="noopener noreferrer"&gt;https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do the same for your user account&lt;/p&gt;

&lt;p&gt;By now you have ruby and node installed on your server and it’s ready&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment with mina
&lt;/h2&gt;

&lt;p&gt;To semi automate our deployment from now we will use mina to setup our environment and deploy from github&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;deploy&lt;/code&gt; user account generate ssh keys to authenticate with github&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; rsa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;copy the the file in &lt;code&gt;~/.ssh/id_rsa.pub&lt;/code&gt; and paste the values in on github &lt;code&gt;Settings &amp;gt; SSH and GPG Keys &amp;gt; New SSH Key&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In your rails project add mina as a dependency&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add mina &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; bundle &lt;span class="nb"&gt;exec &lt;/span&gt;mina init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your &lt;code&gt;config/deploy.rb&lt;/code&gt; and replace everything with the following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;require &lt;span class="s2"&gt;"mina/rails"&lt;/span&gt;
require &lt;span class="s2"&gt;"mina/git"&lt;/span&gt;

require &lt;span class="s2"&gt;"mina/rbenv"&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:application_name, &lt;span class="s2"&gt;"app_name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:domain, &lt;span class="s2"&gt;"app_domain_name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:deploy_to, &lt;span class="s2"&gt;"/var/www/app_domain_name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:repository, &lt;span class="s2"&gt;"git url"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:branch, &lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; :ssh_options, &lt;span class="s2"&gt;"-o StrictHostKeyChecking=no"&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:user, &lt;span class="s2"&gt;"deploy"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:shared_dirs, fetch&lt;span class="o"&gt;(&lt;/span&gt;:shared_dirs, &lt;span class="o"&gt;[])&lt;/span&gt;.push&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tmp/sockets"&lt;/span&gt;, &lt;span class="s2"&gt;"tmp/pids"&lt;/span&gt;, &lt;span class="s2"&gt;"log"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="c"&gt;# add storage to shared dirs&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:shared_dirs, fetch&lt;span class="o"&gt;(&lt;/span&gt;:shared_dirs, &lt;span class="o"&gt;[])&lt;/span&gt;.push&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;:shared_files, fetch&lt;span class="o"&gt;(&lt;/span&gt;:shared_files, &lt;span class="o"&gt;[])&lt;/span&gt;.push&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"config/credentials/production.key"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;# load nvim&lt;/span&gt;
task&lt;span class="o"&gt;(&lt;/span&gt;:remote_environment&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"rbenv:load"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'export NVM_DIR="$HOME/.nvm"'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[ -s "$NVM_DIR/nvm.sh" ] &amp;amp;&amp;amp; \. "$NVM_DIR/nvm.sh"'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"nvm install 22"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
end

&lt;span class="c"&gt;# install latest ruby if it does not exisit&lt;/span&gt;
task&lt;span class="o"&gt;(&lt;/span&gt;:setup&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"rbenv install 3.3.5 --skip-existing"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"gem install bundler"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
end

desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Deploys the current version to the server."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
task&lt;span class="o"&gt;(&lt;/span&gt;:deploy&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;deploy &lt;span class="k"&gt;do
    &lt;/span&gt;invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"git:clone"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"deploy:link_shared_paths"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"bundle:install"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"rails:db_create"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"rails:db_migrate"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"yes y | yarn set version stable"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# optional if you don't use yarn&lt;/span&gt;
    invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"rails:assets_precompile"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"deploy:cleanup"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    on&lt;span class="o"&gt;(&lt;/span&gt;:launch&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
      &lt;/span&gt;in_path&lt;span class="o"&gt;(&lt;/span&gt;fetch&lt;span class="o"&gt;(&lt;/span&gt;:current_path&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do
        &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"mkdir -p tmp/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"touch tmp/restart.txt"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user restart #{fetch(:application_name)}-sidekiq.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;# command("bundle binstubs puma")&lt;/span&gt;
        invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"puma:restart"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        invoke&lt;span class="o"&gt;(&lt;/span&gt;:&lt;span class="s2"&gt;"sidekiq:restart"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      end
    end
  end
  &lt;span class="c"&gt;# you can use `run :local` to run tasks on local machine before or after the deploy scripts&lt;/span&gt;
  run&lt;span class="o"&gt;(&lt;/span&gt;:local&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Done!&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
end

namespace&lt;span class="o"&gt;(&lt;/span&gt;:sidekiq&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Start Sidekiq"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:start&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;in_path&lt;span class="o"&gt;(&lt;/span&gt;fetch&lt;span class="o"&gt;(&lt;/span&gt;:current_path&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do
      &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user start #{fetch(:application_name)}-sidekiq.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    end
  end

  desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Restart Sidekiq"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:restart&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;in_path&lt;span class="o"&gt;(&lt;/span&gt;fetch&lt;span class="o"&gt;(&lt;/span&gt;:current_path&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do
      &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user restart #{fetch(:application_name)}-sidekiq.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    end
  end

  desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Check Sidekiq status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:status&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user status #{fetch(:application_name)}-sidekiq.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"journalctl --user -xeu #{fetch(:application_name)}-sidekiq.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  end

  desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Tail Sidekiq logs"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:log&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"journalctl --user -u #{fetch(:application_name)}-sidekiq.service -f"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  end

  desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Setup Sidekiq systemd service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:setup&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;puts&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Setting up systemd service for Sidekiq.."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    path &lt;span class="o"&gt;=&lt;/span&gt; fetch&lt;span class="o"&gt;(&lt;/span&gt;:deploy_to&lt;span class="o"&gt;)&lt;/span&gt;
    content &lt;span class="o"&gt;=&lt;/span&gt; File.read&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./config/sidekiq.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.gsub&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;APP_PATH&amp;gt;"&lt;/span&gt;, path&lt;span class="o"&gt;)&lt;/span&gt;
    File.write&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./tmp/sidekiq.service"&lt;/span&gt;, content&lt;span class="o"&gt;)&lt;/span&gt;
    run&lt;span class="o"&gt;(&lt;/span&gt;:local&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
      &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"scp ./tmp/sidekiq.service #{fetch(:user)}@#{fetch(:domain)}:~/.config/systemd/user/#{fetch(:application_name)}-sidekiq.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    end

    &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user daemon-reload"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user enable #{fetch(:application_name)}-sidekiq.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  end
end

namespace&lt;span class="o"&gt;(&lt;/span&gt;:puma&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Start Puma"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:start&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;in_path&lt;span class="o"&gt;(&lt;/span&gt;fetch&lt;span class="o"&gt;(&lt;/span&gt;:current_path&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c"&gt;# command("bundle binstubs puma")&lt;/span&gt;
      &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user start #{fetch(:application_name)}-puma.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    end
  end

  desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Restart Puma"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:restart&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;in_path&lt;span class="o"&gt;(&lt;/span&gt;fetch&lt;span class="o"&gt;(&lt;/span&gt;:current_path&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do
      &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user restart #{fetch(:application_name)}-puma.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    end
  end

  desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Check Puma status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:status&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user status #{fetch(:application_name)}-puma.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"journalctl --user -xeu #{fetch(:application_name)}-puma.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  end

  desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Tail Puma logs"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:log&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"journalctl --user -u #{fetch(:application_name)}-puma.service -f"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  end

  desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Setup Puma systemd service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  task&lt;span class="o"&gt;(&lt;/span&gt;:setup&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;puts&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Setting up systemd service for Puma.."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    path &lt;span class="o"&gt;=&lt;/span&gt; fetch&lt;span class="o"&gt;(&lt;/span&gt;:deploy_to&lt;span class="o"&gt;)&lt;/span&gt;
    content &lt;span class="o"&gt;=&lt;/span&gt; File.read&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./config/puma.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.gsub&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;APP_PATH&amp;gt;"&lt;/span&gt;, path&lt;span class="o"&gt;)&lt;/span&gt;
    File.write&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./tmp/puma.service"&lt;/span&gt;, content&lt;span class="o"&gt;)&lt;/span&gt;
    run&lt;span class="o"&gt;(&lt;/span&gt;:local&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
      &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"scp ./tmp/puma.service #{fetch(:user)}@#{fetch(:domain)}:~/.config/systemd/user/#{fetch(:application_name)}-puma.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    end

    &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"touch #{fetch(:deploy_to)}/shared/tmp/pids/server.pid"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user daemon-reload"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"systemctl --user enable #{fetch(:application_name)}-puma.service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  end
end

desc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Copy production key to server"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
task&lt;span class="o"&gt;(&lt;/span&gt;:copy_secrets&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;run&lt;span class="o"&gt;(&lt;/span&gt;:local&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"scp ./config/credentials/production.key #{fetch(:user)}@#{fetch(:domain)}:#{fetch(:deploy_to)}/shared/config/credentials/production.key"&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break down what this code does, mina needs to know the application name, domain name, the ssh user it will login with, we set that at the beginning&lt;/p&gt;

&lt;p&gt;We then set shared directories that will not change across deployments like the &lt;code&gt;tmp&lt;/code&gt; dir &lt;code&gt;storage&lt;/code&gt; and the &lt;code&gt;credentials&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next we setup environment, we load &lt;code&gt;rbenv&lt;/code&gt; and &lt;code&gt;nvm&lt;/code&gt;, the setup task will install ruby and bundler&lt;/p&gt;

&lt;p&gt;the deploy task will &lt;code&gt;git clone&lt;/code&gt; symlink the shared directories, run bundler, migrate the database and precompile assets, after deployment is done, we restart puma and sidekiq if you use sidekiq&lt;/p&gt;

&lt;p&gt;We have setup for sidekiq, puma&lt;/p&gt;

&lt;p&gt;in the config dir add a file called &lt;code&gt;puma.service&lt;/code&gt; we will be using systemd to manage multiple puma instances running at the same time&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# config/puma.service&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Puma HTTP Server
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network.target

&lt;span class="c"&gt;# Uncomment for socket activation (see below)&lt;/span&gt;
&lt;span class="c"&gt;# Requires=puma.socket&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="c"&gt;# Puma supports systemd's `Type=notify` and watchdog service&lt;/span&gt;
&lt;span class="c"&gt;# monitoring, as of Puma 5.1 or later.&lt;/span&gt;
&lt;span class="c"&gt;# On earlier versions of Puma or JRuby, change this to `Type=simple` and remove&lt;/span&gt;
&lt;span class="c"&gt;# the `WatchdogSec` line.&lt;/span&gt;
&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;notify

&lt;span class="c"&gt;# If your Puma process locks up, systemd's watchdog will restart it within seconds.&lt;/span&gt;
&lt;span class="nv"&gt;WatchdogSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10

&lt;span class="c"&gt;# Preferably configure a non-privileged user&lt;/span&gt;


&lt;span class="c"&gt;# The path to your application code root directory.&lt;/span&gt;
&lt;span class="c"&gt;# Also replace the "&amp;lt;YOUR_APP_PATH&amp;gt;" placeholders below with this path.&lt;/span&gt;
&lt;span class="c"&gt;# Example /home/username/myapp&lt;/span&gt;
&lt;span class="nv"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;APP_PATH&amp;gt;

&lt;span class="c"&gt;# Helpful for debugging socket activation, etc.&lt;/span&gt;
&lt;span class="c"&gt;# Environment=PUMA_DEBUG=1&lt;/span&gt;

&lt;span class="c"&gt;# SystemD will not run puma even if it is in your path. You must specify&lt;/span&gt;
&lt;span class="c"&gt;# an absolute URL to puma. For example /usr/local/bin/puma&lt;/span&gt;
&lt;span class="c"&gt;# Alternatively, create a binstub with `bundle binstubs puma --path ./sbin` in the WorkingDirectory&lt;/span&gt;
&lt;span class="c"&gt;# ExecStart=&amp;lt;FULLPATH&amp;gt;/sbin/puma -C &amp;lt;YOUR_APP_PATH&amp;gt;/puma.rb&lt;/span&gt;


&lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"PATH=/home/deploy/.rbenv/shims:/home/deploy/.rbenv/bin:/usr/local/bin:/usr/bin:/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;APP_PATH&amp;gt;/current/bin/bundle &lt;span class="nb"&gt;exec &lt;/span&gt;puma &lt;span class="nt"&gt;-e&lt;/span&gt; production &lt;span class="nt"&gt;--pidfile&lt;/span&gt; &amp;lt;APP_PATH&amp;gt;/shared/tmp/pids/server.pid &lt;span class="nt"&gt;-C&lt;/span&gt; &amp;lt;APP_PATH&amp;gt;/current/config/puma.rb &lt;span class="nt"&gt;-b&lt;/span&gt; unix://&amp;lt;APP_PATH&amp;gt;/shared/tmp/puma.sock &amp;lt;APP_PATH&amp;gt;/current/config.ru

&lt;span class="c"&gt;# Variant: Use `bundle exec puma` instead of binstub&lt;/span&gt;
&lt;span class="c"&gt;# Variant: Specify directives inline.&lt;/span&gt;
&lt;span class="c"&gt;# ExecStart=&amp;lt;FULLPATH&amp;gt;/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&amp;amp;cert=cert.pem&lt;/span&gt;


&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy paste as it is, you might change the PATH config if your deploy user is not actualy &lt;code&gt;deploy&lt;/code&gt; but everything else stay the same. Also note that we are binding puma to a unix socket instead of a port&lt;/p&gt;

&lt;p&gt;If you are using sidekiq create a &lt;code&gt;sidekiq.service&lt;/code&gt; in the config dir&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sidekiq
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;syslog.target network.target

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;simple
&lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"PATH=/home/deploy/.rbenv/shims:/home/deploy/.rbenv/bin:/usr/local/bin:/usr/bin:/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;APP_PATH&amp;gt;
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;APP_PATH&amp;gt;/current/bin/bundle &lt;span class="nb"&gt;exec &lt;/span&gt;sidekiq &lt;span class="nt"&gt;-e&lt;/span&gt; production &lt;span class="nt"&gt;-C&lt;/span&gt; &amp;lt;APP_PATH&amp;gt;/current/config/sidekiq.yml &lt;span class="nt"&gt;-r&lt;/span&gt; &amp;lt;APP_PATH&amp;gt;/current
&lt;span class="nv"&gt;ExecReload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/kill &lt;span class="nt"&gt;-TSTP&lt;/span&gt; &lt;span class="nv"&gt;$MAINPID&lt;/span&gt;

&lt;span class="c"&gt;# if we crash, restart&lt;/span&gt;
&lt;span class="nv"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on-failure
&lt;span class="c"&gt;#Restart=always&lt;/span&gt;

&lt;span class="c"&gt;# output goes to /var/log/syslog&lt;/span&gt;
&lt;span class="c"&gt;#StandardOutput=syslog&lt;/span&gt;
&lt;span class="c"&gt;#StandardError=syslog&lt;/span&gt;

&lt;span class="c"&gt;# This will default to "bundler" if we don't specify it&lt;/span&gt;
&lt;span class="c"&gt;#SyslogIdentifier=sidekiq&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again don’t change anything else except for the username from deploy to your configured username&lt;/p&gt;

&lt;p&gt;Now setup our deployment environment with mina setup&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;mina setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After it’s done, copy the secrets to product, I prefer to use secretes instead of environment variables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;mina copy_secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will copy secrets from your local machine to production&lt;/p&gt;

&lt;p&gt;Setup puma and sidekiq&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;mina puma:setup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; bundle &lt;span class="nb"&gt;exec &lt;/span&gt;mina sidekiq:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the only thing left is our nginx config. Where you store this file is up to you, I’ve been thinking about this for while, you can store it in the &lt;code&gt;config/nginx.conf&lt;/code&gt;, add it to the &lt;code&gt;shared_files&lt;/code&gt; with mina like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:shared_files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:shared_files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"config/nginx.conf"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or put it in the &lt;code&gt;/etc/nginx/sites-available&lt;/code&gt; dir it’s up to you, here is the base config that I use for my apps&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;upstream &amp;lt;app_name&amp;gt;-puma &lt;span class="o"&gt;{&lt;/span&gt;
  server unix:///var/www/&amp;lt;app-domain-name&amp;gt;/shared/tmp/puma.sock&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Define upstream server for myapp&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

server &lt;span class="o"&gt;{&lt;/span&gt;
  server_name &amp;lt;app-domain-name&amp;gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Server name for this nginx server block&lt;/span&gt;
  listen 80&lt;span class="p"&gt;;&lt;/span&gt;

  root /var/www/&amp;lt;app-domain-name&amp;gt;/current/public&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Root directory for static files&lt;/span&gt;
  access_log /var/www/&amp;lt;app-domain-name&amp;gt;/shared/log/nginx.access.log&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Access log path&lt;/span&gt;
  error_log /var/www/&amp;lt;app-domain-name&amp;gt;/shared/log/nginx.error.log info&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Error log path and verbosity&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$document_root&lt;/span&gt;/maintenance.html&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    rewrite  ^&lt;span class="o"&gt;(&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt; /maintenance.html last&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Rewrite to maintenance page if it exists&lt;/span&gt;
  &lt;span class="c"&gt;#  break;  # Stop further processing&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

   location ~ ^/rails/active_storage &lt;span class="o"&gt;{&lt;/span&gt;
    proxy_set_header X-Forwarded-For &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header Host &lt;span class="nv"&gt;$http_host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Forwarded-Proto &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Real-IP &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_pass http://&amp;lt;app-name&amp;gt;-puma&lt;span class="p"&gt;;&lt;/span&gt;

    proxy_buffering off&lt;span class="p"&gt;;&lt;/span&gt;

    expires max&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  location / &lt;span class="o"&gt;{&lt;/span&gt;
    proxy_set_header X-Forwarded-For &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Set X-Forwarded-For header for proxy&lt;/span&gt;
    proxy_set_header Host &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Set Host header for proxy&lt;/span&gt;
    proxy_set_header Upgrade &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header Connection &lt;span class="s2"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Forwarded-Proto https&lt;span class="p"&gt;;&lt;/span&gt;
     proxy_set_header  X-Forwarded-Port &lt;span class="nv"&gt;$server_port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header  X-Forwarded-Host &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_redirect off&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$request_filename&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Serve directly if file exists&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$request_filename&lt;/span&gt;/index.html&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      rewrite &lt;span class="o"&gt;(&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;/index.html &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Rewrite to index.html if it exists&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$request_filename&lt;/span&gt;.html&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      rewrite &lt;span class="o"&gt;(&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;.html &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Rewrite to .html if it exists&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    proxy_pass http://&amp;lt;app-name&amp;gt;-puma&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Proxy pass to upstream if no static file found&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  location ~&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;ico|css|gif|jpe?g|png|js&lt;span class="o"&gt;)(&lt;/span&gt;&lt;span class="se"&gt;\?&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0-9]+&lt;span class="o"&gt;)&lt;/span&gt;?&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    expires max&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Set max expiration for static files&lt;/span&gt;
    gzip_static on&lt;span class="p"&gt;;&lt;/span&gt;
    add_header Cache-Control public&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

location /cable &lt;span class="o"&gt;{&lt;/span&gt;
    proxy_pass http://&amp;lt;app-name&amp;gt;-puma&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_http_version 1.1&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header Upgrade &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header Connection &lt;span class="s2"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Real-IP &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Forwarded-For &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header Host &lt;span class="nv"&gt;$http_host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Forwarded-Proto &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Forwarded-Ssl on&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# Optional&lt;/span&gt;
    proxy_set_header X-Forwarded-Port &lt;span class="nv"&gt;$server_port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_set_header X-Forwarded-Host &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    proxy_redirect off&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# Error page configuration&lt;/span&gt;
  location &lt;span class="o"&gt;=&lt;/span&gt; /500.html &lt;span class="o"&gt;{&lt;/span&gt;
    root /var/www/&amp;lt;app-domain-name&amp;gt;/current/public&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;# Serve 500.html from this directory&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
  keepalive_timeout 10&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;wherever you stored this file, make a symbolic link to &lt;code&gt;/etc/nginx/sites-enabled/&amp;lt;app-doman&amp;gt;.conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Login with your user account with sudo privileges for this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /path/to/nginx-config-file /etc/nginx/sites-enabled/your-app-domain.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check for errors with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it says okay, restart nginx, visit your domain name in the browser and your app should be live.&lt;/p&gt;

&lt;p&gt;During all these commands if you run into permissions errors change ownership of the &lt;code&gt;/var/www/&lt;/code&gt; to deploy and www-data group&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; deploy:www-data /var/www
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will give our deploy user ownership of that directory as well as the webserver if it uses that directory for some other reason&lt;/p&gt;

&lt;p&gt;Now let’s add ssl with let’s encrypt&lt;/p&gt;

&lt;p&gt;Follow the steps on this link to add ssl support &lt;a href="https://certbot.eff.org/instructions?ws=nginx&amp;amp;os=snap" rel="noopener noreferrer"&gt;https://certbot.eff.org/instructions?ws=nginx&amp;amp;os=snap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s it you’ve successfully deployed rails with nginx + puma + mina. The downside is that right now we have to download &lt;strong&gt;mulitiple apps 1 server&lt;/strong&gt;manually from our local machine, I tried to find a github action for mina but I couldn’t. One that I found was using ruby 2 and didn’t work quite as I intended. I could create one myself but skill issues, I don’t know docker and don’t quite understand github actions syntax.&lt;/p&gt;

&lt;p&gt;I understand that this was quite long to just deploy an app but you only have to do this once and never again, you can reuse the &lt;code&gt;deploy.rb&lt;/code&gt; file as it is, the &lt;code&gt;.service&lt;/code&gt; files as they are, the nginx config as it is for the next project&lt;/p&gt;

&lt;h2&gt;
  
  
  Going forward, kamal? docker?
&lt;/h2&gt;

&lt;p&gt;I will keep adding updates to this blog post but for now this setup meets my needs. Kamal 2 is out at the time of writing this and everyone is singing praises on how good it is to setup servers and it is. But, I don’t think it’s for everyone, from what I understand [at the time of writing this] the docs say kamal is good to run the same app on multiple servers, like instances of the same on multiple servers but what I want the vice versa of this, I know this is skill issues on my part I don’t know docker but I don’t have time to understand the ins and outs of docker right now but I do know &lt;code&gt;ruby&lt;/code&gt; &lt;code&gt;shell&lt;/code&gt; &lt;code&gt;nginx&lt;/code&gt; and with this I can deploy to the server with semi minimal effort.&lt;/p&gt;

&lt;p&gt;But this is just my way of doing things, follow me on Hashnode for more articles like this and let me know your thoughts in the comments below&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>devops</category>
      <category>nginx</category>
    </item>
    <item>
      <title>Securing Rails application with Action Policy</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Sat, 21 Sep 2024 08:36:05 +0000</pubDate>
      <link>https://dev.to/slimgee/securing-rails-application-with-action-policy-586</link>
      <guid>https://dev.to/slimgee/securing-rails-application-with-action-policy-586</guid>
      <description>&lt;p&gt;&lt;strong&gt;There is lot of yapping going on in this article if you just want to see the implementation you can jump to the Setup section&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recently, when I was building &lt;a href="https://pulse.welodge.co.zw" rel="noopener noreferrer"&gt;Pulse&lt;/a&gt;, I wanted an admin dashboard of sorts, I wanted to be able to manually create startups other users can then claim later, I also wanted to see a list of registered users, some basic stats, etc.&lt;/p&gt;

&lt;p&gt;The problem now, I didn’t want every Joe and Jill to access the admin dashboard and do whatever they want simply because they registered an account.&lt;/p&gt;

&lt;p&gt;To solve this, I did a deep dive, found solutions like &lt;code&gt;cancan&lt;/code&gt; and it’s derivatives, and a bunch of other gems. However I wanted a setup that was a bit automatic that I could setup once and subsequently use and work out of the box without me writing extra code.&lt;/p&gt;

&lt;p&gt;I guess you’re wondering, Gavin, why did you roll out your own dashboard when there are dashboard gems out there? Well, I tried, but I found that the amount of customization I’ll have to make requires me to write more code than just generating a scaffold_controller in the &lt;code&gt;admin&lt;/code&gt; namespace.&lt;/p&gt;

&lt;p&gt;Okay back to adding authorization, here is what I was looking to achieve&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Users need to have roles,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Roles have permissions (still working on this)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authorize controllers via Policy&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found 2 really good gems for this, &lt;a href="https://actionpolicy.evilmartians.io/" rel="noopener noreferrer"&gt;ActionPolicy&lt;/a&gt; and &lt;a href="https://github.com/enjaku4/rabarber" rel="noopener noreferrer"&gt;Rabarber&lt;/a&gt;. ActionPolicy allowed me to write policies, and in those policies I will then decide if the user has a certain role before they can perform a given action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;To set this let’s start by installing Rabarber to add roles to the users&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add rabarber
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate the migrations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails g rabarber:roles &lt;span class="nb"&gt;users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migrate the database&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now include the roles module to the user model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Rabarber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HasRoles&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let’s assign some roles to our users, I was lazy to build a UI for this so we’ll just use the rails console, I’m going to be the only admin for a while anyway and I will only do this once in production so no big deal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign_roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:super_admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it, this all we have to do to get this running. Now, let’s add action policy to add authorization to the controllers&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add action_policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install action policy with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate action_policy:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s also generate a policy for the startup model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails g action_policy:policy admin/startup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Authorization with policies
&lt;/h2&gt;

&lt;p&gt;My project structure looks like this, in the root controller directory I have unauthenticated controllers for public access which inherit from the default &lt;code&gt;application_controller.rb&lt;/code&gt; I have controller in &lt;code&gt;controllers/app&lt;/code&gt; namespace that inherit from &lt;code&gt;app/application_controller.rb&lt;/code&gt;, finally &lt;code&gt;controllers/admin&lt;/code&gt; with controllers that inherit from &lt;code&gt;admin/application_controller.rb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the app namespace I just call &lt;code&gt;before_action :authenticate_user!&lt;/code&gt; in the application controller and I don’t have to do it ever again in inheriting controllers. Same with the &lt;code&gt;admin&lt;/code&gt; namespace&lt;/p&gt;

&lt;p&gt;From action policy docs, we have to &lt;code&gt;authorize!&lt;/code&gt; on every controller action, which I was trying to get away from.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;application_policy.rb&lt;/code&gt; I added this, to give basic access to anyone with role &lt;code&gt;admin&lt;/code&gt; then in the specific policies I will then give access based that role and another role. For example if you’re admin you can access the dashboard, but you have to be admin + moderator to update startups&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/policies/application_policy&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationPolicy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionPolicy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;default_rule&lt;/span&gt; &lt;span class="ss"&gt;:manage?&lt;/span&gt;
  &lt;span class="n"&gt;alias_rule&lt;/span&gt; &lt;span class="ss"&gt;:index?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:new?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :manage?&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;manage?&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_role?&lt;/span&gt; &lt;span class="ss"&gt;:admin&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;owner?&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, if i don’t have any complex authorizations, I could just generate a policy that inherits application_policy and everything would work out of the box without adding extra code.&lt;/p&gt;

&lt;p&gt;Okay, this alone will not work, we need to tell the &lt;code&gt;admin/application_controller&lt;/code&gt; to automatically authorize all controllers based on the controller name, find a policy for that controller and use it to authorize the current controller. As long as we follow rails conventions this should work out of the box&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/admin/application_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Admin::ApplicationController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:authenticate_user!&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:authorize!&lt;/span&gt;
  &lt;span class="n"&gt;verify_authorized&lt;/span&gt;
  &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="s2"&gt;"admin/application"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;implicit_authorization_target&lt;/span&gt;
    &lt;span class="c1"&gt;# If you don't pass the target, it will be guessed&lt;/span&gt;
    &lt;span class="c1"&gt;# based on the controller name.&lt;/span&gt;
    &lt;span class="c1"&gt;# See https://actionpolicy.evilmartians.io/#/implicit_target&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;controller_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authorization_strict_namespace&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we authenticate the user, this is standard. Next, I added a before action to call &lt;code&gt;:authorize&lt;/code&gt; so that I don’t to do this for every other action in the controllers. The docs say&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also call &lt;code&gt;authorize!&lt;/code&gt; without a resource specified. In that case, Action Policy tries to infer the resource class from the controller name&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then finally we added the &lt;code&gt;implicit_authorization_target&lt;/code&gt; that first calls the parent method and if that returns nil we then use the current class name to find the policy.&lt;/p&gt;

&lt;p&gt;Finally we enable strict namespaces. This allows us to have scoped policies, for example, policies in &lt;code&gt;app/policies/admin/*&lt;/code&gt; will only authorize controllers in &lt;code&gt;app/controllers/admin/*&lt;/code&gt; and so forth,&lt;/p&gt;

&lt;p&gt;We can have other policies for non admin authenticated actions, perhaps we want an authenticated user to only be able to create startups only if they have certain roles and we don’t want that policy to affect the admin policy.&lt;/p&gt;

&lt;p&gt;That’s it we are good to go, at this point authorization now works out of the box! The next thing would be to add custom pages for 401 errors.&lt;/p&gt;

&lt;p&gt;Let me know what you think about this pattern.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>Implementing Event-Driven Architecture in Rails with Active Support Instrumentation</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Tue, 17 Sep 2024 20:21:55 +0000</pubDate>
      <link>https://dev.to/slimgee/implementing-event-driven-architecture-in-rails-with-active-support-instrumentation-d21</link>
      <guid>https://dev.to/slimgee/implementing-event-driven-architecture-in-rails-with-active-support-instrumentation-d21</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL:DR; You can skip to setup if you just to see the implementation&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;When I was building &lt;a href="https://pulse.weldoge.co.zw" rel="noopener noreferrer"&gt;Pulse by Welodge&lt;/a&gt; I wanted to notify a user when they submit a startup for approval, when it’s accepted/rejected. I also wanted to notify the admins that someone has submitted a startup. The first implementation I simply dispatched a noticed Notifier in the controller when startup was submitted, but this did not have some “rails magic” into it.&lt;/p&gt;

&lt;p&gt;After a quick search I found a few articles online about event driven architectures in rails but they all seem to be overly complicated for what I wanted and they seem to rely on 3rd party packages, which is fine but eventmachine wanted to run a separate “event server” or something like which was overkill,&lt;/p&gt;

&lt;p&gt;After a deep dive, I landed on &lt;a href="https://guides.rubyonrails.org/active_support_instrumentation.html" rel="noopener noreferrer"&gt;Active Support Instrumentation&lt;/a&gt; which rails’s own implementation of the observer pattern. Cool, I can now publish an event and have many subscribers which do different things. What the docs don’t say is how to get it to work in userland and that’s what this article is about&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;To get this working properly we need to setup a few things, first we need to auto load “subscribers” which I shall call listeners from here on. We want to store listeners in &lt;code&gt;app/listeners/xx_listener.rb&lt;/code&gt; where &lt;code&gt;xx&lt;/code&gt; is a resource/model in our application. To achieve this let’s hook into the &lt;code&gt;to_prepare&lt;/code&gt; config hook to load the event listeners during application boot&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Startuplist&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
    &lt;span class="c1"&gt;# rest of your app config&lt;/span&gt;
    &lt;span class="n"&gt;listeners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/app/listeners"&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;autoloaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;listeners&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/**/*_listener.rb"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nb"&gt;load&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now all we have to do is define a listener file in &lt;code&gt;app/listeners/&lt;/code&gt; in this case &lt;code&gt;app/listeners/startup_listener.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt; &lt;span class="s2"&gt;"app.startup.submitted"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;startup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:startup&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="no"&gt;StartupSubmittedNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;record: &lt;/span&gt;&lt;span class="n"&gt;startup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"Your startup was submitted"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;Rabarber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assignees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;SubmissionReceivedNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;record: &lt;/span&gt;&lt;span class="n"&gt;startup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"A new startup was submitted"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt; &lt;span class="s2"&gt;"app.startup.accepted"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;startup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:startup&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="no"&gt;StartupAcceptedNotifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;record: &lt;/span&gt;&lt;span class="n"&gt;startup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"Your startup was accepted"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are simply subscribing to all events related to the &lt;code&gt;startup&lt;/code&gt; model, the &lt;code&gt;user_listener.rb&lt;/code&gt; could look something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt; &lt;span class="s2"&gt;"app.user.created"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;SubscribeNewsletterJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which subscribes a newly registered user to a newsletter like ConverKit, MailChimp or Mailerlite&lt;/p&gt;

&lt;p&gt;Okay this is cool, how do we then dispatch these events, right, on &lt;a href="https://pulse.welodge.co.zw" rel="noopener noreferrer"&gt;Pulse&lt;/a&gt; the Startup has a &lt;code&gt;status&lt;/code&gt; enum so I did something like this to dispatch events each time a status changes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;after_save&lt;/span&gt; &lt;span class="ss"&gt;:"broadcast_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;_changed"&lt;/span&gt;

  &lt;span class="n"&gt;define_method&lt;/span&gt; &lt;span class="ss"&gt;:"broadcast_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="ss"&gt;_changed"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;saved_change_to_status?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;
      &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt; &lt;span class="s2"&gt;"app.startup.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;startup: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made sure that each time the status changed via a controller or anywhere else in the code, I publish a &lt;code&gt;app.startup.status&lt;/code&gt; event which listeners could subscribe to and do what ever they want with the data,&lt;/p&gt;

&lt;p&gt;You can broadcast these events anywhere in your app for example we can also do something like for the user model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="ss"&gt;:broadcast_create&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;broadcast_create&lt;/span&gt;
  &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt; &lt;span class="s2"&gt;"app.user.created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On this event we can then send welcome email, subscribe to newsletter, provision a tenant, etc.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this pattern, let me know what you think about this.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What you working on?</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Sun, 15 Sep 2024 20:07:26 +0000</pubDate>
      <link>https://dev.to/slimgee/what-you-working-on-ne9</link>
      <guid>https://dev.to/slimgee/what-you-working-on-ne9</guid>
      <description>&lt;p&gt;Are you working on a saas tool? the next todo app? let's hear it below&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Securing Rails Active Storage Direct Uploads</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Sat, 14 Sep 2024 18:23:21 +0000</pubDate>
      <link>https://dev.to/slimgee/securing-rails-active-storage-direct-uploads-55fm</link>
      <guid>https://dev.to/slimgee/securing-rails-active-storage-direct-uploads-55fm</guid>
      <description>&lt;p&gt;TL;DR; if you just want the code, head over to the bottom of this article&lt;/p&gt;

&lt;p&gt;I noticed something odd with Active Storage direct uploads. Did you know there is literally no authentication at all by default! The idea scared me.&lt;/p&gt;

&lt;p&gt;From the docs, it doesn’t say how to secure that endpoint either. There’s a way to use token auth for API apps but for traditional apps that’s still pretty much open season.&lt;/p&gt;

&lt;p&gt;Let’s look at how that can be problematic, First we have CSRF protection so no one can just fire up curl and start uploading files from a script, however that can be easily solved by first making a legit request, grabbing the CSRF token and voila! We also have security by obfuscation assuming that a malicious actor doesn’t know that active storage uploads exists but I’m sure they can read the same documentation that you and I know read.&lt;/p&gt;

&lt;p&gt;Someone can just spam upload large files and offset your S3 bill or simply run you out of storage, or DDOS your server all of which are not desirable scenarios.&lt;/p&gt;

&lt;p&gt;To solve, this I found some GitHub issues that discussed this but no one provided a solution for what I was worried about, I saw an article from this guy but his solution suggested extending the &lt;code&gt;ActiveStorge::DirectUploadsController&lt;/code&gt; and creating a new route. This doesn’t really solve the problem because the other active storage direct uploads route will still be unprotected, I took his solution and used a much simpler implementation using an initializer.&lt;/p&gt;

&lt;p&gt;Basically, we want&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;authenticate the direct uploads endpoint with a before_action&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rate limit the endpoint to prevent DDOS&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/active_storage.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ActiveStorage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DirectUploadsController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:authenticate_user!&lt;/span&gt;
    &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;by: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails has a configuration hook, at least that’s what I think that is, that allows you to run code before the rails app boots I imagine. The cool thing is, we can do a &lt;code&gt;class_eval&lt;/code&gt; on the &lt;code&gt;ActiveStorage::DirectUploadsController&lt;/code&gt; to add a before_action to authenticate the user then rate limit by user id so that one user doesn’t just spam upload files to the server.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Ultimate Guide To Content Marketing For Businesses: Strategies For Growth And ROI</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Sat, 31 Aug 2024 17:08:43 +0000</pubDate>
      <link>https://dev.to/slimgee/the-ultimate-guide-to-content-marketing-for-businesses-strategies-for-growth-and-roi-4o7e</link>
      <guid>https://dev.to/slimgee/the-ultimate-guide-to-content-marketing-for-businesses-strategies-for-growth-and-roi-4o7e</guid>
      <description>&lt;p&gt;Content marketing stands as a cornerstone of modern business strategy. It's not just about creating content; it's about crafting a narrative that resonates with your audience and drives business growth.&lt;/p&gt;

&lt;p&gt;Consider these statistics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Content marketing costs 62% less than traditional marketing and generates about 3 times as many leads. (&lt;a href="https://www.demandmetric.com/content/content-marketing-infographic" rel="noopener noreferrer"&gt;DemandMetric&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;  29% of marketers actively use content marketing (&lt;a href="https://www.hubspot.com/marketing-statistics" rel="noopener noreferrer"&gt;HubSpot&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;  Companies that blog produce 67% more leads per month than those that don't. (&lt;a href="https://www.demandmetric.com/content/content-marketing-infographic" rel="noopener noreferrer"&gt;DemandMetric&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide will equip you with strategies to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Develop a content marketing framework aligned with business goals&lt;/li&gt;
&lt;li&gt; Create high-impact content that drives engagement and conversions&lt;/li&gt;
&lt;li&gt; Implement effective distribution strategies&lt;/li&gt;
&lt;li&gt; Measure and optimize your content marketing ROI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dive into the mechanics of content marketing that drives real business results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Your Target Audience: The Foundation of Effective Content
&lt;/h2&gt;

&lt;p&gt;Knowing your audience is more than demographics, that's the bare minimum everyone does. It's about understanding their psychographics, behaviors, and decision-making processes. It's about getting into their shoes and having an intimate relationship with them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developing Comprehensive Buyer Personas
&lt;/h3&gt;

&lt;p&gt;Create detailed buyer personas using:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Customer interviews&lt;/li&gt;
&lt;li&gt; Sales team insights&lt;/li&gt;
&lt;li&gt; CRM data analysis&lt;/li&gt;
&lt;li&gt; Social media listening&lt;/li&gt;
&lt;li&gt; Competitor analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example Buyer Persona:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Name: Marketing Director Mike&lt;/li&gt;
&lt;li&gt;  Age: 35-45&lt;/li&gt;
&lt;li&gt;  Company: Mid-size B2B firm ($10-50M annual revenue)&lt;/li&gt;
&lt;li&gt;  Challenges: Proving marketing ROI, aligning with sales team&lt;/li&gt;
&lt;li&gt;  Goals: Increase qualified leads by 20%, reduce customer acquisition cost&lt;/li&gt;
&lt;li&gt;  Information sources: Industry reports, LinkedIn groups, marketing podcasts&lt;/li&gt;
&lt;li&gt;  Decision-making factors: Proven ROI, ease of implementation, scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mapping the Customer Journey
&lt;/h3&gt;

&lt;p&gt;Align your content with each stage of the buyer's journey:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Awareness: Educational blog posts, infographics&lt;/li&gt;
&lt;li&gt; Consideration: Case studies, webinars&lt;/li&gt;
&lt;li&gt; Decision: Product comparisons, ROI calculators&lt;/li&gt;
&lt;li&gt; Retention: How-to guides, customer spotlights&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Follow this &lt;a href="https://flixtechs.co.zw/posts/the-complete-guide-to-identify-and-understand-your-target-audience" rel="noopener noreferrer"&gt;in-depth guide on identifying and understanding your target audience&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Developing a Content Marketing Strategy: From Goals to Execution
&lt;/h2&gt;

&lt;p&gt;A robust strategy turns content into a business asset. Here's how to build one:&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting SMART Goals
&lt;/h3&gt;

&lt;p&gt;Align content goals with business objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Increase organic traffic by 50% in 6 months&lt;/li&gt;
&lt;li&gt;  Generate 100 marketing qualified leads per month through gated content&lt;/li&gt;
&lt;li&gt;  Improve customer retention rate by 10% through educational content&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Content Audit and Gap Analysis
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Inventory existing content&lt;/li&gt;
&lt;li&gt; Analyze performance (traffic, engagement, conversions)&lt;/li&gt;
&lt;li&gt; Identify gaps in the content funnel&lt;/li&gt;
&lt;li&gt; Prioritize content creation based on potential impact&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Resource Allocation
&lt;/h3&gt;

&lt;p&gt;Determine your content marketing budget:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Industry average: 25-30% of total marketing budget&lt;/li&gt;
&lt;li&gt;  Allocate resources across creation, distribution, and analysis&lt;/li&gt;
&lt;li&gt;  Consider outsourcing vs. in-house creation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Editorial Calendar and Workflow
&lt;/h3&gt;

&lt;p&gt;Create a structured approach to content production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Topic ideation (use tools like BuzzSumo, Google Trends)&lt;/li&gt;
&lt;li&gt; Content brief creation&lt;/li&gt;
&lt;li&gt; Production schedule&lt;/li&gt;
&lt;li&gt; Review and approval process&lt;/li&gt;
&lt;li&gt; Publication and promotion workflow&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Creating High-Impact Content: Quality Over Quantity
&lt;/h2&gt;

&lt;p&gt;Focus on creating content that drives results. Here's how:&lt;/p&gt;

&lt;h3&gt;
  
  
  Data-Driven Topic Selection
&lt;/h3&gt;

&lt;p&gt;Use tools like SEMrush or Ahrefs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Identify high-volume, low-competition keywords&lt;/li&gt;
&lt;li&gt; Analyze competitor content gaps&lt;/li&gt;
&lt;li&gt; Discover trending topics in your industry&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Content Formats for Maximum Engagement
&lt;/h3&gt;

&lt;p&gt;Diversify your content mix:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Long-form articles (2000+ words) for SEO and thought leadership&lt;/li&gt;
&lt;li&gt; Video content (54% of consumers want to see more video content from brands they support - HubSpot)&lt;/li&gt;
&lt;li&gt; Interactive content (quizzes, calculators) for higher engagement&lt;/li&gt;
&lt;li&gt; Podcasts for building intimate connections with your audience&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating 10x Content
&lt;/h3&gt;

&lt;p&gt;Develop content that's 10 times better than the top-ranking result:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Provide unique insights or data&lt;/li&gt;
&lt;li&gt; Offer actionable takeaways&lt;/li&gt;
&lt;li&gt; Include expert quotes or interviews&lt;/li&gt;
&lt;li&gt; Use high-quality visuals (custom graphics, videos)&lt;/li&gt;
&lt;li&gt; Ensure comprehensive coverage of the topic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example: Instead of a basic "Guide to Email Marketing," create "The Ultimate Email Marketing Playbook: Strategies from 50 Top CMOs, with Templates and Case Studies"&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Distribution: Amplifying Your Reach
&lt;/h2&gt;

&lt;p&gt;Creating great content is only half the battle. Distribution is key:&lt;/p&gt;

&lt;h3&gt;
  
  
  Owned Media Strategy
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Website optimization: Improve site speed, implement schema markup&lt;/li&gt;
&lt;li&gt; Email marketing: Segment your list for targeted content distribution&lt;/li&gt;
&lt;li&gt; Employee advocacy: Encourage employees to share content (can increase reach by 561% - MSLGroup)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Earned Media Tactics
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Influencer partnerships: Collaborate with industry thought leaders&lt;/li&gt;
&lt;li&gt; PR outreach: Secure guest posting opportunities on high-authority sites&lt;/li&gt;
&lt;li&gt; HARO (Help a Reporter Out): Provide expert quotes for media coverage&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Paid Media Approaches
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Social media advertising: Use retargeting for content promotion&lt;/li&gt;
&lt;li&gt; Native advertising: Platforms like Outbrain or Taboola for content discovery&lt;/li&gt;
&lt;li&gt; PPC for content: Use Google Ads to promote high-value content pieces&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Measuring Content Marketing Success: Metrics that Matter
&lt;/h2&gt;

&lt;p&gt;Focus on metrics that tie directly to business goals:&lt;/p&gt;

&lt;h3&gt;
  
  
  Traffic and Engagement Metrics
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Organic traffic growth&lt;/li&gt;
&lt;li&gt; Time on page&lt;/li&gt;
&lt;li&gt; Bounce rate&lt;/li&gt;
&lt;li&gt; Social shares and comments&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Lead Generation Metrics
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Conversion rate (visitors to leads)&lt;/li&gt;
&lt;li&gt; Lead quality score&lt;/li&gt;
&lt;li&gt; Cost per lead&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Revenue Impact Metrics
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Marketing qualified leads (MQLs) to sales qualified leads (SQLs) ratio&lt;/li&gt;
&lt;li&gt; Content-assisted conversions&lt;/li&gt;
&lt;li&gt; Customer lifetime value (CLV) of content-acquired customers&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ROI Calculation
&lt;/h3&gt;

&lt;p&gt;Use this formula:\&lt;br&gt;
(Gain from Investment - Cost of Investment) / Cost of Investment&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Content marketing cost: $20,000&lt;/li&gt;
&lt;li&gt;  Revenue generated: $100,000&lt;/li&gt;
&lt;li&gt;  ROI = ($100,000 - $20,000) / $20,000 = 400%&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced Content Marketing Strategies
&lt;/h2&gt;

&lt;p&gt;Stay ahead of the curve with these advanced tactics:&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-Powered Content Creation
&lt;/h3&gt;

&lt;p&gt;Use AI tools for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Content ideation (e.g., GPT-4 powered brainstorming)&lt;/li&gt;
&lt;li&gt; SEO optimization (e.g., Clearscope, MarketMuse)&lt;/li&gt;
&lt;li&gt; Personalized content recommendations&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Account-Based Marketing (ABM) Content
&lt;/h3&gt;

&lt;p&gt;Create personalized content for high-value accounts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Industry-specific whitepapers&lt;/li&gt;
&lt;li&gt; Custom ROI calculators&lt;/li&gt;
&lt;li&gt; Personalized video messages&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  User-Generated Content (UGC) Campaigns
&lt;/h3&gt;

&lt;p&gt;Leverage your customers' voices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Customer success story contests&lt;/li&gt;
&lt;li&gt; Social media hashtag campaigns&lt;/li&gt;
&lt;li&gt; Customer-led webinars or podcast episodes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion: Your Path to Content Marketing Excellence
&lt;/h2&gt;

&lt;p&gt;Content marketing is not a one-size-fits-all solution. It requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A deep understanding of your audience&lt;/li&gt;
&lt;li&gt; A strategic approach aligned with business goals&lt;/li&gt;
&lt;li&gt; Commitment to creating high-quality, valuable content&lt;/li&gt;
&lt;li&gt; Effective distribution across multiple channels&lt;/li&gt;
&lt;li&gt; Continuous measurement and optimization&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start by auditing your current efforts. Identify gaps in your strategy. Implement one new tactic at a time. Measure results and iterate.&lt;/p&gt;

&lt;p&gt;Remember, content marketing is a long-term strategy. Consistency and patience are key. With the right approach, content marketing can become your most powerful tool for sustainable business growth.&lt;/p&gt;

&lt;p&gt;Your next step? Choose one strategy from this guide. Implement it this quarter. Track the results. Use those insights to refine your overall content marketing approach.&lt;/p&gt;

&lt;p&gt;The digital landscape will continue to evolve. But the core principle remains: provide value, build relationships, drive growth. Your content is your voice in the digital world. Make it count.&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How To Pick A Domain Name That Doesn't Suck</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Fri, 30 Aug 2024 15:37:29 +0000</pubDate>
      <link>https://dev.to/slimgee/how-to-pick-a-domain-name-that-doesnt-suck-2d9f</link>
      <guid>https://dev.to/slimgee/how-to-pick-a-domain-name-that-doesnt-suck-2d9f</guid>
      <description>&lt;p&gt;Imagine you're building a house. The domain name is like picking the perfect spot for it. It's that important. &lt;br&gt;
This guide will help you choose a domain name that puts your website on the map.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's a Domain Name?
&lt;/h2&gt;

&lt;p&gt;A domain name is your address on the internet. It's what people type to find your website. For example, in "www.coolshoes.com", "coolshoes" is the domain name. The ".com" part? That's called the Top-Level Domain (TLD).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Your Domain Name Matters
&lt;/h2&gt;

&lt;p&gt;Your domain name is a big deal. It's often the first thing people see. A good one makes people remember you. A bad one? They might forget you exist.&lt;/p&gt;

&lt;p&gt;Think about it. You're at a party. Someone asks for your website. Which sounds better: "&lt;a href="http://www.best-shoe-store-in-town.net" rel="noopener noreferrer"&gt;www.best-shoe-store-in-town.net&lt;/a&gt;" or "&lt;a href="http://www.kickskingdom.com" rel="noopener noreferrer"&gt;www.kickskingdom.com&lt;/a&gt;"?&lt;/p&gt;

&lt;p&gt;A smart domain name does more than sound cool. It helps search engines find you. It builds your brand. It's the foundation of your online home.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose a Winning Domain Name
&lt;/h2&gt;

&lt;p&gt;Let's break it down. Here's how to pick a domain name that works:&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep It Short and Sweet
&lt;/h3&gt;

&lt;p&gt;Shorter is better. Aim for 6-14 letters. Why? It's easier to remember. Easier to type. Harder to mess up.&lt;/p&gt;

&lt;p&gt;Think about big brands. Google. Amazon. Apple. Short names stick.&lt;/p&gt;

&lt;p&gt;You might not get a single word. Most are taken. But you can still make it snappy.&lt;/p&gt;

&lt;p&gt;Avoid complex words. If you have to spell it out every time, it's too hard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Keywords Wisely
&lt;/h3&gt;

&lt;p&gt;Keywords can help. They tell people what you do. But don't go crazy. Too many keywords look spammy.&lt;/p&gt;

&lt;p&gt;If you can fit a keyword naturally, do it. Running a fitness blog? "FitFocus.com" could work. It has a keyword, but it's not stuffed.&lt;/p&gt;

&lt;p&gt;Put important words at the start. Search engines like that.&lt;/p&gt;

&lt;p&gt;Remember: A creative name beats a keyword-stuffed one any day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make It Unique and Brandable
&lt;/h3&gt;

&lt;p&gt;Stand out. Be different. Create a name that's all you.&lt;/p&gt;

&lt;p&gt;Mix words. Make new ones. "Pinterest" combines "pin" and "interest". Clever, right?&lt;/p&gt;

&lt;p&gt;Think about Zillow, Skype, or Etsy. These names are odd. But they work. They stick in your head.&lt;/p&gt;

&lt;p&gt;Don't be afraid to get weird. Sometimes, the strangest names become the biggest brands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pick the Right Domain Extension
&lt;/h3&gt;

&lt;p&gt;".com" is king. But it's not the only choice. Here's a quick guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  .com: Best for most businesses.&lt;/li&gt;
&lt;li&gt;  .org: Good for non-profits.&lt;/li&gt;
&lt;li&gt;  .net: Works for tech companies.&lt;/li&gt;
&lt;li&gt;  .co: Popular with startups.&lt;/li&gt;
&lt;li&gt;  .io: Another tech favorite.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Country codes like .co.zw or .uk work if you're targeting specific places.&lt;/p&gt;

&lt;p&gt;If you can get .com, grab it. If not, don't stress. Pick what fits your brand best.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Hyphens, No Numbers
&lt;/h3&gt;

&lt;p&gt;Keep it clean. Hyphens and numbers confuse people. They're hard to remember. Hard to share.&lt;/p&gt;

&lt;p&gt;Hyphens can make you look cheap. Numbers create doubt. Is it "2" or "two"?&lt;/p&gt;

&lt;p&gt;Stick to letters. Your future self will thank you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make It Easy to Say and Spell
&lt;/h3&gt;

&lt;p&gt;Imagine telling someone your domain name in a loud bar. Would they get it? If not, rethink it.&lt;/p&gt;

&lt;p&gt;Easy to say means easy to share. Easy to spell means fewer lost visitors.&lt;/p&gt;

&lt;p&gt;Test it. Say it out loud. Ask a friend to spell it. If they struggle, it's too hard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check Social Media and Trademarks
&lt;/h3&gt;

&lt;p&gt;Before you commit, do some digging. Is the name free on social media? You want the same name everywhere. It makes you easy to find.&lt;/p&gt;

&lt;p&gt;Check for trademarks too. The last thing you need is a lawsuit. Consider talking to a lawyer for full protection.&lt;/p&gt;

&lt;p&gt;Locking down your name everywhere saves headaches later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Look Into Its Past
&lt;/h3&gt;

&lt;p&gt;Domain names have history. Sometimes it's good. Sometimes it's bad.&lt;/p&gt;

&lt;p&gt;Use the Wayback Machine. See how the domain was used before. Was it spammy? That could hurt you.&lt;/p&gt;

&lt;p&gt;Check its backlinks too. Good links are a bonus. Bad ones are trouble.&lt;/p&gt;

&lt;p&gt;Knowing the domain's past protects your future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Think Long-Term
&lt;/h3&gt;

&lt;p&gt;Your domain should grow with you. Don't box yourself in.&lt;/p&gt;

&lt;p&gt;"SeattleBagelShop.com" is great if you're staying small. But what if you expand? What if you start selling more than bagels?&lt;/p&gt;

&lt;p&gt;Avoid trends too. What's hot now might be lame in a year.&lt;/p&gt;

&lt;p&gt;Pick a name you can stick with. Changing later is a pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protect Your Domain
&lt;/h3&gt;

&lt;p&gt;Found the perfect name? Don't stop there. Buy similar ones too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Common misspellings&lt;/li&gt;
&lt;li&gt;  Different endings (.net, .org)&lt;/li&gt;
&lt;li&gt;  Hyphenated versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It costs a bit more. But it protects your brand. It stops others from stealing your traffic.&lt;/p&gt;

&lt;p&gt;You don't need websites for all of them. Just point them to your main site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;Watch out for these traps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Copying others: Be original. Avoid legal trouble.&lt;/li&gt;
&lt;li&gt; Ignoring your audience: Pick a name they'll like.&lt;/li&gt;
&lt;li&gt; Being too clever: Clear beats clever every time.&lt;/li&gt;
&lt;li&gt; Forgetting to check meanings: Your cool name might mean something bad in another language.&lt;/li&gt;
&lt;li&gt; Obsessing over SEO: Don't stuff it with keywords.&lt;/li&gt;
&lt;li&gt; Rushing: Take your time. This decision matters.&lt;/li&gt;
&lt;li&gt; Not getting feedback: Ask others what they think.&lt;/li&gt;
&lt;li&gt; Picking a limiting name: Leave room to grow.&lt;/li&gt;
&lt;li&gt; Forgetting mobile users: Long names are hard to type on phones.&lt;/li&gt;
&lt;li&gt;Letting it expire: Set up auto-renewal. Don't lose your domain by accident.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avoid these, and you're on the right track.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools to Help You Choose
&lt;/h2&gt;

&lt;p&gt;You don't have to do this alone. Use these tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Name generators: Try Namemesh or Panabee for ideas.&lt;/li&gt;
&lt;li&gt; Keyword tools: Google Keyword Planner helps find popular terms.&lt;/li&gt;
&lt;li&gt; Availability checkers: Namecheap or GoDaddy show what's free.&lt;/li&gt;
&lt;li&gt; Brand checkers: NameCheck helps secure your name across platforms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These tools make finding the perfect name easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Questions, Quick Answers
&lt;/h2&gt;

&lt;p&gt;Q: What's the best domain ending?&lt;br&gt;
A: .com if you can get it. But .co, .io, or .org work too. Pick what fits your brand.&lt;/p&gt;

&lt;p&gt;Q: How long should my domain be?&lt;br&gt;
A: Aim for 6-14 characters. Shorter is usually better.&lt;/p&gt;

&lt;p&gt;Q: Can I change my domain later?&lt;br&gt;
A: Yes, but it's a hassle. Pick a name you can stick with.&lt;/p&gt;

&lt;p&gt;Q: What if the name I want is taken?&lt;br&gt;
A: Get creative. Try variations. Or come up with something totally new.&lt;/p&gt;

&lt;p&gt;Q: How do I check if a domain is available?&lt;br&gt;
A: Use sites like GoDaddy or Namecheap. They'll show you what's free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap It Up
&lt;/h2&gt;

&lt;p&gt;Picking a domain name is a big step. It's the start of your online journey. Take your time. Be creative. Ask for opinions.&lt;/p&gt;

&lt;p&gt;Remember: A great domain name is easy to remember, fits your brand, and leaves room to grow.&lt;/p&gt;

&lt;p&gt;Now it's your turn. Start brainstorming. Use the tools we talked about. Find that perfect name.&lt;/p&gt;

&lt;p&gt;Your online future is waiting. Go claim it. Join our newsletter below to get more tips on setting up your business online&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>web</category>
      <category>design</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Rebuilding my site in Laravel</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Thu, 07 Dec 2023 23:16:07 +0000</pubDate>
      <link>https://dev.to/slimgee/rebuilding-my-site-in-laravel-2ege</link>
      <guid>https://dev.to/slimgee/rebuilding-my-site-in-laravel-2ege</guid>
      <description>&lt;p&gt;I just started a new youtube channel and here is my first video&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/jXi1qWGxDHo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>php</category>
      <category>laravel</category>
    </item>
    <item>
      <title>What are you currently working on?</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Sat, 26 Aug 2023 19:34:36 +0000</pubDate>
      <link>https://dev.to/slimgee/what-are-you-currently-working-on-26hc</link>
      <guid>https://dev.to/slimgee/what-are-you-currently-working-on-26hc</guid>
      <description>&lt;p&gt;Let's hear them cool side projects you're currently hacking&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Authenticate With Any OAuth Provider With Laravel Socialite</title>
      <dc:creator>Given Ncube</dc:creator>
      <pubDate>Fri, 25 Aug 2023 19:48:20 +0000</pubDate>
      <link>https://dev.to/slimgee/authenticate-with-any-oauth-provider-with-laravel-socialite-24jm</link>
      <guid>https://dev.to/slimgee/authenticate-with-any-oauth-provider-with-laravel-socialite-24jm</guid>
      <description>&lt;p&gt;Ever tried to create an account on a website and find out there's no option to "login with Google"? Annoying, right? You're not alone. Luckily for you I can help you prevent that and get more signups for your app&lt;/p&gt;

&lt;p&gt;In this post I will show you how to create a universal Socialite Controller that works for any socialite driver&lt;/p&gt;

&lt;p&gt;To get start, let's install socialite with composer on a Laravel pronect&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require laravel/socialite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the way socialite works is that a user clicks on a link that redirects them to a 3rd party site to login and then redirected back to your site for the actual login.&lt;/p&gt;

&lt;p&gt;So first, let's create the socialite controller&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:controller SocialiteController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Redirect
&lt;/h2&gt;

&lt;p&gt;So this is when we redirect the user to 3rd party OAuth provider to login. Add this method to the SocialiteController&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @param string $driver
 * @return RedirectResponse
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$driver&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;RedirectResponse&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$driver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the redirect takes a &lt;code&gt;$driver&lt;/code&gt; as an argument so we can add any driver any time. Say, you had used Google before, and now you want add GitHub, you just change a route parameter&lt;/p&gt;

&lt;h2&gt;
  
  
  Callback
&lt;/h2&gt;

&lt;p&gt;After the user is logged in, they are  redirected to a callback URI that does the actual login. Add the following method to the controller&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @param string $driver
 * @return RedirectResponse
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$driver&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;RedirectResponse&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$socilite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$driver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;to_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Something went during. Please try again.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;whereEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$socilite&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_null&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$socilite&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$socilite&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$socilite&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nf"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Registered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;regenerate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;intended&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RouteServiceProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HOME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method also takes the &lt;code&gt;$driver&lt;/code&gt; as an argument to tell socialite which provider was used login.&lt;/p&gt;

&lt;p&gt;If for some reason something went wrong, we redirect the user to the login page with a flash error message&lt;/p&gt;

&lt;p&gt;We then check if the user has an existing account, if not, we create an new one with a random password which they can change later from the profile page if they so wish&lt;/p&gt;

&lt;p&gt;Else, we log them in and regenerate session to prevent session high jacking by bad actors&lt;/p&gt;

&lt;p&gt;Finally we redirect the user to the intended URL&lt;/p&gt;

&lt;h2&gt;
  
  
  Routes
&lt;/h2&gt;

&lt;p&gt;To  make this work, we need to register the routes for this controller&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/{driver}/redirect'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;SocialiteController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'redirect'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'socialite.redirect'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'driver'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/auth/{driver}/callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;SocialiteController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'callback'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'socialite.callback'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'driver'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'google'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we add &lt;code&gt;{driver}&lt;/code&gt; as a route parameter which will be injected into the &lt;code&gt;callback&lt;/code&gt; and &lt;code&gt;redirect&lt;/code&gt; method thanks to &lt;a href="https://laravel.com/docs/10.x/controllers#dependency-injection-and-controllers" rel="noopener noreferrer"&gt;Laravel's Dependncy Injection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Say a bad actor realizes that the &lt;code&gt;{driver}&lt;/code&gt; parameter is dynamic, and tries other characters like a social driver that does not exist, that could have unwanted side effects.&lt;/p&gt;

&lt;p&gt;To prevent this, we call the &lt;code&gt;whereIn()&lt;/code&gt; method and tell Laravel to expect only a defined set of values for the &lt;code&gt;driver&lt;/code&gt; param, in this case just "google". Anything else besides that will throw a 404 Not Found.&lt;/p&gt;

&lt;h2&gt;
  
  
  Socialite authenticate with Google
&lt;/h2&gt;

&lt;p&gt;Our code is ready for use, in production! In this example, let's allow users to login to our app using their google accounts.&lt;/p&gt;

&lt;p&gt;First, get credentials from Google Cloud Console&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;, visit &lt;a href="https://console.cloud.google.com" rel="noopener noreferrer"&gt;https://console.cloud.google.com&lt;/a&gt; and signin with your google account&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;, Create a new project or use an existing one&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3,&lt;/strong&gt; Click the 3bar menu and select APIs &amp;amp; Services &amp;gt; Credentials&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbybifrad06a8xxug78if.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbybifrad06a8xxug78if.png" alt="image" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;, Click on Create Credentials &amp;gt; OAuth Client ID&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;, Select Web Application from the dropdown and enter name and redirect URIs&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0pdwf6unbert944w60d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0pdwf6unbert944w60d6.png" alt="image" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the redirect URI this is where you put the link to your callback route replace the &lt;code&gt;{driver}&lt;/code&gt; param with "google". For example &lt;code&gt;http://localhost:8000/auth/google/callback&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now all that is left is just creating a link that your users can click to login with Google. For example if you have a Breeze Starter kit installed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'{{ route('&lt;/span&gt;&lt;span class="na"&gt;socialite.redirect&lt;/span&gt;&lt;span class="err"&gt;',&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="err"&gt;')&lt;/span&gt; &lt;span class="err"&gt;}}'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;x-secondary-button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Continue with google"&lt;/span&gt;
                        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-full justify-center mt-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://tuk-cdn.s3.amazonaws.com/can-uploader/sign_in-svg2.svg"&lt;/span&gt;
             &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"google"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-base font-medium ml-4 text-gray-700"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Continue with Google&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/x-secondary-button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this helps you to create more smooth login flows for your apps. To make sure you don't miss another tutorial like this when it's up, follow me here on DEV and get a ping each time I make a new post.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
    </item>
  </channel>
</rss>
