Stimulus, Hotwire, Bootstrap 5, Rails 6 - and a viewer question!

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

I really am dreadful at self-promotion! Anyway, I made another rails 'tutorial' video, this time starting to build a time tracking app (in response to a viewer request) using Rails 6, Stimulus.js and Hotwire/Turbo, with Bootstrap 5 and FontAwesome thrown in for good measure!

I hope it's of benefit to someone!

👍︎︎ 19 👤︎︎ u/philwrites 📅︎︎ Aug 05 2021 🗫︎ replies

Love your videos !!! thank you

👍︎︎ 5 👤︎︎ u/alm0khtar 📅︎︎ Aug 05 2021 🗫︎ replies
Captions
today it's going to be hot wire stimulus rails of course and answering a viewer's question stick around [Music] hey guys phil here again with another ruby on rails tutorial maybe i don't know if it's complete enough to call it a tutorial but we'll see how it goes and what how this started is um a couple of weeks ago somebody i should have the message up here so i could say who but somebody asked me a question on youtube about how to show duration they wanted to do something where they were timing you know like a start stop timer and then they wanted to add up those start stop timers to say this is how long it took so i thought oh that's an interesting um little challenge and i'm not going to completely i'm sorry to you i'm not going to completely do it for you but i should be able to give you a good idea about how to do it but in the meantime i'm also going to do a hotwire stimulus app from scratch to show you to show this duration thing my inspiration for this was twofold one was toggle track which is a cool little uh timing app online timing thing that i use to track my my time when i'm billing for clients and the other was you know 25 years ago i was working in belgium and uh we didn't have a lot to do so i sat around and i wrote this small application called the contractometer that became quite popular with contractors in the company and quite unpopular with management because it would sit in the corner and would tell you how much money you made every minute so i'm gonna do like a a new version of that so let's enough of the waffling let's get on with it let's go into my development testing hopefully you can see all this i think i've got my screen i've got different recordings set up today because i don't know what's going on i couldn't get obs to work it was using 160 of my processor and later today i'm going to be upgrading my uh mac os so if you don't hear from me for a while you know that that didn't go too well all right so let's uh take a look at what we got here i'm uh running ruby 2.7 and rails what do we got to uh 6.1.4 which i think's the latest six maybe it is maybe it isn't hard to tell one of the other reasons i'm doing this is that you know i did that hot wire tutorial uh a couple of months ago not that long ago like four or five months ago most of that stuff doesn't seem to work anymore they've changed things so i'm doing this one as a new hotwire stimulus tutorial let's oh my god i'm talking a lot today sorry the coffee's kicked in let's create a new contractometer good name for it i think and let's use postgres i assume that postgres is running and we don't need no stinking tests and we don't need to run bundler right away because we're i'm just gonna add some stuff immediately anyway and we certainly don't need turbo links why is this still even the default in rail six let's let it rip there we go and it's warning us that we have to run webpack or install and all that by ourselves we'll do that in a bit give me a minute i'm getting there so we can use my favorite command which is bundle add turbo rails and so that will check a gem turbo rails at the bottom and run bundler for us and we can also i think now would be a good time to run webpacker install just so that the thing knows that it has webpacker this might take a minute goes off and doesn't what all the stuff that whatever it is that webpacker install does check some yarn dependencies and things like that installs a bunch of other stuff that gives you tons of warnings because it's not updated properly all that good stuff hot day here in japan even inside now the aircon 28 degrees here okay so we've run that let's run turbo install now this is a bit of a beef with the guys over there hot wire so we have turbo rails which has pretty much zero documentation um it does all the stuff to install turbo for you in a rails app and to let you use it including running yarn and running hot wired turbo rails in the yarn and everything like that it also sets up a cable that uses redis by default so you have to have redis running um maybe we can take a look ah it doesn't matter you have to have redis and postgres obviously running for this to work so we've got turbo or hot wire so now what do you call it i don't understand now if you call it turbo we call it hot wire i don't know what hot wire is and i don't know what turbo is so let's add stimulus rails which will chuck some stuff in and once we run rails stimulus install it'd be nice if they just had one thing because i'm probably going to use them together maybe not always so stimulus install will generate a couple uh you know some directories some dummy controllers and things like that if you don't know what stimulus is you don't know what rails is there's some other videos maybe if i'm not too lazy i'll put links to them wherever links go i can't remember um i don't even know if you can see me because the way i'm doing gonna do the screenshot but i might do the links okay so let's open this thing up and see what happens there it is so we got our thing let's take a look at package here so we've got turbo rails and stimulus is in there and got all the stuff for stimulus controllers all that other stuff is in there nice and good and this will actually run um [Music] but we don't need to run it right now and let's do my thing you know i really hate the way it comes it looks out of the box so let's add bootstrap now this is gonna be bootstrap five and of course you have to add jquery okay we'll never get away from jquery people i don't know who you're kidding and we need because it's bootstrap five we need to add not popper js you need to add this at popper js core this will get all the goodies um for bootstrap and you can see there's proper js there's bootstrap fine let's open the application.js here and do the things that we need for bootstrap which is import bootstrap and uh you can see that it's imported the hot wired turbo rails there for you and imported the controllers already for you and we're going to import a file that's not there yet style sheets application and we're going to create style sheets slash application.scss and in here we will add import this is just to get all the bootstrap stuff in now because i'm typing this i'm probably going to make a state mistake and nothing will work right bootstrap slash scss slash bootstrap this one you do use a semicolon that should work he says without much confidence um and while we're here let's take a look at the app layout and oh yeah let's do that div class container just so we give a nice bit of padding around everything that we're gonna do okay i think we are all set up how's that 10 minutes in or so um what else oh i'm gonna add this i've got this little snippet of code this is i have a love-hate relationship with vs code um workspace it's not crazy that you have to do it like this format it i'm going to add in emmett into the jvs code thing um settings because i'll show you like so here remember how i typed that out but now with emmett you can just do div you see it says emmet abbreviation there and i can just paint and it'll do a tag and put the cursor in the middle i find it quite handy to use this emmett stuff uh so emmett's in there and let's also add uh i know some people don't like it but i like it i'll add robocop in there and run bundle robocop if you don't know what that is um [Music] is a kind of ruby syntax checker and things like that so that's all installed and running so we are up and running so let's let's put a bookmark drink more coffee i think i'm drinking a lot of coffee today you know so i i'm i so yesterday uh i did this video and i got about three quarters of the way through and nothing was working and that then i had to go down the rabbit hole of figuring out that um hot wire or turbo had changed and the way you installed it and everything so i figured all that out last night and here we are version 2 or version 3 of the video hardware problems whole shebang that's why i'm drinking a lot of coffee all right let's clear this thing out and let's create our application so i'm going to use the scaffold you know somebody sent a message i don't know it was complaint or whatever or observation that i use scaffold and it's like why are you using scaffold why aren't you doing everything by hand it's like you know why why are you using power steering in your car you know same reason makes things a little bit easier we don't use it all the time when we're going straight but when you turn corners it's nice to have power steering so i'm going to use scaffold excuse me and we're going to create an object called time tracker which needs to have a topic which will be a string rate per hour which will be a numeric which is a float type thing uh we're gonna we need to start it at which will be a date time and end it at which will be a date time and a status which will be a string that's all i'm going to do if you know i'm going to put this stuff up on github as usual and if you want to fork it and make a commercial product out of it like toggle track um here's what i would recommend doing what i'm not gonna do today uh while we're while i'm talking let's run that you need to you you should have projects that's so easy to do i could i could do that but i'm just i don't see the need for doing it so you'd have your time trackers associated with projects and then you would sum up that duration on a project actually and then you would do some niceties about months and days and years and so on and of course you would have users so you'd use devise and things like that maybe i could do a second video because i'm not going to have time to do that because of all this talking like i'm doing right now but i could do a second video which added in those things maybe on but you know it's just uh i'm going to show you the nuts and bolts of getting stimulus and hot wire running that's what i think everybody wants to know they don't care about me writing an app all right so we've done that um now experience tells me let's go over and we don't want a topic to be nullable it's fine that you don't have a rate per hour we don't want somebody that doesn't have a start of that perhaps you won't have an end to that because something will be running and you have to have a status so i'm going to put those things in there um we might put validations in i might not bother to do that just let database error out for me but in this demo so let's rails db create and db migrate now you can do all that on one line you don't need to ampersand shell script commands together and it should with any luck work holy cow there we are so we're all in and we can go and look at our schema if we want which has one table we can see everything's fine it's a decimal and i think decimal relates if i say numeric depending on the database it'll have a different type i think that's the deal there and we're almost ready to run it roots and let's go in there and add the root root of time tracker's index let's run this thing let's leave this shell there let's create a new shell rails s i won't do anything fancy with that one day i'll do a video about engrock i love ngrok so here we are and you should just see this standard rails you can see it's compiling webpacker over here and yeah we can see the standard uh rails index page we take inspect just to make sure the bootstrap is working indeed it is this is bootstrap five right i don't know how you tell okay uh so we're all good to go that's the standard rails and you know you can do the see the standard rails forms and all that again this is a bug standard rails application we don't care about that so much what we do care about okay uh let's start with getting rid of this index index we don't need you at all what we do need is um pretty standard okay i'm gonna have a form if you've ever seen toggle track i'm ripping off toggle track pretty pretty much exactly let's take the form put that in the index and form okay well let's just show you what we got so we don't we won't have one of these if we run this we'll get an error because we don't have a time tracker so let's go to the controller and in the index we'll make a time tracker keep track no pun intended keep track of how many things i'm going to changes it's only like five lines in the controller you need to change to get all this working with hot wire so there we are this is the form that's being generated from the form it's not the greatest form in the world um so let's let's change that first so form with model time tracker that's fine we could leave those errors but i'm going to take those out because if we got errors we just what are we going to get errors for um [Music] and let's do this because what i want is a little form across the top so use the image thing that i installed we're going to do a class of row and class let's do 9 with column remember bootstrap is 12. so we're going to do that and we will do the text field topic and for me uh okay well we need that form control class and i like to have a placeholder i love placeholders so if you're having to do labels uh placeholder what are you working on okay so you can go and the rate per hour we just need one i'm not going to do anything but i will of course do the form control we don't need the labels and we don't need to start it at we don't need the end of that we don't need the status and we're not going to do this um let me show you what i do so this wouldn't be so bad uh if we save this format it we can see now we go over here and look across the top we'd have that nice form almost not bad right ooh he's fast you see that's because i've done this so many times i would say let's do a margin y so margins on both sides would be in margin x margin on top and bottom mean margin y of two let's just give it some space i love my spaces and then the form submit i'm not a big fan of honking big buttons i much prefer icons and i don't mean russian icons i mean text icons so the way we do that or the way i do it is by using font awesome i love font awesome i pay for it incredibly and anyone who knows me knows how cheap i am i do pay for it and i'm not going to use the paid version here i'm going to use a free version which is free to you i don't know if i can do an affiliate link for font awesome maybe i could put one down so anyway yarn add oh this drives me crazy fort awesome i don't know if they're being clever or was a typo or what the deal is but anyway fort awesome font awesome free that will add the yarn in hopefully and [Music] then we can simply go to our app.js i think and let's import our there it is for awesome free css all that means we can use all the font awesome for awesome font awesome uh icons and then we can't do that of course um using a submit because a submit you can put nice you can put text there oh it's got to compile sorry so you put text there but you can't put html you know there's nothing you can do to put html in there you do it like that it'll say be nice if you put do html save it no it doesn't work you cannot put html in there so you can't use form submit what i use instead is form button now button's not perfect because of the way it looks but for this it will be okay and we just make it of a type of submit do end and then in the middle we can have an icon and let's go over font awesome let's use a play icon and what do we got uh there's this one now this one i'll do this is the only one that works in free i think anyway so now if we refresh you see instead of this oh yeah got a little icon as our submit button pretty good there we go the forms there if you want a little proof zero that out i'm working on something nice you can see how many times i've tried this my rate is five dollars an hour i'm quite reasonable um or maybe it's five bitcoins an hour and you can see it failed first of all it's because started at is uh null and we know that the status and the things and you're saying phil but where's the status where's the there's the things that you said couldn't be known so let's take a look at the controller when we create something we know we're going to create it let's do it like this let's say the started at we're going to create um a hash here with those default values and the status of running and then we're going to merge those with the actual params that are coming hopefully you can see that formatting here all right so i've created the hash a hash with that and merged those into um the time track params now when we go back let's go back here to our oops come on let's do that and if we say something nice five bitcoins an hour let's take a look at our form and there we go it was created this is the standard show because we haven't changed any of that stuff yet we can see that um our tracker was created now the other thing we want to do on this index page now that we've got that working is let's go over and um simply render i think i deleted that i could have kept it render um [Music] oh you're too clever render you just need to render time trackers right now this i think i've talked about this before um kind of little auto magic that will go through iterate over this collection and then call a partial [Music] which has the same name as the object and render that out so that's not going to work because we don't have the partial so let's create the partial time tracker html erb and we've already done the form so let's grab the form here let's dump the form in and instead of using the form we'll just use time tracker topic and time tracker rate per hour and instead of the time tracker duration we'll get to that in one second don't worry and we're gonna leave this column blank for now i'll just put the status in there so again this won't work we know it won't work so i'm going to just comment that out so it's calling that partial and um there we go i'm going to change that form now that i think about it to move that button over there we go and let's put a horizontal line in there holy cow give some space okay it's running now this all started with somebody asking how to do duration so we don't of course keep the duration um in the object there's no point to do that what we will do is calculate that duration so uh let's go to the time tracker object and actually do something inside of here and fix this stupid thing and okay so we want create something called duration and it's pretty simple um we can do a time current minus started at we'll change that to seconds we know started at can't be null right so we just do that and i said it yes duration and there we go 232 seconds you can see there's a couple problems here already first of all who cares if i refresh yeah 240 seconds blah blah blah first of all doesn't look very nice and second of all if it's running that's one thing but if it's not running um it's not time current is it so let's say if it's running we would do this otherwise we want time current no we want ended at minus started at and you say well what's running so let's def something to say running and what i would say is a status slightly hacky but status of running probably normally i would do a constant or something so if it's running we're gonna show the duration and that shouldn't change and let's make it look better time track move helper time helper there it is let's make something well i've done already um this answers your question my faithful viewer about how to show durations and this let's call this duration display let's pass in a time tracker this is how you do it duration it's so simple you're going to kick yourself use maybe it's you didn't know the word duration maybe that was your problem not being facetious if you don't you know that word um then you're not gonna find this there is actually something called active support duration and we can put that in now that we've created which takes a command of seconds and now that we've created this duration we can do that and let's go to a console and i'll show you how this works and so let's get a time tracker last so we've got that time tracker we can see the duration is 395. sorry the colors on this suck um can i change the colors grass well anyway it's not gonna do it so anyway we can see the duration of seconds there now if you use um stupid me i should have called it time tracker okay so if we look at what active support duration will do 7 minutes and 19 seconds done right there but i don't want to display it like that so what you can say is parts and it will return a hash of how many minutes how many seconds how many hours how many days i haven't tried to go longer than that so i don't i don't know if it does weeks months years i'm not entirely sure oh let's let's find out time tracker update started at four weeks ago let's see what happens four weeks and three seconds cool um when is four weeks ago that's still within this no it's not anyway so it's not going to say months but there we go uh let's change this to uh four minutes ago because otherwise it's going to look crazy so we've got this parts which is a hash and i only want to use hours minutes and seconds so what we can do is format that and we use the funky unix so this is a format string of that says two digits colon two digits colon two digits colon and then you say duration hours but which might not be there so let's put in zero and then just keep on going and seconds oh my god seconds and minutes so if we grab this and you'll see here my four minutes and fifty yeah yeah we need to change that to parts so duration is like that minutes and seconds and if i format that it's zero five zero zero three so we're just going to put duration display in that partial now when we run this it's going to that's because i don't want to pass enough just the duration there it is 5 minutes 28 seconds 5 minutes 32 seconds that's the essence of what that person's question was now thank you for watching so far now let's get to the sexy stuff let's do first of all stimulus to upgrade update this thing continuously right that's what we're here for so uh there's a couple of other things i don't like this let's just clean this up first so if the time tracker is running else if the time track is running let's show where is that form let's show the play icon otherwise let's just show the stop icon get rid of the status we can see that it's running actually we can and now we can use some so it's green and it's running and uh we can stop this and now that we're here time tracker update we know that we need to say ended at time current and a status of stopped all right so this thing isn't running anymore and we can see that it's stopped so let's first of all uh i'm gonna have to now because i don't have it's not running anymore another thing let's um my rates gone up we're gonna go back and this one is now running the other one is stopped all right let's get down to stimulus here what we want to do is if the time tracker is running uh we want to uh well we'll put that there too no we won't we want to have a stimulus controller so let's give this up and a data controller a timer and leave that for now so let's get to stimulus let's start stimulating let's create a new file timer controller i'm always lazy and copying the hello and we can then console log connected timer controller make sure let's um let's close everything else here now with just that um this should run you know we should see something here it's going to web pack everything because i've created a controller and there we are connected timer controller i mean it's nice doesn't do anything but it is connected so let's have the controller do something now to do something first of all we need to know what to do so let's create a static this is neat values and so this um kind of let you set a bunch of different things inside of a status a stimulus controller and we need to start and i'm going to use a refresh interval just to show you how that works so we've got these two things inside of values called start and refresh interval notice the camel casing here we can pass those values like this um data uh timer so we need the name of the controller then we need the name of the value and then we need to say it's a value and we're going to do this and we will put in time tracker duration which is the number of seconds and uh because we're on using javascript we're going to multiply it by a thousand just because javascript's date function is in milliseconds so we're going to do the start value like that and then what was the other thing i said it was the refresh interval now instead of camel casing we replace the camel with a dash and lower case and so we can say how often we want this to refresh we're just going to do it by every this is in milliseconds as well um so now if we go over to the timer controller let's just log these out so that you know i'm not lying to you this dot start value so you use it's like three different places three different uh nomenclatures to access the same value up here it's in a hash called values with just start over here we say start dash value and then to actually access it it's this start value camel case i don't know what is going on over there with the stimulus guys but and then refresh interval value so we've got those two things logging out let's just refresh the page it's going to have to rebuild did i remove something i don't even know what this is time tracker seven wrong number of arguments given i'm not sure what's going on there let's fix that in a minute okay um not a number because i'm not passing it i mean time tracker duration is a valid thing right yeah okay 349 1000 why that doesn't work i don't know what's going on there anyway there's the milliseconds there's the refresh interval in milliseconds good to go passed in so let's do something with those now and i always like to create a separate method that does that um let's move those down white space there we go and start date is new date so we're going to create a new javascript date object from the start value and uh again just to show off let's see what that actually looks like and then if if this has that's the nice thing of using these static values here so if it has a refresh interval value then start refreshing so because we're using this special values it we can check in the presence of that so is refresh there again special way of using camel casing so if there's a value in there um then let's start refreshing and then we can say start refreshing i don't know why it's in that drop down list it's not anything special you can call it whatever you want so then we're kind of doing standard javascript really refresh timer because we're going to do a set interval and yeah so we're kind of just doing some standing we'll do another method here draw the timer and [Music] oh oh oh draw the timer yeah like this refresh interval value no not has we go um now we need to do that draw the timer let's draw the timer and what we're going to do is set this the element inner html to be another method holy cow method crazy seconds to time this is something i stole from the internet so let's take a look at that and uh what we so we need to pass in the number of seconds and the way we get the number of seconds we want to say what's the time right now in milliseconds minus what was the start and then because we're doing that we want seconds in our thing so we're just divided by a thousand and then we want to seconds to time and i'm going to paste this in and show you i didn't write this found it on stack interchange stack overflow so those are our seconds and we're just going to grab out the hours add it to two grab out the minutes grab out the seconds and so we have exactly the same format like we had in the ruby so because i'm putting it in by default i'm going to put the xx and then what you should see is this thing now refreshing every second and figuring out the value so let's refresh it's going to build the controller again and that oops and there you go now that's a bit messed up so something is not totally right i'm wondering about that [Music] oh because it's not that at all is it it's not the duration it's of that when did it start proof is in the name start value is the start of that let's try that again there we go tada okay that is a working thing to stimulus that's all the stimulus we need the reason we don't need to have a stop timer you know like something to handle stopped values whatever is because we won't even call it we will only show this stimulus controller if the thing is running and when the thing is not running we're just going to show the duration and it's all fine their stimulus is getting a bit long but i'm going to put in the hot wire now okay so that's where it gets a little bit funky when we have hot wire this hot wire and turbo is a publish and subscribe so we need to when things happen to an object we need to send those things out and then something somewhere needs to listen to those things and act on them so the first thing we should do is um is broadcast i i like to start at the broadcast so if we say broadcasts create a proc can do it there's a few ways to do this i'm just going to use this way because this is gonna auto magically create methods for us that um will send on all the crud actions right create update and delete and i'll explain this in one second it's difficult enough for me to type but to talk and type all right so what this is saying is we're going to broadcast this thing and we're going to broadcast it on the channel of time trackers and when we do an insert let's put the insert at the top and the target will be something called time trackers so if we go to our controller console i'm just going to grab the last time tracker now if i say time tracker update topic to be updated by me we will see they are sending out the performing the turbo stream action broadcast and it's actually going to use that partial if we find it in here there it is you know div class column row heading of two column nine um that's my partial it's so it's rendering on the server the partial and sending that out that's all we need to do on the back end what we now need to do is look on the front end and have the front end listen to a bunch of different things for instance turbo stream from time trackers that will start this page listening so we're just going to put that on for now and we'll take a look at the source here and [Music] we now have the turbo stream channel thing so it's listening onto some encrypted stream and what we want it to do is when things change turbo frame tag time trackers do no end so there's the form inside of another time tracker's thing which we can see there a turbo frame has been created called time trackers and inside of those are going to be the time trackers so now uh no it won't work yet because we need to do the form so let's find the form and we need to wrap the form inside of a turbo frame tag as well now if you remember turbo frames are kind of like a time tracker form this name doesn't matter and format that so now now the form here is also inside of the turbo thing so it knows turbo now will capture everything that happens there and turbo charging uh seven up to seven bitcoins an hour i click this what should happen is that it will show up here automatically and it did we didn't have any refresher mode now a slight problem the order is a bit weird and that's because in my controller i'm not getting things in reverse order here so let's reverse that so everything should be reversed and [Music] what else do we need to do well we don't have any way of stopping the timer so let's add another turbo form and this time we're going to add it on these icons here so let's go over there let's close the other things see again we didn't have to change the controller at all to get that turbo working so now if the timer is running let's do a form with time tracker model time tracker do so i'm going to put a little form here to handle the submitting and we'll add a form [Music] hidden status and the value of stopped and then we'll do the same thing like i did on the form we're going to take a form button and put that uh put that play i oops it's not quite right is it because if you want what you want to do if it's running you kind of want to stop it so we can make this maybe we don't need anything when it's play after it's done stop i wonder if there's a stop sign but anyway i'm going to change those around screwed up something there i think i need another end so now these are buttons doesn't look great but whatever so these when we click on them should stop but you'll see the problem in a second i'll do one and you'll see why it's not great yeah didn't work because there's just not enough woof updated status has stopped it changed the status but yeah it's getting all kinds of other funky stuff because the rendering of this partial failed i'm not showing errors and that's why so we're not seeing it the reason that rendering failed is because i set the status of stop but i didn't there's no end time so for me the easiest thing is to do before update let's set the end of that and we can do that now we'll check that inside so let's def a set ended at and we'll say self ended at equals time current if the status is stopped and we haven't set the end of that yet so what this is going to do is when we go to every time we update it's going to say okay is the thing stopped yes and it hasn't ended up in set no it hasn't so let's set it and then it'll save so what i'm going to do is i'm going to go and change that um which one was that id of three so let's grab that one tt have a time tracker find three and let's update that to be um status of running just so i can get it go back in on the display properly so things still running now what'll happen let's go over here now we click the stopped and did that work yes things didn't update and it got all messy because there's one thing missing for hot wiring and that is this doesn't know about hot wire so it can't update it doesn't know what to do with the updating so we need another turbo frame tag here with the dom oof id of the time tracker just that should work better there we go so now when you stop it it knows what road to do and it stops out automatically you can tell it was a little nervous if it wasn't gonna work okay uh i think pretty much everything there i've done it in pretty much an hour so what do we have let's do a quick little recap here we created a new rails app we installed hotwire stimulus bootstrap 5 font awesome what else did we install we installed lots of stuff and that was weird yeah we installed all of that we created our simple application to track time and we uh then we added stimulus to automatically update the timer i'm going to just add another one here to automatically update the timer we added hot wire to let us do everything as a kind of sim single page application we don't have to go to any other pages and we can stop things and add things dynamically so there we go that's the basics of it it doesn't do everything you know and there are some issues and i think these are issues with hotwire you will see that there's some errors in the console but hopefully i think you know hotmar is an ever-changing thing obviously so that'll get fixed soon anyway thanks for watching a bit of a long one i hope this helps you i'm going to try and bookmark this so you can jump around and we'll see you soon bye for now
Info
Channel: Phil Smy
Views: 1,604
Rating: undefined out of 5
Keywords: ruby on rails, learn ruby on rails, software as a service, saas business, how to create the perfect saas business, tech entrepreneurs, entrepreneur, become an entrepreneur, become an entrepreneur motivational video, be an entrepreneur
Id: YOiEAAac5Co
Channel Id: undefined
Length: 64min 13sec (3853 seconds)
Published: Sat Jul 31 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.