Summarized using AI

Keynote: The Ruby in Zeitwerk

Xavier Noria • April 03, 2025 • São Paulo, Brazil • Keynote

In the keynote titled "The Ruby in Zeitwerk" presented by Xavier Noria at the Tropical on Rails 2025 event, the primary focus is on advanced concepts within the Ruby programming language, particularly in the context of the Zeitwerk autoloading framework. Noria begins with an acknowledgment of the significance of constants in Ruby, explaining their unique properties and behavior in class and module definitions, emphasizing that in Ruby, constants serve more than identifiers; they encapsulate class and module behaviors as well.

Key points discussed throughout the talk include:
- Understanding Constants: Noria clarifies that constants in Ruby are not types; rather, they are regular constants that can hold class or module objects. This understanding is crucial because it affects how programmers approach coding in Ruby.
- Autoloading Mechanism: The talk delves into how Zeitwerk uses a specific API to autoload classes and modules on demand. Noria describes the internal workings of autoloading, where it scans specific paths for files and dynamically loads them when needed.
- Thread Safety: Another important aspect is the thread safety of Zeitwerk. Noria explains how Ruby’s treatment of constant access ensures that autoloading remains safe across different threads, preventing race conditions that could lead to inconsistent states.
- Constant Paths and Representations: Noria discusses the representation of constant paths and the necessity of using the correct abstraction when creating mappings for constants in Ruby, highlighting how this affects the performance and correctness of the code.
- Typing in Ruby: The latter part of the talk introduces the concept of method signatures and the evolving trends towards clearer documentation of types, reflecting the need for developers to know the inputs and outputs of APIs they interact with.

The conclusion of the talk offers encouragement to new programmers, emphasizing that errors are a natural part of software development but can be minimized with good practices. Noria highlights examples from the Ruby community, where certain libraries achieve remarkably low error rates, advocating for a mindset that strives for excellence.

Overall, the session fosters a deeper understanding of Ruby’s mechanisms, particularly Zeitwerk, and instills a sense of responsibility towards writing error-free and efficient code.

Keynote: The Ruby in Zeitwerk
Xavier Noria • São Paulo, Brazil • Keynote

Date: April 03, 2025
Published: April 29, 2025
Announced: October 24, 2024

Everlasting student · Rails Core · Zeitwerk · Freelance · Life lover

Tropical on Rails 2025

00:00:16.080 thank you all right so
00:00:22.199 um yes an honor to be here it's my first time in South America and I am very
00:00:29.599 excited and very very thankful to have been invited to talk in this amazing conference and also there's a nice
00:00:36.880 personal toach in in which this is the land of Raphael which is cool really
00:00:43.600 cool excited to be here um I would like to thank the RA
00:00:51.280 foundation because as part of their uh partnership ship with tropical on rails
00:00:56.800 they assisted me h in my in my trip to Brazil so thanks the rails foundation
00:01:02.640 for that all right so uh in this talk we are going to have uh
00:01:10.560 an overview of a few things related to Ruby that maybe are not so common in our
00:01:16.720 day-to-day programming right uh I am going to showcase them in the
00:01:22.560 context of Zyberg but that's just like an excuse to talk about a few things Ruby all right so this uh talk has se
00:01:30.560 seven sections fasten your belts uh the first five of them are going to be technical
00:01:37.040 and then the the last two of them not so much all right so uh in the context of
00:01:45.040 cyber we are going to talk about constants and to make sure that we are all on the same page about some
00:01:50.960 fundamental things about constants we are going to do uh a very quick recap uh
00:01:57.200 talking about things that are important to have in mind all right so the first thing is that the class of module
00:02:02.640 keywords store classes of modules in constants which is something very unique
00:02:07.680 in the Ruby programming language all right so when we do class C both things are more or less
00:02:14.879 equivalent all right so that thing is creating a class object and storing the
00:02:20.400 class object in a C constant same with a module that thing is um creating a
00:02:26.480 module object and storing the module object in an M constant and that M constant is a regular constant it has
00:02:32.879 nothing special about it okay now the next thing is that Ruby does not have
00:02:38.560 syntax for types and uh if you come from a different programming language that
00:02:43.680 has types um this is important this distinction is important to have in mind
00:02:49.280 when you work in Ruby so for instance here we have project.find something
00:02:56.200 right so the key observation is that project is not a type project is a
00:03:01.440 regular constant okay so here project is a constant that that's an expression
00:03:07.440 that evaluates to a class object okay and that object happens to respond
00:03:12.959 to the fine method that is what is working uh for real in that slide okay so every time you see something like
00:03:20.080 that forget about types forget about a bake concept of classes that's a regular
00:03:25.920 constant okay now another thing that is very unique in the Ruby programming language that is that constants belong
00:03:33.599 literally to class and module objects okay so every class of module object in
00:03:38.720 your program has like a hash table you could think that stores constant names
00:03:45.280 to the values that are that are associated with them okay this is literally happening in your in your in
00:03:52.000 your in your Ruby program all right so they are collections of methods and collections of constants as well and
00:03:57.599 this is very unique to Ruby in the case of top level constants uh they are not except they are not exceptional in that
00:04:04.239 sense where they are stored in object okay so here we have the first one is a
00:04:10.560 top level constant Ruby engine that's a string the second one we can think is a
00:04:15.680 class that's a top level constant as well okay the the third one is the constant
00:04:23.320 pi that is stored in the math module so the first two ones are stored in object
00:04:29.280 the the last one is stored in the math module all
00:04:34.360 right you can you we can introspect this okay so it it could be surprising to to
00:04:40.880 see that when you boot the interpreter the top level has more than 100 constants defined okay and you can you
00:04:47.840 can introspect them in the second in the second um prompt we have that okay so if
00:04:55.280 you list the constants in object there's there's API related to constants in cluster modules you will see things like
00:05:01.759 file kernel those are symbols because is the name of the constants constant names are symbol okay so you see it's file
00:05:10.080 kernel string what is that isn't string like a class no when you write a string
00:05:16.720 in in your source code that's a constant okay and you can see here that's a constant stored in object okay what
00:05:23.120 happens is that when you put the interpreter the interpreter builds the string class it's it assigns the string
00:05:29.440 class to a to a constant call it string you know and you have it okay but you
00:05:34.960 can see here that they are constants in object all right
00:05:40.039 enough so with that we can pass to an overview of how Zyberg autoloads okay
00:05:47.680 because um I believe that it's it's better to uh program when you know your
00:05:53.680 tools okay that's better than having your tools to be black boxes so so if
00:05:58.960 you at least understand more or less what the tool is doing on your behalf uh that's more transparent we we could say
00:06:06.240 okay so I'm going to just explain very briefly how to how it autoloads it does more things okay we we can eager load we
00:06:13.120 can reload and we have different features okay but the key the key aspect is autoloading so how do we
00:06:20.440 autoload uh there's this API in Ruby this is not zy this is this comes with
00:06:25.919 Ruby module autoload okay module autoload allows you to define constants to be allow loaded on demand so this is
00:06:34.160 an example from active record it it is not pervatim like this but conceptually is like this okay and we say module
00:06:41.759 active record okay we say autoload base uh with that second argument what what
00:06:47.840 this is doing is by now I am defining the top level
00:06:53.199 module active record okay and I am setting this thing because whenever I
00:06:58.960 want to refer to active record column column base if the constant is not
00:07:04.240 defined at that point this is telling the interpreter please go do a require
00:07:10.560 on the second argument And in the happy path that require as a
00:07:16.160 side effect has defined it active record colon column base the program resumes
00:07:21.199 and you know you you got the the constant outload by Ruby all
00:07:26.680 right okay so uh the main idea behind Zyberg is to it's Zyber is fully fully
00:07:35.120 based on this API uh we start with configuration and the
00:07:40.560 most important one is where is your code and in rails we call that the autoload paths okay in zyber that's called it the
00:07:48.400 root directories but we have different nomenclature in rails so we have the
00:07:54.080 autoload paths the those are a models a controllers you know a helpers and uh
00:08:01.280 the convention is that those paths are storing files uh and they represent the
00:08:06.639 top level name place so a models userb is expected to define top level user
00:08:12.720 right so we have that configuration in place and we do the following zyber
00:08:18.400 before you start using your code scans the autoload paths one level okay it
00:08:24.319 scans it leads the directories now for each one of the files that you
00:08:31.360 have there you get uh you get the file the base namealized with the extension
00:08:37.279 removed so from user lowercase dollarb we remove the extension we come we get
00:08:43.200 user with U capital with capital U right and we define autoload because now we
00:08:48.880 have the name of the constant that is expected to to be defined in that file
00:08:54.399 and we have the the the file that is supposed to define that constant so we have the necessary information to put an
00:09:01.279 autoload in this case in object because it's a top level constant okay so we set
00:09:06.480 we we scan we set autoloads done and it does nothing else it waits and you are
00:09:12.720 ready to start referring to any constant at any point in your project no matter
00:09:17.760 how deep okay now if you refer to a name space then we do the same thing for that
00:09:23.680 name space so so if you refer to a name space then there's code that intercepts that scans camelize set autoloads in
00:09:32.399 that name space and you you know the autoloader you know scans sub trees of
00:09:38.320 your project tree uh as they are used so this is the basic the basic idea a scan
00:09:44.480 camelize set autoload and let the user uh refer to things as they are so these
00:09:51.279 are examples okay we have here uh userb permission.b those are models you know
00:09:57.120 in this example controller users controller and we have permissions controller in an admin name space so
00:10:04.120 uh Zyberg on your behalf is going to do this you could you could write this by
00:10:10.080 hand okay but this automated for you is doing dynamically so we set an autoload for user an autoload for permission and
00:10:16.240 autoload for user controller that's the top level okay wait it does nothing else
00:10:21.600 no if you do not refer to admin it's going it's going to ignore admin but if
00:10:27.040 you refer to admin then it's going to create an admin module on your behalf
00:10:32.440 automatically and we do this as we saw in the previous slides we can define a
00:10:37.519 module and assign to the admin constant that is done dynamically so it's done with con cons get not like this but it's
00:10:44.079 the same idea and now that we have the admin module created we can set an
00:10:49.680 autoload for permissions controller and repeat that's the basic idea of Zybre okay if you understand
00:10:56.399 this you have Zyber in main you know you know how it works then from here to the gem there's some work to do but but if
00:11:04.079 if you understand this uh yes uh you have a good model of how it
00:11:09.560 works all right so that's how it works and Zyber has an a very fundamental
00:11:14.959 property which is that it's thread safe um let's talk a little bit i have
00:11:21.279 never explained it why Zyber is in thread safe um so let's start with this with
00:11:29.120 this observation class of module definitions are not atomic in Ruby okay
00:11:35.200 so let's imagine we have this model we have um a user class has many post
00:11:43.120 has a validation and has um class method for a special uh user right the root
00:11:50.279 user okay so this does not happen in one
00:11:58.360 shot this happens step by step first the class is defined in the body in the body
00:12:04.480 definition ruby sets self to the class object so at the start of the body
00:12:10.000 definition we have already the user class and it has no associations it has no validations it has no methods but it
00:12:17.360 exists and you can use it okay so when we say that in Ruby class and modules
00:12:23.600 are open sometimes that comes with the image of uh I can open this class in a
00:12:29.600 different file or I can I can monkey patch that gem to fix a back you know or
00:12:35.279 something like that you know it's way easier than that here we are using this property so the class is defined and we
00:12:43.360 are modifying the class because the class is open so in the in the first line in the body we But we are modifying
00:12:50.399 the the class we are adding an association then we are adding validation at that point there's no method except for the ones inherited
00:12:57.839 okay but then we are defining because the class is open all right we are seeing here the class is open and this
00:13:04.399 this is happening step by step indeed those that has many is a method all
00:13:09.920 right it's a method inherited so we are using the class all right now let's consider this we have
00:13:17.200 two threads okay in one let's imagine this is not rails okay this imagine we are using active record directly outside
00:13:24.000 of rails so we have to do requires manually so the first thread is doing wants to use the user it requires the
00:13:31.440 file and then the second thread also wants to access the this special
00:13:38.760 user all right so what happens here this is not atomic so perhaps after the hus
00:13:46.639 many uh association has been uh defined maybe we have a context switch
00:13:53.279 that's possible maybe we have a context switch so what happens let's imagine
00:13:58.639 that the first thread okay let's imagine that the first thread gets to run first
00:14:05.199 it requires the file and then after the h has many there's a context switch and we go to the second
00:14:11.399 thread in that case what happens the user constant already exists
00:14:16.880 the class already exists however we have not reached the point in which the method was defined so we are going to
00:14:23.279 have a no method error in that case all right so this is possible maybe because
00:14:28.880 if the context switch does not happen we don't see the error so this is a race condition and you know is a is is a is a
00:14:36.399 Heisenber back that you know is the worst the worst type of backs because it happens randomly all right so you know
00:14:43.040 and the problem here is that this pro this program is incorrect you should have a require in the second thread okay
00:14:50.480 and precisely this is the one of the motivations for Zyber to not have to think about these things okay all right
00:14:58.680 so now we are in a situation in which we are doing autoloads and I am claiming that this is
00:15:06.000 user that this is thread safe and this is what our race applications do every day
00:15:11.959 okay how is how is it possible may because if the if if we can think if the
00:15:18.160 first thread goes and autoloads user by the same
00:15:24.160 principle we could have a context switch and go to the second thread but what if
00:15:29.600 the method has not been defined is is it the same situation well it it is but we
00:15:34.959 have help from the interpreter because the interpreter synchronizes constant
00:15:40.600 access so basically if there's a context switch and you get
00:15:46.360 here the interpreter says "Ah user yes I have the constant but there's an
00:15:52.320 autoload for this constant happening right now that has not finished." So the
00:15:57.360 interpreter is going to block here it's going to pass and it's going to
00:16:02.560 wait for the autoload that is running to finish once that is finished you
00:16:09.240 resume so the previous autoloading race was not thread safe by itself it was
00:16:15.360 thanks to help from the framework there was logs and stuff doing so you could uh
00:16:20.800 you could autoload in a thread safe manner in in two requests for instance in two jobs okay but if you had an
00:16:29.120 script uh executed by by red runner and you had multi threads there the the
00:16:35.040 autoloading was not thread safe okay in order to make that thread safe you you you would need to add stuff from the
00:16:40.959 framework to put that that coordination in you know in um in place but with
00:16:47.680 Zyberg we do not need this and we can use Zybre in in regular code without any
00:16:52.880 locking or anything thanks fundamentally to this property of
00:16:58.040 Ruby all right uh in Trafford Ruby this is not the case yet okay trafford Ruby
00:17:04.319 uh doesn't have this synchronization in mind uh as uh implemented I I would say
00:17:09.760 okay but uh yeah that's the only caveat okay okay in if you are working with traffic Ruby you would need to eager
00:17:15.839 load for instance or work with only one one single you can still autoload but but you cannot do it in a in a uh with
00:17:23.039 multiple threads all right but yes in in J Ruby and in C Ruby this is working all right now let's go with
00:17:30.799 another topic which is how to generate constant paths so this is the most
00:17:35.840 important data in Zyberg the Cref the constant reference okay we said
00:17:41.360 constants belong belong to class of module objects so the most uh the most
00:17:50.120 um the abstraction with the most fidelity that we can get to represent that is the this pair the pair mod C
00:17:57.840 name which means this this constant name okay this constant is stored in this
00:18:03.799 module okay module would be a class or module okay C name would be a symbol
00:18:10.400 this is the most important data okay since uh The super class of the class
00:18:15.520 class is module okay so uh we can simplify this and say just this is module all right
00:18:22.679 so mod is a short variable that is ideatic to use to be used when you want
00:18:29.360 to hold class and module objects without distinction all right okay this is the most important
00:18:36.559 data so let's see uh some examples okay top level user how do we represent top
00:18:43.360 level user this way so it's a top level constant therefore belongs to object so
00:18:49.360 the first coordinate would be object the second coordinate would be the symbol user right math pi pi that is stored in
00:18:58.320 the math module let's imagine API v1 inventory item okay so item would be the
00:19:05.440 name of the constant and it would be stored in the module API v1 inventory
00:19:10.960 all right but don't don't get confused by this notation okay because what we
00:19:17.919 have here is one object let's forget about the columns and everything is one one object okay so we have one object
00:19:26.320 and a symbol all right so the exercise that we want to do is to write um a cpath method that
00:19:35.360 returns a string with a constant path for a given constant reference okay so
00:19:42.480 for instance the C path of this pair object user could be user top level
00:19:50.320 thing okay we we want the constant path of a string okay in top level we do not
00:19:55.520 put object you know before that so this is the one that we want so we can start
00:20:01.120 writing our method okay c path well if object if the if the first argument is
00:20:07.720 object then return the symbol as a string done easy okay we have the first
00:20:14.720 the first part done how about the other ones okay cath of math pi would be that
00:20:21.520 constant path c path of this thing would be that constant path all right
00:20:27.200 intuitive right okay so we have the last segment
00:20:33.919 we have the last segment as an argument we have pi and we have item so that one is easy what about the the previous the
00:20:40.320 prefix okay how can how can we generate that we we can do it with module name
00:20:46.880 this is a this API in modules all right so if you ask math for the name so math
00:20:54.240 here is is a constant evaluates to a module object that module object responds to name and name returns the
00:21:01.120 string math okay and this is something uh uh stored by the interpreter when the
00:21:07.440 module is defined the interpreter defines an attribute on that module that says your permanent name is math okay
00:21:15.039 that's set by the interpreter there's no public API to change that once set is done okay same for the other one all
00:21:22.000 right so well the else clause could look like this all right we have mod we ask
00:21:28.559 for the name that is going to give us the the prefix and then we f we have colon colon we interpolate the symbol we
00:21:36.320 get uh the constant path that we want and this works but we can improve a
00:21:41.919 couple of things here the first one is that symbol name since this this this um
00:21:48.400 um method exists since Ruby 3 symbol name is a special a special um method in
00:21:55.799 symbol that uh is more efficient because here we are creating a string for the
00:22:02.799 CNAME every time we are we get called so Came toS is get is is is uh creating a
00:22:08.559 string ano string ano string okay same with interpolation in the else clause so
00:22:14.240 symbol name is specialized to be more uh performant if you can leverage this so
00:22:20.480 it returns the string that we expect but that one is frozen and that one is always the same
00:22:25.960 object so by doing this we allocate less strings which is something that is uh
00:22:32.080 always you know good good to have allocate as as as less objects as
00:22:37.360 possible all right especially in in gems like this which are like uh framework
00:22:43.520 level let's say you want to be you want to you want to put the less possible
00:22:49.200 overhead on on the code that is using you right so in this I go the extra mile to
00:22:57.360 do things to be as performant as possible maybe in my regular code I wouldn't go so far but in this I want to
00:23:04.000 make sure I am as performant as possible so less objects now we have it doesn't
00:23:09.200 it doesn't it's not apparent but in mod name we have a problem we have a problem
00:23:14.840 because the name the name method can be overridden okay I have here two real
00:23:21.559 examples so uh for some reason this identity cipher name is
00:23:27.559 identity this this thing they are classes okay this uh cipher thing and this text thing they are classes but for
00:23:34.559 some reason in these gems these things are overridden it doesn't even return a
00:23:40.799 string it gives you a symbol right so we have a problem because in this
00:23:46.480 implementation this is this is this is going to work generally but the moment you open source this and you know a v
00:23:53.840 variety of projects are using it you're going to find this so your c path is
00:24:01.080 broken so if we cannot rely on the name what can we do something that normally
00:24:07.760 you do not use in your day-to-day which is to use module instant method for
00:24:14.279 instance instant method returns a class maybe you have seen this class on bone method and maybe you have seen on method
00:24:21.360 why do why do I want an unbone method okay an unone method so you know that in
00:24:26.960 methods you get a self all right that self is is set
00:24:32.080 by the interpreter for you implicitly okay you know in Python for instance the
00:24:37.840 the self is explicit but still interpreter is is you know in the general case interpreter is setting that
00:24:43.600 first argument on your behalf okay so you have a self anode method is a method
00:24:49.919 that represents the implementation but that is spending to have a self in context it's like a
00:24:56.880 template let's say okay all right the interesting observation is that you are
00:25:02.559 this is not subject to inheritance or to overrides if you if you fetch an
00:25:08.080 instance method it's the original one so we can fetch a model module instant
00:25:13.679 method the meth the name method and that is the original one i I store that in a
00:25:19.200 constant and then I have this helper real mod name that for a given mod okay
00:25:25.600 with bindal by bindal is the API to set the self on that method all right so
00:25:30.799 with this thing we are sure that no matter if the name method is overridden
00:25:36.559 we can fetch the original module name and now we have a good one this one is
00:25:42.279 inside all right now constant references as hash
00:25:48.320 keys new section so as I said this is the most important data in cyborg the constant
00:25:54.880 reference uh I need to uh use constant references
00:26:04.799 uh in some structures in some hash tables and it would be very natural for me to use the constant reference as a
00:26:12.559 key for instance I have a structure that that remembers the autoloads that the
00:26:18.240 the gem has set so this is saying I I I have set an autoload on the user constant okay and I am using at the
00:26:25.840 beginning of of the project I was using constant paths for this but the constant
00:26:32.159 path is not a good a good choice that's not that's not the right abstraction why because a constant path
00:26:40.480 does not uniquely represent a constant indeed it can be the case that the
00:26:46.400 constant has no constant path so you can have constants with no constant path
00:26:51.760 referencing to them with one constant path which is the common theme but you can have more than one constant path and
00:26:58.400 you can have infinite constant path let me show you examples okay this one this module is anonymous it has no it has no
00:27:06.760 constant but again we store constants in modules we do not store constants in
00:27:13.039 constants we store constants in modules so here we are storing the constant in the module right which constant path
00:27:19.600 gives gives me x non there's no constant path that's that's a pure constant is
00:27:26.080 stored in a in a module object that's the right abstraction the module and the constant name right here I have two
00:27:33.840 constant paths okay so there's a module a class the class includes the module and therefore the way lookup works m
00:27:42.400 means that I can refer to that x constant as m x and also as c x so it's
00:27:49.440 two constant paths representing referring to the same one
00:27:55.080 constant now three so we have x and then
00:28:00.760 remember again the same top the same the same theme again the m is not a type is
00:28:07.279 a constant that stores a module object therefore I can do a constant assignment and store that object in other two
00:28:14.320 places so we have m and I say okay I want to store this thing in q and also
00:28:19.760 in p all right what does that mean that mx px and qx is the same thing it's the
00:28:26.960 same object there's no there's no even a lookup argument here it's the same object so we have three constant paths
00:28:36.720 that point to the same thing right now let's let's go with
00:28:43.080 self-referential a little bit self-referential because we have module m x1 m stores the module
00:28:51.640 itself hey this is funny so MX all right
00:28:57.039 good one the normal one but then in the second line we have
00:29:02.760 M what is happening there what is happening is the following we have the top level
00:29:16.159 the module is the module object do you have an X constant yes I have one there
00:29:22.880 you go and you can repeat this as much as you want so m MM we can we can have
00:29:29.600 theoretically an infinite number of constant paths pointing to the same constant
00:29:36.159 and if you think that this is a contrive examples I am happy to say that it is not because this is what happens with
00:29:43.080 object because object the constant object is a top level constant where top
00:29:48.880 level constants are stored in object right so uh let's imagine we have
00:29:55.039 a string which is a top level constant uh so uh object do you have a constant
00:30:01.520 call it a string yes tople constants are stored in object so the first one is
00:30:06.559 kind of normal but what about the the other one well object you you have that's a that's
00:30:14.399 a class object do you have a constant call it object yes it's a top level one so I have it all right so do you have
00:30:22.399 and do you have a string constant yes I have one awesome so you can put as many objects as you want all right again an
00:30:29.520 infinite number of constant paths pointing to the same constant so the the
00:30:34.720 the the coral area of of all this is that the constant path is not a good abstraction for a constant the good
00:30:41.760 abstraction is the module that that stores the constant and the constant name done that's it all right so let's
00:30:49.960 think for a moment all right moment of rest
00:30:56.279 okay so from here I would like to go to here this is the right thing i want to
00:31:03.840 map the constant reference to the file all right so I did this refactor and
00:31:11.120 ship it in 2020 all right as part of Zyber 2.5 and in a moment I got a
00:31:21.640 ticket what the so the problem the problem is that this project
00:31:28.480 has a class that has the hash method redefining it we were talking about redefining name
00:31:36.080 before which kind is kind of common but redefining hash so it it it is not only
00:31:41.679 redefining hash but it's changing the signature because the hash method that you inherit from object has no arguments
00:31:47.600 and this one has one argument it's is not using parenthesis but it is one argument okay and and this has nothing
00:31:54.720 to do with hash codes because this is a special method let me explain what is happening here and why it broke the
00:32:02.360 release so um yes uh I I I ship it immediately a revert of that you know so
00:32:10.000 I have to say that that I have to say that the reporter was very kind and was
00:32:15.200 like look I I acknowledge that this is a little bit weird perhaps okay because
00:32:20.480 it's kind of breaking a contract but uh and and and and he said uh I would be
00:32:26.559 willing to refactor my code but they was like wait a moment technically this is possible technically Ruby does not
00:32:34.000 require that your class module objects are hashable so it is my duty to revert
00:32:39.840 this and think about maybe a different solution so that is what I did i revert immediately and
00:32:47.360 uh that part of the release u let me explain a little bit in case you are not familiar with hash tables what's
00:32:53.919 happening here okay so from object we inherit the hash method and the eql
00:32:59.840 question mark method those methods are directly related to hash tables okay to
00:33:04.880 to it allow it this these methods allow you to use the object implementing those
00:33:10.960 methods as a hash key all Right so the first one generates a hash code uh and
00:33:16.880 the second one says whether two objects represent the same hash key by default
00:33:24.159 uh the implementation that we inherit from object says
00:33:29.440 uh two objects represent the same hash key if they are the same object symbol if you have the same object ID you are
00:33:35.600 the same the same key otherwise you have different keys okay um so um to explain how this works
00:33:44.240 I I thought about the metaphor because when you do a a key look up in a high table and there's kind of two steps
00:33:51.279 there's kind of two lookups happening inside the structure okay there's a f
00:33:56.720 the first one is a fast one and then there's a linear one okay the details of
00:34:02.399 the implementation may may may vary but conceptually this is what is what is happening so the the metaphor is like
00:34:09.040 like the the the key of your room in the hotel okay so uh when when you go to the
00:34:16.639 reception when you enter the hotel and they give you a key
00:34:22.040 okay uh the the rooms in the hotel are organized by clusters are organized okay
00:34:27.919 in the in a high table things are organized also like by clusters they are called normally buckets but it's the
00:34:34.079 same thing okay you have the rooms organized by floor in a hotel so what do
00:34:39.919 you do something very fast you first locate the cluster you first locate the floor and how you do that normally you
00:34:46.960 say we oh the leftmost digit is a four four floor that's quick and you get the
00:34:52.879 elevator to the fourth floor so that's fast and that's the role of the hash code the hash code is an integer okay
00:34:59.119 and it and and there's a mathematical operation that is super fast and gives you to the bucket it leads you to the
00:35:04.400 bucket okay and then when you go to the fourth floor what do you do you you scan
00:35:10.640 okay maybe well we can we can uh ignore signs or everything but you know you you
00:35:16.400 you say okay is this my number no is this my number no that's a linear search and that's what EQL does it is telling
00:35:24.320 you if the both both things are the same key okay so uh what is happening is that
00:35:34.560 uh in a constant reference like this if we compute hash the the array class
00:35:39.599 overrites hash and eql and basically the hash code is compute
00:35:45.599 uh is a function of the hash code of the items to compute the hash code of the
00:35:51.839 first of the first item invokes the hash method on the item and that's the one
00:35:57.040 that it was overridden in that project and it is worse because I cannot this
00:36:04.000 this is out of my control this is the implementation of array so I can I cannot do anything here all right so I
00:36:11.440 reverted and I was like oh man I I am not feeling comfortable with the
00:36:17.599 representation of constant references as constant paths but is the best that I I can I can do right now and I am I know
00:36:25.119 that these things are not hashable so I don't know I parked the problem okay I
00:36:31.119 parketed the problem with time i introduced a proper
00:36:36.160 class to represent constant references at the beginning uh it was everything was simple let's say let's keep it
00:36:42.400 simple at the beginning and then when you when you realize which are the good abstractions and how you want to model
00:36:48.640 your code then you maybe you know evolve the implementation so at some point I introduced the surref the surref is
00:36:55.599 basically the pair okay with the mod and the C name and it has API it has API that is very helpful to express things
00:37:02.800 in a in a in a nice way like I can set values I can set autoloads you know and that's that uh API abstracts me from the
00:37:11.040 uh constants API in modules all right so I did this just to
00:37:17.119 improve the modeling in the gem but um
00:37:22.560 last Christmas or something like that I was like "Oh wait a minute that problem
00:37:28.800 that I had in 2020 with this now I have control about
00:37:34.160 the hash." So if I want to store a Cref in a hash table I can do that i I just need
00:37:41.760 to define EQL question mark and hash all right not like that but with the same
00:37:47.760 trick that we did before now we have a technique to to jump over that that uh
00:37:55.359 overrides so I can go with the same technique i can go fetch the original
00:38:01.200 hash method you know and use it this works this works i was very happy so I I
00:38:08.560 was able to um store C refs in hash tables but I did
00:38:15.200 not ship this yet because then after working this happens you model something
00:38:21.599 and when you find the right the right abstraction sometimes all of a sudden
00:38:26.880 you see things that you couldn't see before or things are possible now that were not before and you have maybe
00:38:32.640 elevated yourself and now you see a little bit better and you you find a solution for something you
00:38:38.000 that happens now there's another API that
00:38:43.040 normally we do not use which is hash compare by identity this is a weird
00:38:49.119 method okay but this talk is about these little
00:38:54.160 unknown methods that are that found their place in zyber what is that
00:39:00.560 exactly what we need compare by identity
00:39:06.880 uh tells when when you when you create a hash and you say compare by identity
00:39:12.800 basically that is ignore the hash method ignore the EQL of
00:39:18.960 the key treat them as if they were objects so it ignores
00:39:25.240 everything and uh if you think about that module class
00:39:31.119 of module objects they are the same thing if they are the same object okay there's no way that the same class or
00:39:37.359 module object is represented into different objects they have this property they are unique since they are
00:39:43.200 unique we can use the original uh implementation in object or the same semantics let's
00:39:49.720 say so that's another way to um solve this problem because now in
00:39:57.839 this case I have a two-level structure two-level hash table that indeed models
00:40:03.520 exactly what is happening in Ruby so I can have for each class and module object the hash table of its
00:40:10.359 constants the point is that here no I can use the module as a hash key
00:40:18.880 i couldn't do this before but now I can why because I have hash compare by
00:40:24.599 identity so this has been another way to work around that so during Christmas and
00:40:31.200 start of January I did these two implementations i had two versions of the gem uh working with these two
00:40:36.680 implementations and then I compare and the second one is way faster okay uh
00:40:42.560 it's it's uh faster than the one with uh with CREF and faster than the original
00:40:48.160 one that I wanted to replace with constant maps so I was very happy very relieved
00:40:55.359 to solve this uh this has uh I maybe the change lock has internal improvements so
00:41:02.079 no nobody's seeing this but I I sleep a little bit better now with this this uh
00:41:07.920 what shipped in February and yes I can have uh cerefs high
00:41:15.720 tables all right we're finishing
00:41:22.839 so a little section about typing typing yes all right
00:41:29.079 so all methods in Zyborg have a type signature all
00:41:35.319 right and um when we think about signatures we normally think about
00:41:41.119 static typing do I like types do I not like types that kind of thing right but
00:41:46.640 my observation is that no matter what no matter if your language is that is
00:41:51.839 statically typed is dynamically typed when you use an API you have to
00:41:57.680 know what you can pass and what what you are going to get back there's no way there's no way around that you get an
00:42:04.560 object you need to know which methods I can I can can it be nil which is the API
00:42:10.240 for this object can I pass a string or or has to be a path name if the name of
00:42:16.800 the argument is file is it is it a file name is it is the file object what what it is i have to know otherwise I cannot
00:42:23.839 program right so to me it's fundamental that we
00:42:30.440 document the types or the duct typing interface or whatever Rubyatic thing is
00:42:37.599 that allows you to program against an API all right so in Zyborg if you open the source code everything is documented
00:42:44.960 public and private because the point is not external documentation the point is that you open the source code and you
00:42:50.800 have context to understand the source code and to maintain the the source code so it is both for users in the case of
00:42:58.400 cyber in the indeed there's no formal API like uh airdoc API generated the API
00:43:06.000 is described in English in the readmi all right I have the types in the rhymi
00:43:11.040 okay so here you can pass uh an string a path name you can pass an array of path
00:43:17.119 names or strings whatever it's it's it's done in English all right but internally this is for internal purposes okay to
00:43:23.599 understand the code and to maintain the goal to help with that so for instance you can see this okay something like
00:43:29.119 that okay and this at the at the at the beginning
00:43:34.560 this has this over the over the years in the project I have used different notations for that and nowadays I have
00:43:42.480 this notation that is taking shape in the community and if you I don't know if
00:43:49.520 you were paying attention there was a slide in the keynote of benicious that was using this notation as well and the
00:43:56.560 thing is that this is taking shape all right uh this is taking shape as an as a
00:44:01.680 as a as a way to represent signatures in Ruby there's tooling already supporting
00:44:07.119 this uh in RBS you can do this in Sorbet you can do this now and
00:44:14.480 um BS code for instance has syntax highlighting nowadays for this so if if you have this signature defined in your
00:44:21.680 source code uh VS Code is going to syntax highlight it okay is not going to
00:44:27.200 be treated as like a a generic uh comment which is very nice so my my
00:44:32.960 message here is this is I believe this is coming we are going to start seeing
00:44:39.200 more of this in the Rails team we have also the idea to at least test the
00:44:44.640 waters a little bit and see if we like you know doing this in the source code we are going to do this as
00:44:51.000 well so this is coming and I believe this is a good thing
00:44:57.200 and I have a last section okay and this like se this last section uh is for
00:45:04.160 everybody but especially for people that are starting programming so um how many of you have
00:45:13.119 been programming for less than one or two years for instance right yeah
00:45:19.800 okay all right so as I said for everybody in the room but especially for
00:45:26.000 you okay section special for you is a section about software
00:45:31.720 errors so the thing is in software we are extremely
00:45:40.079 extremely extremely used to have bugs so every single release of anything
00:45:46.640 has always an item that says bug fixes the most simple iPhone application that
00:45:52.720 you can think there's a new release chances are that there's an item that says back fixes in companies your
00:46:00.480 exception tracker is normally very be very busy all right and if we are new to
00:46:06.640 programming and we are receiving all these inputs maybe we get to the conclusion
00:46:14.240 that that this is the way it is there's no way around that you know and that you have to just
00:46:21.040 accept it okay so in the 90s in in a
00:46:26.079 forum in Usenet for those of you that know what Usenet was I saw this sentence tech which is a
00:46:35.760 type setting system written by Donald Nuth used for scientific papers and math
00:46:41.839 extremely complex software the sentence was T is considered to be BF free that
00:46:46.880 was in the 90s okay and that was a revelation for me and an inspiration
00:46:52.200 because I was in this mindset of yes box uh I don't know there's no way around
00:46:57.680 that and all of a sudden I see oh this is
00:47:03.880 possible and this is the message that they want to pass to you this is
00:47:09.119 possible so in our case Zyber has has had about
00:47:15.760 60 releases is the race autoloader since version six six is being used by 700
00:47:22.640 gems more or less it has half a million daily downloads and more than 4 uh 100 million
00:47:30.880 uh so sorry yes more uh 400 million uh total loads this includes CI and local
00:47:38.480 installations and deployments and and whatnot so the the number is not that important but you could say it's used
00:47:45.760 yeah you can all right so Zyber routinely has no issues
00:47:54.520 routinally all right sometimes you get one okay i I just show one with a hash
00:48:00.880 thing okay occasionally you have one but the the the the norm the norm is that
00:48:06.160 there's no issues there's no known errors it doesn't mean it's bug free but there's no known errors
00:48:13.880 okay and we have more examples like this in the Ruby community for instance um
00:48:20.160 SQL from Jeremy Evans buckfree no known errors let's say that
00:48:25.599 way action cable from Vladimir dement no no
00:48:30.800 errors as well empty issue tracker okay for me the the good issue tracker is
00:48:35.839 empty has it has to be empty all right your exception tracker should be empty
00:48:41.440 so this is not something easy especially if you are on tight deadlines especially
00:48:48.720 if you do not have good coverage especially if you are inheriting a code base that is difficult to maintain i am
00:48:55.280 not saying this is easy i am saying this is possible so that's the message for you for those of you that are starting
00:49:02.800 the message will be do not take box for granted we I I have shown you uh a few
00:49:10.960 models a few references that might inspire you all right do not take them
00:49:16.079 for granted so my message is that uh I encourage you to think
00:49:22.680 independently and to pursue your own excellence and your own standards that's
00:49:28.559 it all right thank you
Explore all talks recorded at Tropical on Rails 2025
+10