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.