Workshop: How To Build Basic Desktop Applications in Ruby

Summarized using AI

Workshop: How To Build Basic Desktop Applications in Ruby

Andy Maleh • November 14, 2024 • Chicago, IL • Workshop

Workshop Overview

The video presents a workshop conducted by Andy Maleh at RubyConf 2024, focused on building basic desktop applications using Ruby, specifically leveraging Glimmer DSL for LibUI. This workshop consists of practical exercises designed to teach various aspects of GUI development through notable software architecture principles including MVC (Model-View-Controller) and MVP (Model-View-Presenter).

Key Points:

  • Introduction to Glimmer DSL for LibUI:
    • A cross-platform native GUI gem for Ruby.
    • It facilitates rapid GUI development with minimal prerequisites, having previously won a special award for its capabilities.
  • Workshop Structure:
    • The workshop is divided into hands-on exercises focusing on:
    • GUI Basics: Introduction to controls, properties, and listeners in a GUI.
    • MVC Architecture: Theory surrounding Model-View-Controller design.
    • MVP Architecture: Exploration of Model-View-Presenter and data-binding concepts.
    • Advanced Data-Binding: Insights into creating more sophisticated Ruby applications with better state management.
  • Real-World Examples:
    • The speaker discussed the utility of Glimmer by referencing a real application (the Sidekiq UI) developed by a previous attendee of his workshop. This established credibility for the functionality provided by Glimmer.
  • Hands-On Exercises Overview:
    • Building a Basic Application: Participants begin with simple exercises involving creating windows and controls, progressively advancing towards implementing data binding and using MVC principles.
    • Advanced Techniques: As the workshop progresses, participants learn to apply advanced techniques like observers and data-binding to make applications responsive to user input.
  • Final Exercises:
    • Andy emphasizes incremental development, advocating for participants to test their code throughout the exercises to ensure understanding and maintainability.

Conclusion:

The workshop concludes with a robust understanding of how to structure desktop applications in Ruby, focusing on MVC and MVP design patterns. Participants are encouraged to explore Glimmer's capabilities further during an extended hack session to apply their newfound skills in real-world applications. The workshop efficiently bridges theoretical knowledge with practical coding exercises, reiterating that clean code structures lead to effective application development in Ruby.

Workshop: How To Build Basic Desktop Applications in Ruby
Andy Maleh • November 14, 2024 • Chicago, IL • Workshop

Learn how to build basic desktop applications in Ruby with hands-on code exercises!

Workshop outline (every step will involve a hands-on exercise or more):
1. GUI Basics (Controls, Properties, and Listeners):
2. Observer Pattern and MVC (Model-View-Controller) Software Architecture
3. Data-Binding and MVP (Model-View-Presenter) Software Architecture
4. Advanced Data-Binding

This workshop will be conducted using Glimmer DSL for LibUI, the prerequisite-free ruby desktop development cross-platform native GUI gem that won a Fukuoka Ruby 2022 Special Award after getting judged by Matz, the creator of Ruby.

Please install the latest version of the Ruby gem (run `gem install glimmer-dsl-libui`) and confirm it is working (run `glimmer examples`) in advance to hit the ground running when the workshop begins.

RubyConf 2024

00:00:15.000 so everybody here wants to learn how to build basic desktop applications in Ruby uh so I'll help you walk through this
00:00:21.439 Workshop it's a 2hour uh hour Workshop which will involve uh some Theory and some exercises as well as even tests um
00:00:30.199 before I get started though I want to mention that I gave this workshop last year a different version of it uh which
00:00:36.640 we've I've improved quite a bit for this year but uh after I finished the workshop last year Mike peram who is the
00:00:43.399 owner of the sidekick project reached out to me and told me that he built a sidekick UI using glimmer DSL for libui
00:00:50.079 so this is like a product of uh the workshop of last year so it's just giving you a real example of real apps
00:00:57.280 that you could build by uh using the knowledge that you would learn in this Workshop so quick is a UI for sidekick
00:01:04.040 that is an open source Ubie Jem it was built built by Mike peram initially and then he had me help him with the rest of
00:01:10.640 its features he built that like one screen MVP of it and then I helped him with the rest um and also as a result of
00:01:17.320 that project I extracted a glimmer libui gem for graphs and charts so that was also a product of you know somebody
00:01:23.960 attending my workshop last year building a real app and then we extracted a gem out of it so now everybody can use
00:01:29.400 graphs and charts arts in libui so uh as far as the outline of the workshop uh we're going to start with
00:01:36.520 the guei basics uh and then we're going to move on to the MVC software architecture um and then we'll go into
00:01:43.880 uh MVP and data binding so to clarify mvc's model view controller it's a very famous pattern in guy apps or user
00:01:50.920 interface apps in general it's even used in rails web application development MVP is like a more advanced version of it
00:01:57.600 model view presenter uh which lever is the idea of data binding or the feature of data binding uh after that I'll given
00:02:04.439 that this is a basic desktop development workshop I'll just go quickly through Advanced Data binding uh without
00:02:10.280 spending too much time there and then bonus if we have extra time at the end these are outside the scope of this
00:02:15.560 Workshop but if we have extra time I'll go through them uh custom components and Scaffolding uh and if uh we don't go
00:02:23.239 through them then the hack Day event in the afternoon for glimmer DSL for libui
00:02:28.480 can be used to learn those two other Concepts so uh before we get started I
00:02:34.040 want to make sure everybody has access to the GitHub repo the workshop so you can SC scan this QR code uh but
00:02:41.319 hopefully everybody did that before the workshop because that was one of the prerequisites of the workshop so if
00:02:46.480 everybody has done this already we can just move on um has anybody not
00:02:52.319 downloaded the GitHub repo uh very few people uh so what I'll
00:02:58.560 do is uh you have the same QR code here you just
00:03:03.720 have to zoom but uh you can also copy those instructions so we cannot wait for
00:03:09.760 people that don't have the workshop because that was one of the prerequisites but you can ask your neighbors uh to give you the link and
00:03:16.480 they could give you the link on how to access the repo so basically you're going to get clone this URL you're going
00:03:22.120 to enter the repo and bundle uh and then the repo contains the link to the presentation slides so you can click on
00:03:28.400 the link and follow with me if you have an online connection if for whatever reason your Wi-Fi connection is down uh
00:03:34.439 the repo uh should include uh a copy of the slides in hard form so if the Wi-Fi
00:03:40.720 connection is good now but it it falls like uh it basically crashes later you can always use the repo presentation
00:03:47.920 slides um so I'm going to move on from here please ask your neighbors for help
00:03:54.640 if you need access to the slides
00:04:00.360 okay um so yeah part of the instructions is to install the glimmer DSL for libui Gem so this is uh the simplest glimmer
00:04:07.720 uh DSL for building desktop applications so glimmer started as a library that supported uh s the S swt goey toolkit
00:04:15.120 which is used in the Eclipse IDE and it supported it through jruby so it was originally A J ruby gem eventually I uh
00:04:22.800 the the idea of a glimmer DSL for building desktop apps proved itself so I ended up making seven other glimmer
00:04:30.120 libraries one of which is the lib UI Library uh other libraries will cover the gtk toolkit the TK toolkit FX Ruby
00:04:37.840 etc etc all of the glimmer libraries are similar So today we're going to use the glimmer DSL for liui library that won
00:04:43.639 the Fukuoka Awards uh as an educational exercise uh this is a very good educ
00:04:49.120 Educational Library on building desktop apps because all you have to do is install the gem and get started you
00:04:54.160 launch the examples you will get an app like this this has all the examples uh
00:04:59.479 and and uh if you launch any of the examples in it it'll give you apps like hello world like hello world with a
00:05:05.240 button uh some graphs and charts uh a table with a form and uh a control
00:05:11.479 gallery and a game here like snake so we're not going to get more into that we're just going to dive into the basics
00:05:17.000 because we want to get started with exercises so to present glimmer it's basically the simplest DSL possible for
00:05:22.080 building an app as you saw I said window open and it gave us the window uh now I can open a Ruby block it's a DSL and I
00:05:29.120 can customize the text of the window so so now it says hello world now I can add a label within the window and customize
00:05:36.479 the text to hello world as well and you'll see here it'll show a label within the window so it's really the
00:05:42.479 simplest code possible that you could think of to build guey here we can customize the font of the label uh and
00:05:49.720 then here we customize it again so it's very very simple here is a more sophisticated example so we have a
00:05:55.560 button counter uh before it basically is a glimmer libui application so in more
00:06:02.000 serious app development that's how we build apps we include this uh mix in and
00:06:07.039 then we declare a before body block where we initialize variables so here we just have a counter model and the
00:06:13.680 counter model is here it's just a counter that has a count attribute accessor uh so just to zoom in on it a
00:06:19.800 bit there it is uh and it's initializing the count to zero uh and then here we
00:06:25.960 just have a body uh and the body is just a window with a title and dimensions it's got a button uh the text of the
00:06:33.319 button is data bound unidirectionally to the count on a counter and uh if I click
00:06:39.039 the button onclicked it'll increment the count by one on the model however given that it's datab bound by
00:06:45.440 unidirectionally here every time the count updates on the model it'll automatically using this Arrow update
00:06:51.039 the text of the button so this is just a DSL declaration that's saying I want to bind this unidirectionally to copy data
00:06:57.840 into the text of the button and I have an onread converter so this will convert the data and it'll add a prefix that
00:07:05.440 says count so here we start with a button that says count zero we click it it becomes count one we click it again
00:07:12.520 it becomes count two so I mean I'm just diving into a more sophisticated example right off the bat just to give you an
00:07:18.039 idea of how we build apps with this but we're not going to start with this at all we're going to start with much simpler
00:07:23.639 exercises uh so I only included the glimmer DSL filii design principles here
00:07:29.680 so that you have a an idea about them but I'm not going to cover them we're going to this is a workshop so we're
00:07:34.919 just going to dive into exercises but you can read it on your own um so to start with the basics the
00:07:41.639 guey DSL Basics the the first thing you want to learn is the control keyword so uh lib UI lets you build U elements in a
00:07:49.680 user interface uh as controls so controls enable users to either see data
00:07:55.919 or Control Data manipulate data like for example inter ing text into a field or
00:08:01.280 selecting an option from a combo box uh or uh entering a number into a spin Box
00:08:07.120 Etc so think of controls as elements they're very similar to the idea of
00:08:12.240 elements in HTML so this is like the desktop version of what HTML does uh in
00:08:18.360 fact HTML actually copyed desktop development but yeah desktop apps usually call these controls Widgets or
00:08:25.400 uh components so anyways you need to know the control key words to get started so there's a word that's window
00:08:32.440 uh there's a word that's button label entry so this is a label so just to zoom
00:08:38.399 in hereit a bit this is a label this is an entry uh this is a button and the
00:08:43.880 entire thing is a window so that's the first concept you want to learn is controls uh they're usually underscored
00:08:50.720 and they're declarative they're a declarative way of building UI so there are many controls you can check the
00:08:56.680 glimmer DSL liui supported keywords in the in the libui repository the glimmer
00:09:02.440 libui repository sorry because uh this is uh it has a table that has a list of
00:09:08.200 all the controls that are available we're not going to you know worry about mastering like memorizing every control
00:09:14.640 right now we're just going to use the simplest controls in the exercises but I just wanted to give you an idea about
00:09:20.040 them there are even more controls like menus radio buttons scrolling area tab tabe items
00:09:26.279 Etc so next the next concept you want to learn is control arguments when you construct a control you use uh the
00:09:33.560 control name and then you can open parentheses and pass it arguments every control has its own Arguments for
00:09:39.640 example a window will take the title of the window as an argument optionally um
00:09:45.120 and uh so it builds a window that says hello world uh also you can pass some
00:09:50.200 some controls take extra arguments like this one takes uh the width and height of the window so it'll build a window
00:09:56.079 that's 300 by 200 uh so that's what that is the third concept you want to know is
00:10:02.120 the control content block so if you put curly braces in front of a control name
00:10:08.040 you open the basically the content of the block of the control so like we I showed you already we we had a window
00:10:14.480 with a label inside it so that the label is within the content of the window uh
00:10:19.640 so this is a way of basically so you can add nested controls or you can uh
00:10:24.880 specify properties for the window like I can say the text of the window is hello world that would be a way of specifying
00:10:31.839 a property or the third thing you want uh that that you can put inside a window is
00:10:37.079 listeners um so now if we go into a window you can
00:10:42.240 still pass arguments and open a block so you can do
00:10:47.680 both uh and here's an example of properties so I have a window it has a title property of hello world a Content
00:10:54.639 Siz property of 300 comma 200 so all you do is put the property name space the value that's it it's that simple it's
00:11:01.399 the simplest way of thinking about guey basically doesn't get any simpler than that uh control properties uh vary
00:11:08.880 between controls but I mean you can set the text of an entry or a label the checked property of a
00:11:15.120 checkbox uh this uh selected property of a combo Box Etc like there are many many
00:11:20.680 properties you can check them in the glimmer DSL for libui supported keywords table uh the next concept is listeners
00:11:28.200 so listeners always begin with oncore so uh listeners is a way of implementing The Observer pattern by Ena
00:11:36.079 you enabling you to observe part of the view to do uh work if something changes
00:11:41.800 so for example if a window closes I can say unclosing print a statement to the
00:11:47.120 user that says bye-bye uh so that's a way of basically hooking a listener to a window uh and this is an imperative code
00:11:54.959 block so it takes a do end block by the way for uh control when you put content
00:12:00.800 you intentionally don't want to do end here I'm using curly braces because we're declaring controls and we're
00:12:06.839 declaring their contents so we want to be declarative not imperative when you say do end that's imperative so that's
00:12:13.160 why glimmer has its own standards for blocks it's not the same standards that you use in in Ruby in general uh only
00:12:19.399 when you're doing imperative code like on closing that's when you say do end but when you're doing declarative code
00:12:25.079 you just open curly braces because you're just doing a declaration um here here's another example of a button that has the title
00:12:31.720 click and on clicked it does something so these are examples of listeners and there are many listeners onchanged on
00:12:37.959 toggled on closing on draw Etc on Mouse up on Mouse down Etc um okay
00:12:46.000 so next we have uh nested controls which I already mentioned it's basically
00:12:52.440 nesting a button within a window uh but also uh one form of nested controls is layouts so you can have a
00:12:58.720 vertical box it lay out uh controls in a vertical manner or you can have a horizontal box uh so it'll put controls
00:13:05.880 in a in a horizontal manner uh or horizontal way and then there's a form which builds a form and then there's a
00:13:11.800 grid the grid is currently unstable in glimmer DS in sorry in the libui toolkit
00:13:17.120 that glimmer covers uh so glimmer runs on top of libui and libui is a CA library and right now grid is unstable
00:13:23.800 in it so I advise against using it but usually you could almost do everything with vertical box and horizontal box um
00:13:31.360 So within layouts once you put a control inside a layout the control can specify
00:13:36.720 layout properties so all of a sudden label now can customize how it's laid out within a vertical Box by using the
00:13:43.639 stretchy property so stretchy if I say stretchy false here it means I don't want the label to stretch to take as
00:13:50.519 much space as possible I want the label to take only the minimum size needed uh and then the form will take the rest of
00:13:56.759 the space so by default stretchy is true on on any uh controls within a vertical
00:14:02.320 box or horizontal box but uh if I say stretchy false here I'm basically saying I don't want the label to take the to
00:14:09.240 take a lot of space just to take I just want it to take the minimum amount of space um
00:14:17.279 so um another thing that uh nested controls does is basically for a table
00:14:23.920 you can Nest columns so I want to specify that this table has an animal column a sound column a description
00:14:30.759 column and a guey column and here you basically say Okay I want a Tex a text
00:14:36.720 color column a checkbox test text color column image text color column Etc so
00:14:43.160 this is a way of basically uh most of the time we will only use text column
00:14:48.240 which is the simplest column in a table but there are many kinds of columns on a table so that's another form of vestic
00:14:54.279 control uh finally the last concept you want to know about the guey Basics is control operations so any control that
00:15:01.600 you declare in glimmer like a window will return an object and the object is a window proxy like it's a window uh
00:15:09.600 representation uh of the window that's rendered on the screen so this window object has methods and one of the
00:15:15.600 methods methods is show so I can basically show a window by calling the show on the
00:15:21.639 object um and the show uh method will
00:15:27.959 actually start the guey event Loop for you automatically so that you don't have to do the work yourself um so uh usually
00:15:36.519 in also you you might assign those objects to variables and then do the
00:15:41.639 things with those variables later on and I'll show you that in later
00:15:47.000 exercises um so another example of control operations is I I created a checkbox here and then I'm checking if
00:15:53.600 it's checked it says false and then I check it intentionally and now on in the user interface it should show a check
00:15:59.120 checkbox then if I check it again it's it's true so these are examples of operations invoked on the object that
00:16:05.480 was assigned from glimmer DSL keyword so let's get started with
00:16:11.000 exercises um so the first thing we want to do is launch The Meta example so
00:16:16.680 that's just running glimmer examples so if if you go to the directory of where
00:16:21.800 uh how to build desktop applications in Ruby is the directory of the repo we just say glimmer examples
00:16:31.639 uh there it is so this gave us the meta example which has a lot of examples and it shows us the code here like for
00:16:37.839 example I have a window with a button in it and on closing is going to say bye-bye and uh if I launch
00:16:44.880 it I can go here and press a button and it show does something so what I want
00:16:50.720 you to do is actually delete this code so I actually asked for basic window I
00:16:56.360 believe there you go basic window uh and then we're going to delete this code up to include glimmer so we're going to
00:17:02.440 keep those two statements uh and then here we're going to add new code so what
00:17:07.520 I'll do is basically I want you to follow with me just a second sorry I have it zoomed
00:17:14.400 in uh so I want you to follow with me uh in doing all the exercises up to the
00:17:20.439 section two uh uh up to section two basically so we're in section one uh so
00:17:26.600 we have exercise 1 2 3 4 5 6 7 8 9 10 11 uh they they get progressively more
00:17:33.360 complicated and then in the end we have a section one test so I want you to do all the exercises and then do the
00:17:40.039 section one test and you can get ahead of me if you want but what I'll do is I'll slowly go through the exercises
00:17:46.480 myself as well uh while you guys are going through the exercises uh but you can you can beat me basically and I want
00:17:54.039 you to keep keep going forward till you stop at section two
00:17:59.840 so uh for the first exercise we're just going to do uh window show so I'm going
00:18:05.200 to go here after I deleted the code I'm going to add uh window.
00:18:12.039 show and then launch and now it gave me a window here
00:18:17.360 that is empty so this is the simplest thing you could do with glimmer is Just Launch a window so I just took said
00:18:23.799 window show and I I launched a window so that's exercise one exercise two is how to pass arguments to un uh like a
00:18:31.600 control so I'll go here and do hello
00:18:38.159 world doesn't matter that much how you spell it but um there it is so it gave me a Hello World
00:18:45.520 window so now I delete that code so after every exercise delete the code and to be ready for the next exercise uh
00:18:52.520 Delete the code up to include glimmer so keep exclusive so keep include glimmer
00:18:58.159 in the codes um so next this is an alternative way of of creating a window so instead of using
00:19:04.600 arguments I'm going to use properties uh so I'll pass the title as a
00:19:10.799 property the benefit of this approach is uh when you have when you use properties you can use more advanced ways of
00:19:17.360 setting the property uh like data binding which we're not going to do right now but that's the benefit of properties over
00:19:25.559 arguments so I'm going to declare a window
00:19:31.240 and set a text say hello world and then I'm going to invoke show
00:19:36.919 on
00:19:46.840 it uh there it is my bad uh text is an incorrect property I
00:19:53.760 made a mistake so so I need to use title that's why it showed up without a title but if I run it again there it is it's
00:20:00.280 good so it's got a title of Hello World um okay so this is an example
00:20:08.640 including a label within a window with arguments so let's do so I'm going to zoom in just so you
00:20:15.640 can see I'll do um I'll clear the properties and I'll put a label here and it's also
00:20:22.720 Hello World um and then I'll launch it
00:20:30.880 there it is uh so now we have a window that has a title of hello world and it has a label within it that says hello
00:20:42.159 World um the next one is using properties for both so what I'll do is
00:20:48.320 uh I'm going to stop and let you continue uh till you reach the test uh
00:20:54.480 once you reach the test uh I want all of you to uh I so I want to stop because in
00:21:00.240 case people have questions I want to help them uh so before I continue I want to ask does anybody have any questions
00:21:07.880 so far okay anybody does anybody need
00:21:14.159 help okay awesome so I'll let you continue till till you finish uh all the
00:21:22.080 exercises up to section one the section one test
00:21:30.559 okay uh it looked like uh everybody's doing very well so I'm going to continue going through
00:21:37.080 the exercises on my machine while you guys keep going through the rest of them so
00:21:43.440 um okay so the next one was the one where we set properties inside the
00:21:48.840 widgets or controls so going to set a title on the window
00:22:00.600 and I'll open a block for the label and I'll set the text of the
00:22:08.080 label and launch it there it is it
00:22:17.279 worked okay this one starts getting more sophisticated it has a listener um so
00:22:23.520 let's do this one hello button with a listener
00:22:42.960 the button title is greet it's the first argument for the
00:22:48.720 button onclicked it's going to open a do end
00:22:56.000 block and then finally it's going to put a
00:23:09.320 so I'm going to launch it and we'll hit the Greet button and it
00:23:15.559 it gave us a greeting um okay so let's go with the
00:23:20.679 next exercise so this one is doing a a layout
00:23:34.440 and it's setting more properties like content size I'm going to set set content
00:23:48.000 true and then we'll open a horizontal box but we'll put two elements in
00:23:56.360 it a label with a full name or as a full
00:24:03.760 name and an entry that says John Smith and we'll hit launch so this
00:24:11.440 launched a window that has a margin margin around everything so one of the nice things
00:24:17.640 about desktop development is usually you follow the operating system standards you don't customize the view that much
00:24:23.400 so you don't waste that much time on The View uh you can get more you can focus on the business requirements without
00:24:29.840 worrying about such details so if you need margins you just say I want the standard margin so that's what margin
00:24:35.279 did and then we set the content size and then we have a horizontal box that puts a full name and then horizontally it
00:24:41.399 puts an entry next to it so that's what that was so let me go with the next
00:24:49.000 exercise um okay so this one is going to have a vertical box as well
00:24:59.799 you know I am cheating and not deleting everything I suppose that is a good idea you don't have to always delete
00:25:09.640 everything okay and the vertical box is going to contain two horizontal
00:25:15.360 boxes the first one is full name and the second one is do OB date of birth the date of birth is going to use
00:25:22.679 a date picker instead of an entry and the T date picker is gonna get
00:25:28.520 a Time property with the year being 2004 the month being 11 and the month day
00:25:34.640 being 17 so let's launch that one there it is so now we have
00:25:40.760 a uh an app that lays out things both vertically and horizontally so uh
00:25:47.880 horizont like vertically we have two rows of uh fields and then horizontally
00:25:53.159 in the first row we have a full name and the value and then in the second row it's a date of birth and the value and
00:25:58.320 and this is uh like a date picker it's a special widget or special control that customizes dates so
00:26:06.760 let's close that
00:26:12.240 one okay next one is using stretchy so I want to show what stretchy does so
00:26:18.840 basically only the labels are going to get stretchy false so I'll open the label
00:26:26.840 block and I'll put stretchy false and I'll copy this and paste it
00:26:33.080 here that's it so now when I launch it something changed so you can see that the labels
00:26:40.679 are like do is only taking as much space as needed for the doob uh word uh or
00:26:47.200 acronym but but and then the rest of the space is given to the next control so when you say stretchy false basically
00:26:53.600 we're preventing this from stretching all the way
00:26:58.679 so I mean show it to you on the slides before full name and do were taking
00:27:04.679 exactly as as much space as the next uh control uh so they end up sharing the
00:27:10.240 space breaking the space into two halves uh and each taking 50% of it the issue
00:27:15.799 with that is uh if like if there's a lot of empty space here that's wasted it's not a good idea to do like to lay things
00:27:22.880 out this way so the next Improvement is you can make stretchy fs and make each one of those labels not take all the way
00:27:29.919 uh however this comes with its own issue so one of the recommendations of user experience uh development or user
00:27:36.200 usability is basically to have alignment between Fields so this made them unaligned so I'm going to show you the
00:27:42.279 next option this option will align them and we avoid taking extra space beyond
00:27:48.200 what the longest field will take that's by using a new layout which is called the form layout uh form will simplify
00:27:55.200 things I will not need a horizontal or a vertical box anymore I can just take the form put uh controls in it and for each
00:28:02.480 control label becomes a property instead of a separate control so when I uh
00:28:07.559 specify an inry i specify the label for it and then the form the form layout will take uh will take uh
00:28:15.120 basically uh well will it it'll control where to put the label for you so that you don't have to worry about it so let
00:28:21.960 me get back here and write that that code
00:28:29.960 instead so this code is going to be a simplification so I'll remove the
00:28:35.039 vertical box make it a form I'll move the horizontal box and I'll take the
00:28:40.200 labels and basically I'll remove the labels but I'll make in properties
00:28:46.200 instead so this can be deleted and then it becomes a property
00:28:56.360 here same same here I can delete this and then make it a property here because the form layout can use the information
00:29:05.320 of what of the label property to automatically lay the label for you out in the guei so let's execute this there
00:29:13.519 it is this one looks much nicer everything is aligned um and um yeah you get the
00:29:19.640 nicest version of the form here um it's still good to know about stretchy false because it's useful for other kinds
00:29:26.039 kinds of user interfaces that's why went through it uh but in any case let's move on so the most complicated sample in
00:29:33.240 section one is uh the option selector so the option selector is showing you so
00:29:40.799 basically we have three options options one two and three um and if I select one
00:29:49.120 of them or or two of them for example I want to see a summary of the selected options on top as a string so this is a
00:29:56.000 label that puts option one comma my option three um and uh if I if if
00:30:01.799 nothing is selected it'll show it'll show none but if I select something I want it to show me option one comma
00:30:06.880 option three uh so to make this work um basically well I mean let me run
00:30:14.080 it first let me copy that code want it so for the purposes of saving time
00:30:20.159 I'm not going to type this out I hope you typed it out but um before I started talking about it but I'm actually just
00:30:26.960 going to cheat and and take it from here if you feel like you're out of time
00:30:32.159 you can cheat it's not the end of the world but basically yeah gooey
00:30:37.480 Basics that's the section one directory and then you can go to
00:30:42.640 exercise 11 and then option
00:30:49.840 selector so let me just copy this
00:30:56.279 code and paste it here okay let me launch it okay that
00:31:04.679 worked so basically as you see it started with the summary of the selected options being none meaning none are
00:31:11.159 selected I select this one now it says option one I select option three now it says option one comma option three then
00:31:17.679 if I add two all of them are selected um and then I can unselect all of them again so
00:31:24.919 um the way this works is basically
00:31:32.440 Bally um so we have a vertical box to lay things
00:31:37.480 out vertically first as far as a label and options so we have two rows a label
00:31:43.320 row and an options row uh and then the label was assigned to a variable called selected options label uh and then we
00:31:50.600 have a horizontal box that lays out all the checkboxes and we're assigning all of them to a checkboxes array so Ruby
00:31:57.200 gives you a nice method called three times like times the times method on Integer or fix num so I can say three
00:32:03.519 times uh run this block and map it into an array that will get stored as
00:32:08.799 checkboxes and then here I have a for each run of the uh block it'll basically
00:32:15.559 create a checkbox control with the title being option and the option number and
00:32:21.799 then we use a listener so we have an on toggled listener uh on toling of the checkbox it'll basically do this work so
00:32:29.519 what what it will do is print the the checkbox that was toggled Let's ignore that the put statement is not that
00:32:35.440 important here but then we're going to take uh the checkboxes array that was assigned here and we're going to select
00:32:41.880 all the checked ones so this is uh like basic Ruby is I'm using a select
00:32:48.240 iterator and it's picking all the checked checkboxes uh and then here I say if this array is empty then just
00:32:55.080 print none otherwise uh take the checkboxes map them to their text so
00:33:00.639 every checkbox has a text at property and then join them with a comma so this
00:33:07.279 is showing you how to do things if we only have GUI uh with no MVC
00:33:12.440 architecture uh typically with MVC architecture you want to divide uh and conquer the complexity of this code by
00:33:19.760 having a model view in a controller or just a model in a view uh but here we're I'm just showing you the pain that we go
00:33:26.440 through if we don't have MV so without MVC we have all the logic in one place and that's that's how things
00:33:33.360 happen without MVC uh but uh again I'm trying to graduate you gradually through
00:33:39.360 you know we start with a view and then we move into MVC and we do model controller then after that we're going to do MVP which is a model view
00:33:45.799 presenter and data binding so for now we're going to just stick to writing views so I'm going to ask you in section
00:33:51.519 one test the section one test to basically do the same thing we just did except for a an address uh form so we
00:33:58.919 have an address form that has um six Fields so a label that says full name
00:34:07.120 okay a field of a full name with label and entry and then Street and then city state and zip uh and that's just to keep
00:34:14.000 things simple I could have actually asked you to use some sophisticated widgets here but for now I'm keeping them entry except for the last one this
00:34:20.760 one's going to be a checkbox and it will not have a label uh it's it's going to be billing and shipping and basically at
00:34:27.399 the bottom bottom there's a label that will print a summary of the address so here uh you know it's going to say John
00:34:33.399 do comma One Park comma Chicago comma Illinois comma 60654 and if I check the billing and
00:34:39.960 shipping checkbox it will add a suffix at the end that says billing and shipping so this is going to be your
00:34:45.480 first test uh so it's going to test your knowledge with this it's an exercise uh so you will implement this like just
00:34:52.960 open a ruby file locally on your machine and put all the code there and obviously at the top you have to add require
00:34:59.240 require glimmer DSL libui and include glimmer before you write your code uh
00:35:04.480 but then you're going to have to do this exercise uh one thing to note is that
00:35:09.520 you're going to have to use two listeners so every time a user types stuff in here you want to listen to The
00:35:14.960 onchanged Listener for every entry and then for the checkbox a checkbox has a
00:35:20.480 different listener it's called on toled so every time the checkbox is checked or unchecked the UN toogle listener is
00:35:26.800 triggered and when either of them get triggered you want to make sure to update this label with the summary of
00:35:32.320 the address so when there is no uh when the checkbox is unchecked you get this
00:35:38.280 when it's checked you get this so I'll give you 10 minutes to do this exercise and you're welcome to ask your neighbors
00:35:45.119 for help or ask me for help with it uh but yeah this is the simplest way of building desktop apps we're going to get
00:35:50.680 into more sophisticated ways in section two and section three section three will show us how to do real serious app
00:35:57.040 development uh but this is we're going to start with the simplest way of doing things so I'll give you 10
00:36:04.200 minutes and yeah you can flip between those two slides on your machine if you want to read the details you all of you
00:36:10.839 should have the slides on your machine
00:36:18.119 already it just occurred to me that this way of writing apps where the view mixes the code of the model and the controller
00:36:24.880 in the same place is how shoes application development happens in the past so glimmer improves over shoes uh
00:36:31.400 shoes was an old uh it's one of the oldest Ruby libraries for building goys
00:36:36.480 which stopped getting maintained and that's why glimmer took over and tried to do better than shoes one of the things that it does do better is
00:36:42.720 basically gives you a very nice uh structure for building apps with MVC or
00:36:48.000 MVP uh instead of doing things this way this is how shoes used to do apps in the past so it's good to learn more basic
00:36:55.960 ways of development because glimmer was inspired by shoes as well but it also improved upon it significantly so that's
00:37:02.480 why I'm showing you this approach first before I move into more sophisticated
00:37:09.079 approaches the irony is that the more sophisticated approaches will end up yielding simpler code to reason about
00:37:15.079 even though uh they're more sophisticated so you'll see that in section two and section
00:37:24.560 three um actually I do need to colle clarify something though my bad so the
00:37:30.560 test does not need you to write the code in The Meta example I wanted you to create your own Ruby file and run it uh
00:37:37.240 by saying Ruby and then the file that's it so actually for that it's simpler you
00:37:43.040 can run it from the command line you don't have to be
00:37:49.240 here yeah because you want to save your changes you don't want to lose them if here everything uh gets lost after
00:37:55.640 you've closed the app okay so okay so so yeah if you if you written
00:38:01.280 anything in the glimmer meta example for the for the test itself copy it into a file and save it because the glimmer
00:38:07.520 meta example right now does not support saving it's outside its scope to
00:38:16.040 save okay we're out of time so I'm going to have to go through the section one
00:38:21.240 test myself so for the rest of the exercises
00:38:27.640 will mostly execute them from the repo uh except for the tests so the tests will be uh code that you have to write
00:38:35.200 but the rest of the exercises will be executed from the
00:38:47.880 repo um okay my internet connection is a bit bad so I'm just going to use my IDE
00:38:53.839 so by the way I built this IDE in glimmer this is built in Ruby this ID is 100% built-in Ruby including uh syntax
00:39:04.599 highlighting like uh this stuff it's all done in Ruby so that's just giving you a real example of what you could do with
00:39:10.480 glimmer uh but yeah um so if I go to test one and open it up
00:39:19.760 um so basically I saw a few people uh do a near complete version of this so I
00:39:27.160 think some people got it but I'll go through it for the rest or for everybody even uh so we have an address form so
00:39:35.280 you can t put the address form as the title of the window you you can give it some dimensions and make it margined but
00:39:41.280 the key part is here we have a form and it has an entry that says full name and
00:39:47.520 onchanged it's going to update the address summary um so
00:39:54.240 basically how about I just run it so if we go to the repo
00:40:01.440 here um one way of running glimmer apps is to say glimmer and then the name of the app
00:40:08.319 so um if you go down into some exercises I give examples of that so you can say rubby or you can say glimmer uh but
00:40:17.520 basically I typically will will use glimmer uh because glimmer will automatically load the glimmer Library
00:40:23.920 even if your script didn't have require glimmer DSL lii so anyways I'll uh do
00:40:29.160 section one test and address form so let's run it
00:40:34.760 there it is so it gave us an address form um so we need to build the address
00:40:40.400 form but also on onchanged of every entry for example unchanged the full name I put John Doe and you can see here
00:40:47.040 it immediately updated a label that says John Doe which is the summary of the address um so if we go back here
00:40:58.599 into the code you can see here it said update address summary it goes here and it calculates the new address from all
00:41:05.319 the fields so every field got assigned to a variable so you see we have a full name entry a street entry a city entry
00:41:12.760 state ZIP and the billing and shipping checkbox so uh it's very simple it's
00:41:18.520 just you know you assign variables you go into you execute this method it grabs their data uh and it joins them it joins
00:41:27.720 all the ones that are not empty uh and then if the checkbox is checked so here
00:41:32.800 we check if the checkbox is checked it'll basically append this suffix uh and then it'll give you uh the
00:41:41.520 address summary and then finally you set it as the text of the summary label so at the very bottom I have a summary
00:41:47.760 label that's empty so you don't really have to put any value in it uh I mean I saw some people that put TBD that's okay
00:41:54.680 you can put TBD but you don't even need to put anything if you want to you could just do this and
00:42:00.160 um this will give us uh a summary at the bottom so now um on on in invocation of
00:42:07.800 every field we're calling this method to update the address so then if I type in here the name of the sorry yeah the
00:42:14.200 street name um Chicago let's just say Chicago
00:42:20.200 Illinois some zip code and then if I put billing shipping see it bumped it added
00:42:25.319 billing shipping here if I remove it got removed so um a simpler way of writing
00:42:31.079 this example would have been to duplicate the code so for example I could have just to taken this originally
00:42:36.280 when I wrote it I duplicated the code so I like I I noticed that I had to do this on every changed listener
00:42:44.359 so on every one of them I ended up doing this then I realized you know we can refactor this and extract a method out
00:42:50.440 of this so the next advancement would be to then take this code refactor it uh
00:42:56.920 and and put it in the method and then now the method does
00:43:02.040 everything and that's because we have variables that are assigned kind of like the summary label variable so that's
00:43:08.000 what variables enable you to do uh so that's pretty much it for test one so
00:43:13.559 let's continue into section two so section two is MVC software
00:43:18.920 architecture this is now getting closer to how you build real apps um so MVC was
00:43:24.559 a pattern that was uh invented by the people people that came up with small talk uh it was an old pattern called
00:43:30.319 Small Talk MVC and it basically had a view model and controller uh this is uh
00:43:36.720 the correct way of doing MVC it's not the rails way the rails way is a bit incorrect the clean correct way is
00:43:42.280 basically the view observe the model data for changes and if the model attributes change the view will update
00:43:49.119 itself uh and then the controller enables a user to interact with a view by observing uh user control U
00:43:55.960 mechanisms like Mouse up key up selection onclick stuff like that and
00:44:01.599 when those events fire the controller will manipulate the models and make uh
00:44:07.079 make a change in them but then given that the view is observing the model when the model is updated the view will
00:44:12.319 reflect those changes in itself so this is the correct way of doing MVC which will will result in the most
00:44:17.599 maintainable code uh rails unfortunately doesn't do it that way they they have the controller push data into the view
00:44:23.280 which is incorrect uh so but so you need to learn that and do the right wave MVC
00:44:29.720 for desktop development because it is simpler it will enable you to build better apps um and yeah it relies on the
00:44:36.760 Observer design pattern I already went through this now for the view to observe the model uh there is a keyword observe
00:44:44.480 that lets you observe a model attribute and then pass it a block afterwards and then the block will let you do some work
00:44:51.280 if the attribute of a model changes and I'll show you an example of that in a moment yeah like here's an example so we
00:44:57.400 have we observe a model attribute and then we pass it a block and then we do some work uh behind the scenes is
00:45:03.240 constructing a glimmer datab binding Observer object with the proc method and then it's invoking observe model
00:45:09.880 attribute uh if we're not using the glimmer DSL but we we have the library we can do it this way this is useful in
00:45:16.520 the model layer if you need to do observations but in the view layer we have the DSL so you can just do this um
00:45:23.240 so for example here uh so let's so there are two ways of doing MVC there's the explicit controller approach which is uh
00:45:30.839 more similar to uh the old Small Talk MVC so basically we have a model Class A
00:45:37.000 View class and a controller class The View class will have an on toggled listener that will uh basically uh when
00:45:44.359 the checkbox is toggled it'll it'll send work to the controller the the controller will toggle the option with
00:45:50.520 the option number for example um and also the view will observe the model uh
00:45:56.280 that's behind the controller so here we have an option selector model it'll say observe the selected options and if they
00:46:02.079 change I want my summary label the selected options label uh text to get
00:46:08.079 updated with a summary from the model uh so we delegate work to the model to calculate the summary so we separate
00:46:15.040 that logic into the model and then the view though will observe this uh array and if it changes it'll automatically
00:46:21.079 update its own label so that's a way of uh having the view uh observe the model and then update itself
00:46:27.680 uh uh Ruby gives us a simpler approach we can do implicit controller instead of explicit because the un toggled uh
00:46:35.119 listener and glimmer uh which takes a block basically the block of it is a controller so you can think of the block
00:46:41.960 as an implicit controller so that way you can cut out the middle man and not even create a real controller class and
00:46:48.480 just have this implicit controller call the model directly so it's going to call the option selector model and toggle the
00:46:53.839 option with the option number uh and then the view part doesn't change so um
00:47:00.160 for the rest of the exercises we're going to follow on GitHub uh and we're just going to run them from GitHub
00:47:05.599 directly before we get to the test so basically I want you to go through here
00:47:11.280 so skip through section one in the read me so we're going to go to the readme page uh which is the homepage of the
00:47:17.920 GitHub rep repository so we're going to skip all the way to section two and uh
00:47:23.599 for every example so all the examples live here so if I open this directory it's going to show
00:47:28.800 me uh all the exercises will live there but basically for every exercise I want you to copy this a glimmer and then this
00:47:36.559 uh file name so I I can just press the copy button here and then go to the command line paste it and boom it ran
00:47:45.520 the exercise so this is the simplest way of doing uh MVC right now it does the
00:47:50.880 behavior is exactly the same this was just a refactoring uh so the behavior is the same but uh the the code is much
00:47:59.280 better now because we have MVC architecture in the code uh so after you run it successfully uh you can look at
00:48:06.359 the code so if I click here to see the code actually it's easier just open it in my IDE so let me go to my IDE and
00:48:15.680 open it here there it is so we have an option selector model we have an option
00:48:20.800 selector controller and we have an option selector view uh typically you would separate them into different fil
00:48:27.160 but for the purposes of just doing an exercise here were keeping them all in the same file uh the view however became
00:48:33.240 a class so this is new for you so now we're doing something more advanced and more correct when you build real apps is
00:48:40.000 you want to create a view class and include glimmer into it that's the simplest way to do things there we're
00:48:46.720 going to learn an even better way uh eventually but this is the simplest way to get started you include glimmer in
00:48:53.240 the initialized method you just initialize a bunch of variables like the model and the control roller and then you create the guey body and then you
00:48:59.799 register observers because you want the view to observe the model for changes uh create the guey body has the old code
00:49:05.440 that we were working with just a moment ago so it's got a a window uh and usually you want to assign the window to
00:49:11.880 a global window variable or like an instance variable sorry uh but the rest is like just basic glimmer DSL so we
00:49:18.160 have a window it's got a vertical box uh it's got a horizontal box and then three times meaning for every checkbox it's
00:49:25.559 generating a checkbox here here uh and on toggle of a checkbox I'm basically calling the controller and telling it
00:49:32.040 toggle the option so uh in this approach we don't need to assign the checkboxes
00:49:37.559 to an array anymore uh that got simplified away now the controller takes
00:49:43.720 care of uh knowing about an array of checkboxes the controller and the model take care of those details so here all
00:49:50.040 we have to do is have the view toggle an option on the controller and that's it that's the simplest uh way of the old MV
00:49:57.000 approach how to do it basically have the view delegate to the controller and then the controller delegates to the model so
00:50:02.440 if we go here to the controller the toggle option will delegate to the model toggle option and then the model toggle
00:50:08.880 option will do the real work so that way this is like correct business app developments you have the model handling
00:50:14.520 the business logic uh you have the controller sitting as a glue between the view and the model and then the view
00:50:20.839 lets the user see the user interface and interact with the model and the model also has the summary method now so this
00:50:26.960 got refactored as well uh this will enable you to you know do the logic of checking of rendering the summary here
00:50:33.559 in the model uh so yeah the code is a lot better now um so let's jump into the next
00:50:41.319 exercise so now if we go to exercise two under section two again we copy the code
00:50:46.680 from here um we paste it here we run it and it's going
00:51:01.520 example uh and this will behave the same exact
00:51:07.040 way so um what's the what's different though let's open the code and see what
00:51:12.960 changed in exercise two so the the key thing that's different is we don't have a we have a an option selector model and
00:51:20.079 we have an option selector view the controller is no longer needed because we're using such a cool language called Ruby that lets you bypass the need for
00:51:27.920 an explicit controller by using implicit controllers as Ruby blocks so here what we did is basically we're like you know
00:51:34.720 we we're in Ruby on toogle is already taking a block this block is the controller it's an implicit one so I
00:51:40.799 don't need to create a class so we can cut out the middle man and this will then delegate immediately to the model
00:51:47.240 and it'll toggle the right option that's it and when the options are toggled um
00:51:52.400 register observers you know I forgot to go over this earlier it's also it's also on the other class register observers
00:51:58.559 will have the view observing the selected options array on the model and
00:52:03.880 uh it'll basically summarize the options and send set them in the label so that's
00:52:09.400 pretty much it this made things a lot cleaner and better so let's go through
00:52:14.760 the next exercise so the next exercise is
00:52:20.319 basically we're going to refactor the address form that we built in exercise one uh in order to make it follow MVC so
00:52:28.000 we're going to create an address class that will store uh text or buan values
00:52:33.400 basically all the uh all the fields are text except the checkbox this one's going to be a Boolean and then we're
00:52:39.240 going to have an address form view that will wire listeners uh just like we did
00:52:44.319 before to the model uh and we'll delegate the work to the model um so
00:52:50.040 I'll give you 10 minutes for this as well we should have 10 minutes for this and by the way this is the only the
00:52:56.920 tip of the iceberg there's much cooler stuff coming later uh so the coolest stuff will come at the end uh so I mean
00:53:04.720 there were three problems in shoes that glimmer solved one of them was uh mixing a view concern with the model concerns
00:53:11.880 glimmer solves that completely the second issue in shoes was H tables and trees didn't have their own widget like
00:53:17.599 I I didn't have a table or a tree widget which is important to business apps uh glimmer solves that it does give you a
00:53:23.040 table uh and in certain glimmer toolkits there are there is a tree widget as well and then the third issue that shoes had
00:53:29.240 was uh it didn't give you an easy way of building custom components glimmer does support that completely which I'll go
00:53:34.760 over in the hack Day event in the afternoon because it's outside the scope of the workshop unless we finish early
00:53:40.200 then I I can cover it in the
00:53:51.799 workshop but yeah in section three we're going to build an even uh smaller version of this we're going to refactor
00:53:57.680 this again it'll be even smaller it'll look very very cool by using uh Advanced features in
00:54:05.760 Ruby uh it just occurred to me you don't need to use your own code for the first
00:54:11.160 address form that we built if you didn't finish it it's better to actually take the one that was in the repo so it's
00:54:18.280 actually this one that that's in the test01 directory under section one uh so
00:54:23.599 I wanted you to take this and refactor it don't you don't have to start with the one that you built in case you
00:54:29.319 didn't finish it what I what I personally did is I
00:54:36.119 literally copied this code pasted it in a different file and then refactored it into
00:54:52.960 MVC one thing I might have glanced over quickly um yeah I did when I was showing the MVC
00:55:00.480 examples is that uh when you build the create guey body sorry yeah when you build the body inside create guy body
00:55:07.040 you're going to assign it to a window attribute uh or window instance variable
00:55:13.160 uh in order to launch the W show the window we need an extra method here show
00:55:19.119 uh and then it'll enable me to uh show the window so here then I can instantiate the view and then call the
00:55:25.680 launch method then that would launch the app uh that's one way of doing it I an alternative way is that if window was an
00:55:32.640 an attribute then I could have done here. window. show but it's actually cleaner to just have a launch method
00:55:39.119 hiding it hiding the details uh so you will likely need this as I mean you'll
00:55:45.119 definitely need the launch method as well in order to start your app unless you have a different way of starting the
00:55:50.480 app I mean a different trick for this would be to uh put this as the last
00:55:55.920 method and then invoke show directly here but it's not very clean it's better not it's better not to do that it's
00:56:02.680 better to just have a launch
00:56:08.559 method yeah we're actually out of time so I'm just going to go through uh the details of this uh test exercise and by
00:56:16.079 the way we're going to have a hack Day event in the afternoon so if you didn't get to finish the any of the exercises
00:56:22.079 or tests you will have enough time to go through all of them in the hack Day event afterwards um
00:56:29.440 so let me launch the test two implementation it's actually section two
00:56:35.920 test one sorry there it is let me just launch it just to show that it works so
00:56:41.880 typically you can say Ruby and the file name that should work or you can say glimmer and the file name glimmer is the
00:56:47.359 more default way in glimmer apps but we I already launched it you know now it's
00:56:53.039 it's summarizing the address as you can see C uh and then if I check the checkbox it
00:57:00.520 has a suffix so it definitely works um the code is basically uh we're doing
00:57:06.160 implicit controller MVC so we only have an address and an address form view the address has all the attributes needed
00:57:12.599 for an address like uh full name uh Street city state ZIP billing and
00:57:17.839 shipping uh I'm aliasing billing and shipping as a question mark method because it's a Boolean this is just
00:57:23.680 optional uh and then here the summary method does the work of uh calculating the summary of the address so this is
00:57:30.000 the same work that we did earlier in here inside this method update address summary uh except this one this method
00:57:37.200 was mixing model and view concerns because it was updating it was calculating the summary and then it was
00:57:43.359 setting it on The View label uh whereas here it's divided now the model is only doing calculating the summary that's it
00:57:50.000 and it gives us a pure string it doesn't talk to the view uh but then the view here it's observing the model
00:57:57.240 uh under register observers and it's saying observe address full name and uh if anything changes execute this block
00:58:04.400 and so if we look at this block it's basically taking the summary of the address that I just showed you and it
00:58:09.599 set it as the text of the label um and uh one more piece of the puzzle is that
00:58:15.760 for every field like full name we're going to call it unchanged listener and
00:58:21.599 have it copy the the entry text into the the model full name attribute so that
00:58:28.960 means whenever I yeah let me run it again whenever whenever I change any
00:58:34.920 field like I I type in John here it's basically copying this value and storing
00:58:40.319 it in the model attribute uh and then the view is observing the model attributes so then later the view here
00:58:46.520 at the bottom will actually uh this Lambda is going to fire on any changes that happen
00:58:52.839 to full name so this Lambda will fire it will take the address summary and it'll store it on the summary label and that's
00:58:59.920 why we see things updating here in the bottom as I'm typing through as you can see there you go so
00:59:08.079 that's pretty much it this is like a a clean uh MVC approach with an implicit
00:59:13.400 uh controller because like basically those unchanged blocks are the controllers they're implicit controllers
00:59:19.640 and here we have an un toogle this one will actually read the checked property for the check box and store it on the
00:59:25.440 model nice thing is that the GUI uh controls are all objects so you're just not doing normal object oriented
00:59:31.760 programming here so it's very simple um so that's pretty much it let's dive into uh section three now we have
00:59:39.799 about 40 minutes left or a bit less so um okay so now we're going to talk about MVP and datab binding so MVP is an
00:59:48.160 advanced a more advanced version of MVC it's like an evolution of it uh that I
00:59:53.559 believe Microsoft came up with it uh with the innovation of data binding so the idea is that there would be a
00:59:59.440 presenter sitting between the view and the model and the presenter could be implicit or explicit if it's implicit uh
01:00:05.520 basically a presenter will let you bind any field you see on the view to attributes that that that are on the
01:00:11.839 model so that way if the model changes the view will update and if the view changes the model will update uh so this
01:00:18.359 will simplify code significantly because then you can declaratively wire the view to the model you don't have to use
01:00:23.960 listeners like on toggled uh manually anymore except for certain op actions
01:00:29.720 like clicking a button so let's get into it so if I have an entry and it's got a
01:00:34.920 text I want a data binding basically bidirectionally to first name user so this is this syntax is completely
01:00:40.599 unheard of there's no I I don't know of any other programming language that has this only Ruby can do this Ruby is so
01:00:46.440 amazing it lets us do operator overloading uh within a DSL context and
01:00:52.000 here like basically uh the spaceship operator will give us a very intuitive
01:00:58.280 expressive way of saying I want a data bind first name to text on Entry bidirectionally um dat date time picker
01:01:06.760 also does the same thing on its time property and it's datab bound by direction to event time on reservation
01:01:12.880 um a button a button text however uh so you need bir directional data binding
01:01:18.880 when the view allows the user to make changes to the data however the button text cannot change usually once you set
01:01:25.799 up a button's text and it says submit in general uh the the pressing the button will not change the text however you can
01:01:32.760 have the model change the text so if the model updates you can uh so here you used unidirectional data binding and you
01:01:38.559 say if the count on a counter updates I wanted to propagate that change to the text and you can even set a converter
01:01:44.680 here I have a converter that will convert it to a string so assume here it's an integer it'll get converted to a
01:01:50.480 string and set as the text of the button so this is unidirectional data binding there are cases where unidirection data
01:01:56.119 binding uh is the better option uh or it works better it's when you don't have two changes happening at the same time
01:02:03.760 uh label text is the same way you use uni directional data binding with it um and then MVC MVP uh sorry like you can
01:02:12.520 apply MVC and MVP at the same time for example I can use data binding for most of the fields and then on click of the
01:02:19.079 button I can do an action and on click it's basically going to update the count on the counter but then given that the
01:02:25.359 entry text given that the view is observing the model to update itself here with an implicit presenter it'll
01:02:31.599 basically on read of the count it'll store the new value in here in this in this text box so every time I click the
01:02:38.640 button it'll update the value here automatically for me and also the button text is unidirectionally data bound to
01:02:44.400 the same uh attribute on the model so every time I click the button it's going to update both this 11 and this 11 it'll
01:02:50.960 become 12 on both of them so I mean yeah this is amazing like an amazing uh
01:02:56.319 possibility in Ruby is the ability to do operator overloading with and build dsls
01:03:02.880 and when you combine the two together I have the simplest code possible to describe the problem you cannot write
01:03:08.400 any code simpler than that to describe the problem I mean right now I'm I actually last year uh took the glimmer
01:03:15.000 project and built a web version of it so now we can use it in the web to build user interfaces for rails and I took a
01:03:21.960 reactjs uh application at work and I refactored a page I rewrote it it was a
01:03:27.480 500 line of lines of code of react code I refactored it using glimmer DSL for
01:03:32.559 web and it became 50 lines of code with this code and is super expressive way simpler than react way better like uh
01:03:39.880 way easier to maintain I would say a doubles productivity at least meaning you could do 12 months of work in other
01:03:46.000 Technologies in six months only in Ruby because Ruby is so much better than other Technologies so anyways uh let's
01:03:52.119 move on this is table data binding table is a bit more sophisticated because when you bind to the attribute on a model
01:03:58.319 like contacts the attribute has to be an array because every every element in the array is going to represent a row in the
01:04:04.000 table uh and the amazing thing here is with this very short sentence like in in one line of code uh you can it will
01:04:12.279 automatically datab bind all the attributes on every contact to every column on the table by convention so
01:04:18.720 glimmer does convention by configur con convention over configuration just like rails so here it's basically if we have
01:04:25.440 a name column it'll assume there's a name attribute on contacts and it'll try to use it uh and then it'll datab bind
01:04:32.279 automatically so with this tiny line it does everything for you uh in in other Technologies I usually have to write
01:04:38.319 lines of code to data bind data to a table here here it's a lot simpler uh here's an example where you can
01:04:44.119 customize the attributes so it's convention over configuration but you can also configure if needed so here
01:04:49.319 we're customizing the names of the attributes to different to map to different attributes so I'm just going
01:04:55.079 over this quick because these are Advanced datab binding approaches uh content datab binding is very Advanced
01:05:00.559 it's basically um you can datab bind the content of a control so I mentioned earlier that you can Nest controls
01:05:06.920 within each other and if I have a vertical box and its content might change dynamically you can use content
01:05:12.520 data binding so I can say I can put this content block within the vertical box
01:05:18.039 and bind it to an attribute on a model and every time an attribute changes it'll rerender that uh the entire
01:05:24.640 vertical box uh so here for example I have a dynamic form so if I have all of
01:05:30.039 those checkboxes checked I will see all the entries but if I go here and I say I don't want to enter an email and I don't
01:05:35.240 want to enter a street this part is has content data binding so the content will
01:05:41.799 get refreshed with the new entries that uh are selected only so it'll exclude
01:05:47.760 email Street and Country so this with data so this now updated dynamically so
01:05:53.000 this is a very Advanced feature of data binding uh you don't use it all the time but for the few problems it solves it's
01:05:59.000 very good so uh we're going to go through uh again a few exercises and then it we're
01:06:05.039 going to have two tests so test one is going to be uh refactoring the address
01:06:11.680 form that we just built again to to follow the MVP pattern uh and then uh
01:06:18.319 section test two is going to be uh a very Advanced example of using datab binding table data binding uh form data
01:06:25.680 binding and then wiring everything together let's not go there yet though let's go through the exercises
01:06:32.079 um let me just see how much time I have we don't have a lot of time and we need
01:06:37.920 about 20 minutes for the exercises we only have about 25 minutes so what I'll do is I'm going to Breeze through those
01:06:43.880 exercises and show you the code of each so the first one is uh whoops let me go
01:06:50.160 through the GitHub repo and go to section three there it is
01:06:56.319 so let's run option selector MVP so now we're refactoring option selector to be
01:07:11.200 now it's doing uh data binding so let's look at the
01:07:18.079 code um so the key difference here is that we don't have an uh an on toggled
01:07:24.920 listener any anymore now what we're doing is taking the checked property on a checkbox and data binding it to an
01:07:31.520 option attribute on a model so for every option there's going to be an option number here attribute so now if you look
01:07:37.599 at the model see I have attribute accessors option one two and three so that means the benefit of datab binding
01:07:43.880 is instead of keeping them in an array like we did before we can split them into their own attributes and you data
01:07:49.920 bind every checkbox to an attribute and that and you're done that's it uh however one um Wrinkle In This is that
01:07:56.640 we need to make sure that every time they're checked the summary is refreshed for the model
01:08:02.520 so what you do here is you override the writer for each one of them so I'm overriding the the attribute writer for
01:08:09.520 option one uh the one that takes an equal sign and I'm telling it to update the summary at the end uh so that way
01:08:17.640 every time we update one of them the update summary method will get triggered and we'll update the summary attribute
01:08:24.080 and then the set summary attribute is also datab bound except this one is unidirectional so the summary attribute
01:08:30.120 is datab bound unidirectionally to the text of the label and that's the reason why uh when I ran it every time I check
01:08:37.480 one of those you can see the label here updating because this text is getting uh receiving updates through uni
01:08:43.600 unidirectional data binding um so that's pretty much it now it would be nice if
01:08:50.640 we can get rid of those methods if we don't have to do update summary ourselves that would be very cool so
01:08:56.600 let's see the next exercise the next exercise is going to improve on this so this one's going to use a model
01:09:02.159 Observer so let me run this
01:09:07.520 one so there you go it works by the way you
01:09:15.040 can follow with me on your computer if you want to run the exercises feel free to do it uh but I'm going to keep
01:09:21.520 talking just to uh Breeze through the rest of the exercises so now let's open the next exercise that I just ran okay
01:09:29.080 notice there's an improvement right away uh the methods for option one equal option two equal option three equal are
01:09:35.359 gone now now what we we're doing is using a model Observer so at the model
01:09:40.600 layer if you want to do an observer you can use this class which comes with glimmer and uh if you use the proc
01:09:46.719 version of it it will basically build an observer that will trigger this proc or block on every change so it'll update
01:09:53.360 summary and then I'm taking the Observer and I'm telling it observe myself I am the object that I want to be observed
01:09:59.480 but I want to observe option one option two and option three that means if any changes happen to them this will get
01:10:04.880 triggered and it will update the summary and then the update summary method will then get called and then will update
01:10:10.960 this attribute and this attribute fortunately is data bound you need directionally to the label so it'll
01:10:16.440 happen it'll get uh reflected in the view so that's how this one works so it
01:10:21.679 actually improved the code a bit now the updates are happening indirectly it's still not the best code though it would
01:10:27.239 be cool if if the app if glimmer can know how to do this automatically for you so that you don't have to write that
01:10:33.640 code even uh so fortunately there is a way to go further and improve further Beyond this so let's go to the next
01:10:39.239 exercise so here's the next exercise it works
01:10:44.560 just wanted to show that it works but then here we go to um the code now boom now it's smaller
01:10:51.440 all of the sudden we don't have any Observer code here and we just have the three options we don't even have an
01:10:57.159 update summary method anymore we removed the summary attribute and made it a method and now if you call summary it'll
01:11:02.960 give you the summary uh and that's the simplest version of the model possible it doesn't get any simpler than that uh
01:11:09.000 and the the nice thing about following MVP with data binding with glimmer is that the model is unaware of the view so
01:11:14.719 you can write much cleaner model code that is not uh mixing view concerns unlike code that was written with shoes
01:11:20.640 in the past it would always mix view concerns here you don't have to worry about that so for example if um I go to
01:11:27.800 the view now I'm just using datab binding here and data binding here however there is one more one
01:11:35.040 change to the data binding if you look at the summary method that is showing Cal recalculating the summary it it's
01:11:42.199 using computed data binding now it has a new Option called computed buy so if you know that the summary will change every
01:11:48.920 time option one option two or option three changes you can declare it so you can tell that glimmer that is the that
01:11:54.960 is the the truth of the situation so what we're going to do is we're going to say computed by option one option two
01:12:01.159 option three and now glimmer has this information it knows okay every time option one option two or option three
01:12:06.320 changes we're going to update the sum summary automatically so now the code is as clean as possible it's like the model
01:12:12.760 is super clean very lightweight the view is super lightweight we just have a couple of data bindings that's it and
01:12:18.159 then for this one we just have a computed buy and that's the simplest uh version possible so this is the this is
01:12:24.480 the version I know normally use when I build real apps so that's the amazing thing about this like I uh I play drums
01:12:30.639 as a hobby uh I play drums in a rock band as well and uh I needed a metronome one time because my app on the my phone
01:12:38.120 received an update that broke the metronome so I built my own metronome and glimmer in like 10 minutes because of how awesome the syntax is like the
01:12:45.080 productivity here is actually better than even rails uh but both rails and glimmer run on Ruby and so thanks to
01:12:50.840 Ruby the the awesomeness all come comes from Ruby so um
01:12:56.159 so let's move on to the next exercise so now we're going to add a reset button where we can click the reset and will
01:13:02.120 reset all options so let me run that example so uh now I can select the
01:13:09.800 options and then I can click the reset button it resets all of them now the nice thing about data binding is the way
01:13:16.080 I would implement the reset is the reset button doesn't have to go and update each one of those checkboxes to empty
01:13:22.040 instead um let me close it go to the code
01:13:28.159 code um the model is still the same it has an option one option two option three and a summary however now we add a
01:13:34.400 new method reset and it's very simple all it does is it takes each one of the options and it sets it to false you have
01:13:40.760 to use by the way the setter method because glimmer listens to those methods it it you like Ruby uh allows meta
01:13:47.800 programming meta programming we can have a glimmer can listen to any changes that happen through any of the attributes on
01:13:53.080 a model so you need to use the syntax and not the syntax of not the instance variable syntax but it doesn't matter
01:13:59.239 once you do this uh that's all what glimmer needs to update the view now the
01:14:04.639 view is basically doing uh so in the view we just have a button it has an
01:14:10.199 onclicked listener and it triggers the reset method and that's it so once this gets triggered uh given that we have the
01:14:17.960 checkboxes data bound B directionally here they will automatically receive the update so you don't have to do anything
01:14:23.280 else all I did in the view is added this code that's it and then all and then the model does the actual Logic the model is
01:14:30.080 describing how we think about it in at the business layer so in the business layer I'm just clearing the options I'm
01:14:35.760 setting all of them to false and by doing that it it happens in The View like you already saw like uh I I can
01:14:43.400 check the checkboxes and then I click reset and it all got reset automatically
01:14:48.440 so again that's pretty amazing because then you can write code that is very minimal and you can be a lot more
01:14:54.159 productive uh and I mean the sky is the limit once this hits the web I only
01:14:59.199 started the Ruby uh the glimmer web project last year and I finished the first beta earlier this year so uh
01:15:07.040 things will be amazing in rails apps once we start using glimmer in the front end of rails apps as well uh so next
01:15:13.440 we're going to go through a more advanced example that shows table data binding uh let me just check the
01:15:22.239 time I think we have about 20 minutes so uh or 15 minutes but um so table data
01:15:29.520 binding will enable you to update let me see there you go let me run the example
01:15:36.520 it enable you to make updates to a table's data basically so this is a very
01:15:42.159 sophisticated example it's a a much closer reflection on what real apps might be so we have a form here uh that
01:15:48.960 you can fill in and when you save it'll save a contact in a contact table and
01:15:54.000 then here we have a search box and it lets you search through the table so let me add a new person here John Doe John
01:16:02.159 um let's just say host.com uh so if I don't fill all the fields and
01:16:08.639 I try to save it'll tell me validation error so I have to save I have to fill all the fields so I'm going to I just
01:16:15.719 Sav the contact boom there it is it got added to the table um and then uh here I
01:16:21.560 can I can click and type anything to filter the table There He Go Skai so it found Lisa Skai uh or I can let's just
01:16:29.880 say I want everybody in uh let's say Jordan there you go
01:16:36.120 Jordan ma multiple people yeah so anyways it's it's basically a basic
01:16:41.520 filter so this is a very sophisticated app uh desktop app uh and it it uses all
01:16:46.719 sorts of uh all different kinds of datab binding so let's get into it so it's a form table the main model here is a
01:16:53.679 contact uh you can use Ruby struct to build models it simplifies declaring
01:16:59.639 attributes and also declares an equals method for them I believe in a duplicate
01:17:04.760 method that will enable you to duplicate all attributes automatically so that's why I use the struct here um and then I
01:17:11.360 have a valid method that will validate the fields and a reset method that will reset the fields and that's it it's a very simple model uh the presenter so
01:17:19.400 here I'm using an explicit presenter so sometimes when the view is very complicated you want to use an
01:17:25.880 intermediary model between the view and the models that's basically an explicit presenter the presenter is almost like a
01:17:32.920 kind of a controller but not exactly but it's sort of like a controller uh so the presenter is like a mirror image of the
01:17:38.960 view but at the model layer so in the view like if I click here and look at
01:17:45.199 the user interface we have a form here so the form will let you enter a new contact so here we have a new contact
01:17:52.440 method on the presenter uh also The View H has a Save contact button so in the
01:17:58.199 presenter I have a Save contact method that will get triggered when the button is pressed and then the view Has a Field
01:18:04.679 uh for filtering here so here we have a method that says filter table that will
01:18:10.159 execute filtering also we have an attribute called filter value which will store the
01:18:15.520 filter value that the user types and then when filter table is executed it will filter the results and it keeps
01:18:21.639 unfiltered contacts as part of the results um and also we have yeah unfiltered
01:18:27.639 contacts and when initializing the presenter we initialize it with a set of contacts so in a real app you would load
01:18:33.480 those contacts from the database and load them into this array but if I'm not using a database for now I'm just faking
01:18:38.960 my own data so I faked a few contacts um and filter table will do some logic that will uh look through all the attributes
01:18:46.159 of the contacts we're not going to worry about the details for now but what matters is these four methods that I showed you that map to the view uh so
01:18:53.960 that way when we go to to the view now it's got the form that has all of
01:18:59.159 the fields and all of them are data bound to the presenter new contact model so the name the email the phone the city
01:19:05.840 the state uh and then the safe contact uh action button will onclicked will
01:19:11.400 trigger validation and if validation is successful it'll save the contact if not it'll show a message box uh and then
01:19:18.400 finally there's a search entry field which will let us enter a filter value that is data bound to the filter value
01:19:24.080 attribute on the presenter and after writing so this is a a datab binding Advanced feature you can use hooks so I
01:19:30.480 after write to the model I want to trigger filter table on the presenter so we're saying after the value gets
01:19:36.400 updated in the filter field we want to trigger filter table on the presenter and finally we have a table and it's
01:19:42.800 very like simple thanks to glimmer data binding we just have cell rows that are datab bound to contact and our presenter
01:19:48.199 that's it usually logic like this if I were to build this app in Java I mean right now it's about 130 lines in Java I
01:19:54.480 would take a thousand lines that would say like in in other Technologies it's a lot more cumbersome to build something
01:19:59.520 like this um but uh yeah this is it this is like the simplest way possible of
01:20:05.560 building this uh in Ruby finally Dynamic form is what I
01:20:11.440 showed you an example of uh in the slides it's basically a form that uses
01:20:17.360 content data binding and as I type these on as I toggle these on and off it
01:20:22.719 updates the form because here it's using content data binding so if I were to look through the
01:20:30.639 code the key thing about content data binding is that you have a like a control here and then the contents of
01:20:37.600 the control uh you Nest within it a Content uh basically control that will data bind
01:20:44.600 to an attribute on a Model customizable attributes on user and this this block
01:20:49.760 will get rendered every time a change happens so that's that's a dynamic part of it and it will change the content of
01:20:55.520 the form dynamically basically um and within it you can use standard data binding as well like I'm
01:21:01.639 using standard data binding for attributes on every one of the attributes that are selected by the user
01:21:06.920 so um that's pretty much it um so let's get to test one and test
01:21:16.040 two um I would say I don't think we're going to get to test two so let's just
01:21:21.480 do test one refactor the last address form example we're going to take 10 minutes for this um and after that I'll
01:21:31.440 I'll close with a few other slides and then we'll do the rest of the workshop and the hack Day event in the afternoon
01:21:56.199 by the way just like before you don't have to start from your code you can start from the test one code that you
01:22:03.080 find in the repo under section two so this is the older version wait this is the oldest version this is the newer one
01:22:10.239 that used MVC so you want to take the one that did used MVC and update it and make it use make it become MVP instead
01:22:17.280 of MVC by using data mining
01:22:33.719 uh one thing to keep in mind is uh you do want to follow incremental development when you're doing your work
01:22:39.440 for example if I start with this MVC version and I want to convert it to MVP uh I might go here and like add a text
01:22:47.199 and data bind it B directionally to let's just say address full name
01:22:55.239 uh and then I can delete this code and then I I I can run it you don't have to wait to till till you typed all the code
01:23:02.120 before you run it it's recommended to run it incrementally see it works even though I
01:23:09.239 refactored it it works so it's just uh that's one nice thing about desktop apps
01:23:14.480 is they start instantly and you can restart them as many times as you need so I typically do incremental
01:23:20.800 development um which is highly recommended
01:23:52.800 okay we're out of time so so I'm just going to go through this
01:23:58.239 quickly so let me open the test Code test one
01:24:04.800 MVP um so the model uh we have just have an address model now that has a bunch of
01:24:10.760 attributes that are dynamically declared with attribute accessor and then in the summary I'm using some Ruby Wizardry
01:24:18.360 here to uh delegate each each attribute name to the send method to call it and
01:24:25.239 get grab its value and then I I'm compacting the results meaning removing any nails and then rejecting any empty
01:24:31.639 strings and then joining them together with a comma uh so this part doesn't
01:24:36.679 matter it's just basic Ruby but uh as far as the view now it got simplified
01:24:41.880 significantly so now we have the attributes datab Bound by directionally like that so we Loop through address
01:24:48.440 attributes in a in an each iterator and for each attribute we declare a label and then we data bind the value to the
01:24:55.159 text uh and then here uh we simply take the label which is the summary label and
01:25:01.560 we datab bind it to the summary on the address and then you can do computed by to do computed data binding and pass it
01:25:07.560 the names of the attributes that are that it depends on so in this case it's the address attributes so that's the
01:25:13.920 benefit of putting all of them within a a constant here is I was able to reuse that constant in multiple places like
01:25:19.760 here here uh here and here which shortens the code significantly so this
01:25:25.119 is a much shorter version of the app the the same app now it's about 50 lines of code so um
01:25:32.480 yeah that's what that one is uh let me show you test two very quickly test two is a movie reviews app that will uh
01:25:40.040 basically lets you enter a movie review for each person uh so like you put the reviewer name a movie name and a in a
01:25:47.159 rating like a rating of eight it's from 0o to 10 and as you enter them one by
01:25:52.360 one it actually will update uh will summarize the information in a table for you and give you the average rating of
01:25:59.679 of the movie with all the reviewers so for example three people entered Matrix
01:26:05.159 uh the average ended up being 9.33 and it's Josh Sarah and Dwayne uh
01:26:11.639 so yeah different people can enter different movies so um you can look at this example after the workshop uh given
01:26:19.280 that you didn't have time for it but let me just open the code of it and run it
01:26:32.199 so basically uh I'm going to enter let's just say
01:26:37.960 Andy Matrix 10 save immediately it showed up over
01:26:43.440 here um then here I'll put uh Sarah Matrix I say nine save now it's 9.5 and
01:26:51.480 it puts it puts reviewers here Bob Terminator
01:26:58.639 2 save Etc so uh the the key thing about this app is that the model that shows up
01:27:04.560 here is different from the model that's being entered here so that's why this app is more complicated um but yeah you can uh dive
01:27:13.080 into it uh on your own time uh yeah I mean this is the code
01:27:20.239 I'll let you dive into it on your own time but again the table of code is super simple it's just a basic data
01:27:25.360 binding with computed data binding that's it um let's look
01:27:31.159 into the remaining material we only have about two minutes
01:27:39.000 left uh Advanced Data binding I'm just going to cover what we have so data binding supports converters if you need
01:27:45.159 them uh convert them on read to the from the model and right to the model you have hooks you have computer data
01:27:50.920 binding which we use today uh nested data binding index data binding key data binding so there are a lot of advanced
01:27:56.440 features in data binding which are a bit outside the scope of a basic desktop development Workshop but I just wanted
01:28:02.960 to cover them also components we we do support components of course obviously because you have to support components
01:28:09.080 for example if you want to build a name and address form you're going to build it as a component and then reuse it um
01:28:15.000 you can even have custom windows and reuse as reusable components uh sometimes they're known as apps so you
01:28:22.000 can encapsulate an entire apps a component and use it in another app if you want so there is full component
01:28:28.000 support there is scaffolding support just like rails uh you can scaffold components or you can scaffold an entire
01:28:34.280 app and it will generate uh like the whole MVC structure for you it'll assume
01:28:40.840 uh implicit controllers so just a view and model uh and it'll generate a rake file so you can package your app as a
01:28:46.560 gem and it generates a hello world version of an app and then you can customize it and do whatever you want
01:28:51.639 with it um so we're going to continue hacking uh at
01:28:57.880 the main stage Ball Room south from 3:45 to 545 so you can come there and continue uh finishing the exercises of
01:29:04.800 the workshop building an app that uses memory or a database or a flat file uh
01:29:10.320 you know the sky is the limit so hit me up there but as far as next steps I recommend checking out glimmer DSL for
01:29:16.639 swt this one runs on J Ruby and it and it has a much more uh stable toolkit it's 100% feature complete it can do
01:29:23.639 anything and you can package it as a as a native app on Mac Windows Linux like a DMG file
01:29:29.159 or exe or MSI installer Etc and you give people an installer they don't have to install Ruby or anything they just
01:29:35.080 install your app and they're ready uh glimmer DSL for WX is the newest glimmer it supports another toolkit that is
01:29:41.119 native I think this is going to start replacing libui in the future because it's more stable than libui and then
01:29:47.199 glimmer d alpha web is all of the awesomeness you saw today in rails now in the front end of rails apps so you
01:29:53.080 could do that uh um you can build this like too MVC app completely with uh like
01:29:59.440 let me jump through it there we go this is glimmer on the web so this is all this is CSS written in glimmer DSL this
01:30:06.320 is uh HTML in glimmer DSL and there's even uh JavaScript you can WR JavaScript in Ruby using a ruby to JavaScript
01:30:22.960 much for
Explore all talks recorded at RubyConf 2024
+64