ERNIE MILLER: Good afternoon everybody.
AUDIENCE: Good afternoon.
E.M.: How's it going?
AUDIENCE: It's going good! Hi Ernie!
E.M.: Hi. My name's Ernie Miller. I work for
a company called Appriss in Louisville, Kentucky. Please ask
me about them later so that I can justify
expensing the trip.
AUDIENCE: Whoo!
[applause]
E.M.: If I could ask you all to do
me a favor, as it turns out, most of
my fellow co-workers that had come out to RailsConf
have had to catch a flight to go back
home, and so they're missing my talk. And so
what I'd like you to do is to Tweet
very enthusiastically about the talk, regardless of if it's
very good, so that, you know, for at least
until it goes on video, they're gonna think they
missed the talk of a lifetime.
So, RailsConf, huh? Have you guys had a good
times?
AUDIENCE: Whoo!
[applause]
E.M.: Yeah? Yeah.
I've had a ball, too. I've had a ball,
too.
So.
AUDIENCE: Ah, yeah!
E.M.: I am not.
AUDIENCE: [indecipherable - 00:01:10]
E.M.: Yes. Yes it was.
So, I am not a member of Ruby core.
I am not a member of Rails core. However,
last month, this game came out, and I'm an
avid gamer, and I'd like to happily report that
I am very frequently a member of Damage core,
at this point.
Unfortunately, membership only lasts for about fifteen seconds at
a time. Then I get booted again. So, so
there is that. But, you know, I'm thinking, you
know, it's, it's moving up in the world. I'm
a member of something core.
So, so, it's come to my attention recently that
I have been giving a fair number of talks
that usually involve me ranting about something, and I
think I'm becoming a bit of a curmudgeon.
[applause]
So, you're not supposed to clap for that.
[laughter]
OK. So, one of the things that I want
to start off by doing is saying that I
recognize that most of us would not be in
this room if it weren't for Rails, and that
we really owe a lot of our opportunity to
get paid for doing Ruby, I know I do
anyway, to Rails's existence. So, first off, I'd like
to get a hand for Rails and how awesome
it is and how it enables us to do
everything.
[applause]
OK. OK. Nope. Nope. That's enough. Nope. No more.
We're done celebrating.
[laughter]
Just that much celebration. Nothing more.
[laughter]
OK. All right. Stop the part.
So. That. That gets me every time.
So, that being the case, I, I have to
say, a lot of us have been hitting brick
walls. How many of you have been building something
relatively complex with Rails, and you sort of feel
like you've, you've gone up against a brick wall.
You can't make anymore forward progress and you feel
like you've been kind of, you know, so- yeah,
OK. So some hands are going up right.
And, and, and so it seems to me, like,
if there is that much talk about it, you
know, it seems like we often get the response
that, you know, we just need to run with
more passion at the wall. Run faster. And, and
if we just fully commit that we'll burst through
and we'll be in this sort of magical land
where everything works the way that it's supposed to,
and just don't fight so much, right?
I have to think that if so many of
us have kind of run into these sort of
problems, that maybe there's something there. Maybe there's, there's
some kind of an issue there, and I wanted
to give a few opinions about, you know, why
that might be.
So, Rails is a convention over configuration advocate, right.
That's, that's the big thing that we always talk
about, that we would prefer to have convention over
configuration. Now, conventions are really what, what opinions want
to be when they grow up, right. So, most
conventions have stemmed from an opinion. We hopefully share
the opinion, but I want to be clear that,
when I talk about the opinions that Rails has
today, I am not talking specifically about any members
of the core team. I'm not talking about the
people, though the software may in fact reflect opinions
of people, what I want to talk about is
what does the software tell us, right?
Because we know that Rails is opinionated software. So,
with that out of the way, let's go with
the first opinion that Rails, Rails has, that I
believe Rails has anyway, which is that code should
read like English. And we see this pretty frequently
in comparisons between straight-up Ruby versus Rails kind of
code, right, so.
Here's something that a lot of us actually like.
Like, I know this is one of the things
that hooked me on Rails right off the bat,
was like, oh, I can say, like, five dot
days ago, or, you know, because I'm writing something
in old English, I can say one dot fortnight.from_now.
That's very useful. Right.
So there's nothing particular, when we say like, one
dot megabyte, right, there's, there's nothing megabyte-y about the
number, right. It just does some multiplication.
Similarly, we have some, oh yeah. Fortnight. Yeah. Similarly,
we have some differences where we have something that
array already has, for instance, on it, like the
sh- I just found out it's called the shovel
operator. I always just said less than less than,
but whatever it is, right. So we have, like,
array less than less than object, right, and we've,
we've got that alias to append, so that we
can say append.
We have object dot in, so we can ask
whether an object is in an array include of
ask an array whether it has an object. Things
like that.
We have string methods that have been, you know,
kind of patched in for, for instance, the string
inquirer. That's one of my, one of my favorite
ones. Right in the middle there, we have a
check on the left. In Ruby, you simply ask
if the environment string equals production. But in Rails,
there's a thing called a string inquirer, which simply
patches method_missing so that you can actually say, OK.
Is the string actually containing the method that you
just sent minus the question mark? And so, that's
really all its doing - the thing on the
left.
Here's another one. So, in time, we can say
beginning_of_day, we can say midnight, we can sat at_midnight,
we can say at_beginning_of_day. We can say midday, noon,
at_midday, at_noon, at_middle_of_day, right. They're all the same thing,
and yet there's this really clean API that's exposed
this whole change thing, right. It's fairly discoverable. Like,
I could make a reasonable guess that if I
said change_minute to something, or if I said change
any piece of the time to something, it would
be able to do exactly what I ask it
to do just then. But instead we have these,
these aliases that allow us to write code that's
like English.
Now, when I go into a coffee shop and
I want something that's got a high caffeine content,
I may order one of these. You all know
what this is, right. This is coffee brewed by
forcing a small amount of nearly boiling water under
pressure through finely ground coffee beans. And that's what
I go up to the counter and ask for,
right.
Nope. You know. They'd probably go, you mean an
espresso, right? Because espresso is what we call a
loan word. We borrowed that word from Italian because
it was concise, it was a good way to
say what it was that, that we wanted to
say without all of those words, right.
Ruby is a language in-and-of-itself. It borrows from English
where it makes sense. But it is my opinion,
anyway, that if there is a concise way to
say something in Ruby, we don't necessarily need to
borrow a loan word in English. You know how
you sit across the table from that guy who
always like to use a foreign word for something
just to sound clever, right? Right. That's what we're
doing, right. Like, it doesn't make a lot of
sense.
Now, here's one you may initially disagree with. I
believe that Rails believes that models descend from ActiveRecord,
or at the very least an ORM. Now, if
you disagree with me, I have three words for
you. Rails generate model.
What do you get? You get an ActiveRecord subclass,
right. Now, you might say, well that's silly, right.
But this is the first exposure that Rails developers
are likely to have to what goes in a
model's directory, and I have talked to extremely smart
developers who have told me that they went a
number of years before they realized they could stick
plain-old Ruby classes in app models, because they just
thought that, that's not what models are. Right?
And so, rightfully so then, Rails believes that the
data is the domain, right. That's the ActiveRecord pattern.
That's the whole point of it. And if you
don't believe me, I'll show you some README information,
right.
The prime directive is that we're minimizing the code
needed to build a real-world domain model. Lots of
reflection and runtime extension is the understatement of the
year. And magic is not inherently a bad word.
Now, what this really comes down to is, is
Rails was very much a reaction to very configuration-heavy
frameworks in Java, XML files and the like. Big
reaction to that, right. So what we had determined
at that point was that configuration is the devil,
right. And once you have, once you have determined
that there is a greater evil out there, then
a lot of lesser evils start to seem pretty
great by comparison, right. So you can rationalize yourself
into a really tricky spot.
And so, our goal is to be able to
write class Post inherits from ActiveRecord::Base end and have
magic happen, right. Like, for instance, we infer, and
I want to talk a little about, about one
thing that we do in that case, right. We
infer what the attributes should be on that, on
that model without writing a single line of code.
Now, there's a problem with that. Namely, that in
order to infer the attribute names, you must first
invent the universe. So I want to walk us
through what that looks like. We're gonna do one
deep dive into some Rails internals, and then I
promise we'll come back up for air and we'll,
we'll talk about happy things.
So, so you might imagine pretty, pretty early on,
that we're gonna go ahead and initialize a module,
and we're gonna actually have a module to store
our attribute methods in. That's actually something I kind
of like, right, because as a Rubyist, I expect
to be able to call super if I define
my own method on the class, so that makes
a lot of sense, right.
So if we look at what happens when we
generate the module, we have a new anonymous module
that extends Mutex, cause we don't want to be
modifying the module from two different threads.
And, then in method_missing, we have this handy little
hook that defines the attribute methods, right. And so,
in method_missing, we call this every single time, right.
And the idea being that once we've defined the
methods, they're, we're not gonna hit method_missing, at least
for those methods anymore.
And so we bail out, if, in fact, we've
already generated them. And we call super with column
names, right. Now, where do we get column names
from? Well, it's not there. In fact, method_missing was
the only thing that actually sits on, on the
instance level. The rest of the stuff that we're
gonna be looking at sits on the singleton. It
sits on the subclass of ActiveRecord itself as a
class method.
So, when we call super, we're actually calling super
into ActiveModels. Definition of defining attributes, right. So we're
saying, define attributes methods from ActiveModel. We're passing it
the column names. So here's column names. Well, column
names is, again, a singleton method. It knows that
it needs columns, right. And below here, we have
columns.
Well, columns needs the connections, for starters, to the
database to talk, to figure out, you know, what
table it's gonna read from. And it also needs
to determine table name, right. So now we need
to know the table name. So we don't have
the table name yet, so we call a reset
table name, and most of the time I'm gonna
skip past some of the, some of the more
convoluted things and just talk about the compute table
name, right. So we have to compute the table
name. It makes sense, right?
So, this is kind of a big method. Hopefully
you all can see the, the part that matters,
which is we call undecorated table name with the,
again, we're sitting on the class. So name refers
to class dot name, right. The string name of
the class. The full module name.
And, undecorated table name basically does exactly what you
see here. Let's say we have a namespace model,
Conferences::RailsConf becomes RailsConf becomes rails underscore conf becomes rails_confs,
OK. Which is great. And then, of course, if
we have Animals::Moose it becomes Moose it becomes moose
it becomes mooses. Which makes lots of sense, right?
Well, that means we need to handle irregular pluralization.
So we check and we see whether or not
there's a default pluralization to find for moose. There
is not. There, there, there are some interesting ones,
I think. We've got, like, sheep. We do have
sheep, so we're at least in the ball park.
We have jeans. We have fish. But we don't
have moose.
So, we go into an initializer and we set
inflect.uncountable "moose" and then it works, right. But. Configuration.
Or we can say self dot table_name equal 'moose'
and then bypass all of that altogether, right. But,
again, configuration.
Now, if configuration is the devil and magic is
the goal, then we have to think to ourselves,
what is, what is magic, really? And, and an
enjoyable magic trick is basically willful suspension of disbelief,
right. So, that is, if this magician came up
and said, I'm going to saw my assistant in
half, and you didn't think, there's a trick here,
like, it's based in the rules of reality, like,
there's something going on. It would be pretty horrified,
right. We'd be like, I don't be- where have
I gone? Like, what is going on here, right?
And so there is this level where we would
do better to consider, how can we root this
kind of magic in some reality that the average
person is going to be able to understand? And
then kind of build on that knowledge, sort of
scaffold, if you will. And, otherwise, you end up
with what I, what I referred to as douche
bag magic, which magic that bites you, magic that
screws you over in all sorts of wonderful ways.
So, let's imagine, for a moment, a world, a
world where we do have magic that we can
understand. And, so let's just say we have ImaginaryRecord::Record
that always asks us to configure a table name.
That always asks us to define the attributes in
a very mapper style, data mapper style. Right?
Given an attribute name, tell it's how its to
load the attributes. Tell it how to load the
attributes. Maybe some other stuff. We don't, we're just,
we're imagining here, right. We're, we're just in your
imagination.
And then let's say that we have a MagicRecord
that inherits from the ImaginaryRecord::Record, and does all the
same stuff that we did before, except it calls
these methods on the class in order to set
the table name programmatically, in order to set the
attributes programmatically, right.
Well, that would be pretty cool. We would have
to make some trade offs, but we'd basically get
to the point where we could do something like
inherit from MagicRecord and get the exact same behavior
as we wanted. But as a freebie, now we
have the ability to drop down another layer, if
we want. And to actually define this stuff with
less magic.
And you say, well that's great. So that's attributes.
We kind of understand that. That's pretty simple, right.
Big whoop. ActiveRecord also does all these other things,
I mean, you know, it's got like attribute typecasting
and associations and serializations and secure passwords and transactions
and macro reflection and nested attributes, time stamps, lifecycle
callbacks, validations, dirty tracking, mass assignment, sanitation, to name
some, and oh, by the way, we have querying
and persisting, right. That's kind of a important thing
in a persistence library.
And so, I say yeah. Yeah, exactly. That's. That's
an interesting problem that we have, right. Because they're
all kind of living in one place, and how
do you expose this kind of interface, this kind
of way of interacting with, with your library, to
cover all of these things. And my answer would
be basically that you, you don't. You, you allow
this to tell you maybe I've got a crazy
idea.
So, this is the ancestry chain of, of an
ActiveRecord::Base subclass. I just wanted to show it to
you real quickly. Now, yeah. Yeah. Three cheers for
the, for the ancestry chain of ActieRecord::Base. So, somebody's
clapping. Oh no. Right.
SO, in Rails we call this clarity. You may
have heard that earlier. Because API surface area is
irrelevant. We don't care about that. In other words,
the Single Responsibility Principle is super simple for sufficiently
large values of responsibility. Like, if you're responsibility is
to do everything, then you just say, OK, gotta
do everything. It's gotta work, right. That's my responsibility.
Well, it works. All right. Done.
And so awhile back, actually last weekend, I Tweeted
some of these statistics about an observation on an,
a brand new Rails 4.1 app, what a subclass
of ActiveRecord has, what the view has, and what
the controller has in terms of ancestors, public protected
class methods, public protected instance methods, and same for
private methods, right.
Now, look. It may very well be that this
is exactly the API size that we need. I'm
not gonna argue that one way or the other
right now. But what I will say is that
there are other ways to get there. Just because
we want these methods does not mean they all
actually have to be implemented in modules that are
included onto the class.
And so what I'm getting at is that, it's
very, very hard to keep track of just what
you're doing. I've been writing Rails for over seven
years. I'm not exactly sure how long at this
point. But, I know that, since I've been doing
it, I still have to have documentation open most
of the time when I'm doing anything serious. Like,
maybe I'm just dumb. Possible. I don't know. But
I need documentation. It's a really big namespace to
try to keep in your head all at one
point.
So, this is on, online at GitHub, and you
are free to do science to verify my empirical
observations and see whether or not they are accurate,
and I would love to see PRs to this
repo, actually, with, you know, different versions of Rails
to see how things have changed, complexity-wise.
Now, it's not enough really for ActiveRecord to just
do this on its own. It actually encourages us
to do this, right. Rails has this whole helper
thing, right. And, let me give you an example
of something I actually encountered and, I'm embarrassed to
say, spent like half a day trouble shooting with
the, with helpers.
So, I decided I had a really great idea,
and I was gonna use something that I heard
that OO developers like to do, which is polymorphism,
and use it at the view layer, right. And
I'm like, I can have something that is summarizable,
and so I might have a helper for posts
that's summary, and I might have a helper for
reviews that's summary.
And I can share a partial that prints that
summary in an appropriate way for that particular thing,
right. That seemed pretty cool. So this is how
it would look in a partial. And in development
it worked wonderfully. It worked exactly the way I
expected. And then I deployed to production.
Now, how many of you think it worked? Right.
How many of you think it didn't work. Well,
it's kind of a trick question, because honestly you
can't really tell me, because you don't have enough
information right now. Because there's this thing, right.
OK, so, seven years ago, there was a commit
to Rails that said, we are going to make
it default assumption you want all helpers all the
time. No comment.
And, for a long period of time, I'd like
to imagine it was some kind of a dark
age in Rails. There was really no way to
opt out of this, per se, until a patch
came in that added include all helpers as a
configuration option. So I thought, this is great. I
found this, now I found this after hours of
searching, like, because I just somehow missed the memo
that we had done that. It was way back
in 2.3, like, this has been the way for
awhile, right.
But, like I just kind of assume, like, it's
named post_helper, it should help posts. Not, like, other
things. But that's not how it works. So there's
this kind of big namespace, right. And so, I
would recommend that nearly all of you consider adding
this to to your application dot rb. You will
be much more sane for it. If you're going
to use helpers, at the very least, having a
namespace that you can kind of control. If you
want to be available to all controllers, then throw
it in application_helper or have a module. You can
still use modules, I hear. It works.
Now, we did all this in the name of
convenience, right. Convenience is, is going to trump everything
else. We want convenient usage at expense of anything
else. And, you know, so I own a house.
And I think to myself often that, you know,
when nature calls, I have, like, to get up
and like, go to a special room to go
do my business, right.
[laughter]
And I mean that's really inconvenient. So like, why
don't I just install a toilet, like, in every
room in the house, right? Because then, like, if
I'm watching the tube, it doesn't matter you know.
Do my thing. It's all good. but the problem
with that is, right, that first off, you've got,
like, plumbing now to help support this, this aspiration
of having a toilet in every room. And plumbers
are expensive and, like, stuff breaks and then it's
like, I've got a leak and I've got damage
and stuff, and then the other problem is, like,
every room is a toilet, right.
So, I would encourage you to consider that if
this is kind of what you're looking for, if
a giant namespace of, of, of methods and functions
is what you would really like to have, then
have I got the language for you.
It is super convenient, like, everything is at arm's
length. What's that? You want to do something with
a string and an array, yeah, doesn't matter. MySQL,
sure. Absolutely. I mean, just a thought.
So now another, another Rails opinion is, who needs
classes when we've got modules? And I say this
primarily because of one interesting piece of code, which
is ActiveSupport::Concern. So, ActiveSupport::Concern you may think is mainly
for having a convention around having a class methods
module inside your module. And instance methods and so
forth. But it's not.
The problem the it's designed to solve is that,
in this world, the Ruby world, the one that
we live in lots of times, we, we actually
would have to include. There's a lot of extension
onto a class, right, when you include a module
in the Rails world. Like, a lot of the
modules that Rails is built on go and do
metaprogramming and add methods to the singleton or whatever
onto the class itself, right.
And if you're adding stuff to the singleton, then
it's not sufficient for you to go ahead and,
and do that in a module that is itself
included, right. Because when it gets included in the
module, it modifies the module singleton, not the class
singleton. With me so far?
Right. This is a huge problem. So. So let's
solve it. Now, we have this ability to have
dependencies that get trapped by ActiveSupport::Concern, so that each
singleton knows what, what its dependencies are, and then
at the time that you include the, the dependency
that you're really looking for, it can also go
back and include into your module all the other
ones that it needed, and so then that way
they can do their metaprogramming magic on your singleton
instead of their singleton.
It's great. And it's, you know, so what it
is is that there was this, this, this problem.
There was this problem that we had. And, you
know, we decided to bundle a, a, a free
razor with every yak. And, I mean, I guess
that's one solution, sure. And so, so I've been
thinking a lot about this lately.
And one of the things that sort of bothers
me is that, you know, we're regularly told not
to, not to fight the framework, right. But our
framework pretty actively fights the language. There are things
that the language is saying, hey, this might be
crazy. Maybe you don't want to do this. And
we're like, I won't do what you tell me.
I will do exactly what I want to do.
And it encourages this, this kind of module-centric design.
Limits the entry points that we can have. So,
you know, Yehuda made a really great point, actually,
when he talked about that we're kind of on
the 400th story, right. And, and I agree. We
are, we are like way up here in terms
of abstraction.
But, like, I've heard rumors that, like, maybe, on
most buildings, like, those other floors like have people
in them, and like, they do things. And like
maybe you can not just be trapped on the
400th floor forever, but maybe you want to like
go down, cause like, I heard, like, on floor
350 there's like an ice cream store, and like,
I like ice cream. Right. So maybe I want
to go down and have some ice cream.
But, like, I can't. Because I could only do
things with things that are instantiatable, right. And if
everything is a module, I'm not instantiating that. I
guess I could write my own class and include
half of what's going on, but that seems like
I'm just perpetuating the problem.
So, here's another, here's another Rails opinion. Rails believes
that your conventions suck. First example is, now, I
know it's gonna be hard for you to believe,
but there were conventions for building applications before Rails
came out. Like, people were actually building - I
know, it's a little weird. They were building web
applications, and, and one of the things that they
used was JavaScript, right.
Now, love or hate JavaScript, it, it, it's here
to stay at this point. We, we, we're gonna
be writing it. And so, Rails, once upon a
time - you guys remember this? You remember this?
Like, let's, so. If this was before your time
with Rails, we had, we had this great idea,
right, which is, JavaScript's a pain to write. And
let's write Ruby that writes JavaScript instead. And so
we called that RJS, and you could write these,
there were these methods that if you said page
dot whatever, it would generate some JavaScript. And you
could go look at the JavaScript and it was,
you know, about what you would expect generated JavaScript
to be.
And then we decided, you know, OK, maybe that
was crazy. But we're still not happy with the
whole state that we have to write JavaScript. So
we decided to use JavaScript that looks like Ruby,
right. OK. Great. All right. Fine. I can see
that.
And then, at that point, now, we were at
a point where there's this other convention, where like,
if you're using jQuery, and many of us are,
like, let's say, let's say that I want to
render, I have decided I'm gonna be on the
straight and narrow, I am going to render my,
my, my views in Ruby. I want to render
static html that I spit out. But I need
some dynamic parts, and so I'm gonna render some,
some JavaScript as well that's gonna do some things,
right. Use jQuery.
This is a pretty standard convention, right. I mean,
how many of you have this exact code in
your code base somewhere, right. If you're not raising
your hand, I'm very shocked. So, we thought, you
know, that's, that's great. It's really cool to be
able to hook into $(document).ready and do some stuff.
And so, so Rails said, that's a horrible idea.
You should use Turbolinks. And so now, look, you
can yank Turbolinks out of your gemfile and that's
true. You can do that, right. But it's my
opinion that convention should probably not completely break someone's
expectations if they're doing server-side rendering, and this is
something that you, you are going to run into
if you're just starting out with Rails. You'll be
like, well, why isn't this doing? Well, we don't
fire. We don't fire the doc.
No, there are, there are other gems that will
make this seem like that is not the case.
But the fact of the matter is, it's gonna
bite you. So, so Rails said, you know, we
have opinions about how you should write JavaScript.
Rails has opinions about how you should maintain data
integrity as well. And so in the Rails world,
this is a typical, typical way to handle some,
some validation of data integrity, right. Maybe you make
sure that you have a customer, and you need
a unique reference number, and you have a typo
there.
validates_associates. Validates :associates and :line_items, right. And you won't
save an order unless its :line_items are valid. And
that's really interesting to me, because objects, unfortunately, you
know, they don't really understand their relations to other
things very well, because those live somewhere else. They're
in the data store separate, right. And, you know,
I, I really think that it would be great,
like, if there was some sort of a system
that we could use. So, like, I don't know,
it would be like a system for managing, I
don't know, relational data, maybe? I don't know.
It would be great if we had one of
those. And it turns out that, like, we do.
And they are, like, super good at understanding relationships
between data. And they are super good at, at
validating the data that goes in and out and
ensuring that things are atomic, right, and that, that
our updates are not gonna run into race conditions
and the like, right?
I mean, and I'm talking about, even the, let's
say, less than civilized versions of databases, those are,
those are also able, able to do pretty well
at this. Because, Ruby, the Ruby land has to
cross a process boundary to know any of this
stuff, and that's pretty lossy, really.
So, I don't think it would be fair, you
know, I've griped a lot at this point about
a few of the opinions that I think Rails
has, and I feel like you came here hoping
that I would offer you some solutions, right. I
want to show you some solutions that I've found.
These are my solutions. They look pretty nice, I
think.
Science.
No. So, I've been doing this thing lately, and
I'm calling it IDD. And that stands for Ignorance-Driven
Development. It's really blissful. And it's this, this crazy
idea that, like, maybe starting with Rails g model,
like starting out thinking about your persistence, is maybe
not the way to go.
If you're thinking about your persistence for - I
mean, I can tell you, all right. Raise your
hand if you have stopped to consider what your
database scheme, schema is gonna look like after you've
typed Rails g model, and you're just kind of
frozen there, like, crap. Analysis paralysis sets in, right.
Because, like, migrations are a pain and databases are,
ew, yuck. We want to do everything in Ruby,
right?
And, and so that's a problem, right, because I
don't necessarily know what the best way to store
my data is off the top of my head,
right? I may find that out as I go.
So here's a crazy idea.
Start with Ruby and ignore persistence, off the bat.
Like, probably that is not your domain. Your domain
is not to be a CRUD app. Most of
us here, anyway, our domain is not to just
be a CRUD app, right. We have some business
logic.
And after you start with Ruby, then you start
to identify the scary things, right. And this is
like the part of our domain that we're really
scared we're not gonna be able to implement. Like,
what is the thing that's gonna be super hairy,
that we're just kind of like, afraid to tackle,
right. And removing the, like, the analysis paralysis that
can set in when you're trying to figure out
where your, what your data's gonna look like, while
you're just kind of playing around with, with an
algorithm, is great, because it gives you this sort
of power to attack the scary things. You can
do, I know it's crazy to say, some TDD
and attack these scary things.
And it's, the cost of experimentation is a lot
lower when you're not messing around running migrations. You're
just running super fast tests. Everything is really nice.
And you validate that the business logic that you're
trying to write is going to be sane before
you go any further.
And so, so what that might look like, for
instance, is let's say I have a monster class,
right. I simply give it some teeth and some
claws, right, and it can bite and it can
scratch, and I write my test. And let's just
assume that biting and scratching is a very, very
difficult thing for a monster to do, right.
And, and so it starts out that way. And
you gradually iterate on the things that you want
your monster to do, right. And then what you
find out is that, eventually, after having iterated through
maybe, maybe many, many iterations on what this class
looks like, what its collaborators might look like, you
get to a point where you're like, OK. I
know how to make this monster now. I know
how to, how to tackle it, right.
And now is a time for you to admit
persistence. Right, now you start to think, OK, well,
I've gotta persist this stuff somewhere. I have some,
some things about this business logic that are gonna
require me to save some data to the database.
And so you decide, well, what database am I
gonna use? Or is it gonna be NoSQL or
is it gonna be SQL or is it gonna
be, I don't know, something in memory? Right.
And then from that point, let's just say you
decide, well, now I'm gonna do some ActiveRecord. And
so you say, all I do is now inherit
from ActiveRecord::Base. The teeth and claws are no longer
supplied via injection. But instead they come from relationships
in some way, and we maybe have determined that
we require that we have teeth and claws.
And then, you guys are gonna think. You notice
there's no methods here, right. Right. No methods. It's
not doing anything yet, except the ActiveRecord stuff. You're
gonna think I'm trolling you. But here's what I've
started to do.
I use modules. Now, the way I see it,
you know, in firefighting there's this thing called a
back fire, where it's like, OK, so, when they
say, like, fight fire with fire, that's actually a
thing. And like, I'm like, you know what, Rails,
if you really wanna burn the whole place down,
that's OK. I got my own little fire going
over here. And it's gonna, it's gonna allow me
to have some semblance of order. And let me
show you what that looks like. And before you
think that it won't work, I can only just
say that it has worked for me. Give it
a try. Strangely, I'm not going to prescribe that
it's gonna be the solution for everyone. I know
that's kind of the hip thing to do is
to say, like, my solution will work for you.
But it worked for me.
And what it would look like is this. So
I now have behaviors that I expose in modules,
right. These behaviors are specifically intended to provide one
isolated thing, one isolated capability to the persisted record,
right. And so in this case, like, here is
the biting module, for instance, right. And it knows
how to bite. And let's say this is after
we have developed all of our, all of our
stuff in plain Ruby, right, this is what we
landed on for what bite need to, need to
perform, right.
And so once we've done that, we can write
a class, you know, we can actually just test
against the class that limits the API service that
our behavior interacts with. This was the goal. Like,
this is why I do this at all, is
because thinking about how I'm gonna interact with this,
like, essentially infinite API that, that a lot of,
a lot of Rails provides, particularly ActiveRecord provides in
this case, is, is too hard for me. Cause
I'm dumb, OK. So, what I need is some
help to help me just be able to see
into the test. This is the part of the
API I care about.
And you may be concerned about this. You may
say, this looks a lot like ActiveSupport::Concern. I can
understand why you would say that. Let me show
you why that's not the case. So, earlier we
talked about how ActiveSupport::Concern's primary goal is to solve
the problem of modules that depend on other modules
that modify their base class, right.
The intention in this, and that might look like
this. Like, let's say now there's like a whole
fighting behavior that might bite and might scratch, we
don't know yet. There may be some probability associated
with it, that against a particular target or maybe
more than one target or whatever, and so it
sort of has this dependency, not just on the
API that the class that it's being included into
has, but also the stuff that's getting included into
that class already. So it expects bite and scratch,
which are provided by other two behaviors. So, that
kind of thing becomes actually painful. That actually becomes
painful when you're not using ActiveSupport::Concern.
And so, what you actually have here is a
canary in the coal mine, a sentinel animal, right.
It's, it's something that's telling you, there's this thing
that's doing more than one kind of thing that's
more complex, right. And it really isn't fully encapsulated
in the stuff that I've got so far. And
it leads you to understand that maybe there's another
thing. Like maybe there's an encounter, right, and it
might include one or more combatants that select targets
and do one of their actions, right. Either bite
or scratch. All right.
So, to kind of start to sum things up,
it, we, we, a lot of us work in
the startup world, right. And it's really frustrating and
surprising to me that we embrace this idea of
a minimal, minimum viable product in the startup world,
right. Build the smallest thing that could possibly work
and then go from there.
I, I kind of feel like in software, the
whole goal, really, is to make as few decisions
as you possibly can in order to make, get
the desired result, right. Every decision you defer till
later is going to give you some capability to
be able to react to change, right. So I
would like to see us, both in Rails and
in the software that we write, start off with
fewer opinions. Start off with fewer assumptions. Make sure
that the floors that we build have a purpose.
One great way to do that is to make
sure that you can instantiate a lot of the
things, right.
If you can instantiate a lot of the things
then they must have a purpose that we can
reason about. We can think about. As opposed to,
well, they might modify five hundred methods on another
class. And in general, just be a considerate software
writer.
Thanks for your time.