I’m trying to migrate my models to be dumb data bags that have some useful state
altering methods. I’ve found that using query objects, I was able to reduce the
complexity of some of my models and controllers.
The Problem
My User model is gigantic. It’s well over 600 LOC, and it’s growing at a scary
rate. Testing is becoming a nightmare because of all the methods. A few methods
I have realized, I could remove completely and extract them into query objects.
Now this method isn’t exactly all that complicated but my User model is
rittled with these methods. They are just there to be used by the controller
like so:
All that method does, is execute a query. It doesn’t alter the state of any
model. This could be extracted out into its own class and be isolated enough
that testing becomes very simple.
The Solution
The solution is simple, kill The Batman. First, I need a query class. When
creating query classes, keep in mind that you need to interact with
ActiveRecord::Relation objects. This will allow for the query objects to be
chained and used again later.
Now looking at this, it is easy to say, “Well that didn’t change much”, but it
did. I can remove the old method from the User model, and reduce the
complexity a little bit by doing this.
Chaining
Here is an example of chaining some queries together
Recently I’ve been playing with Ruby’s class_eval and define_method to
append methods to an object. Meta programming is something new for me and I had
a problem that would benefit from using some of Ruby’s cool features.
The Problem
There was a model that had 6 fields that needed to be generated at some point in
time. Some fields had to be unique and others had to be generated a bit
differently. Here is a contrived example of what I was doing:
If these generators are spread across multiple models, it will cloud the source
code and a test will need to be written for every generator. If we utilized
shared examples, there wouldn’t be a need to write a test for every single
generator.
The Goal
In the end, I wanted an easy to read DSL. Adding more DSL to a class can mean a
”code smell”, however in this case, I do not think it is a code smell.
app/models/some_model.rb
1234567891011
# End goalclassSomeModel<ActiveRecord::BaseincludeGeneratablegeneratable:some_field,lambda{SecureRandom.hex(32)}generatable:unique_field,lambda{SecureRandom.hex(32)},unique:trueends=SomeModel.news.generate_some_field#=> some 32 character strings.generate_unique_field#=> some 32 character unique string
The pros
Easy to read
Easy to figure out where generatable is being loaded from
Compact
Testing becomes easier (more on this later)
The cons
Can’t use the model in the lambda
Adds to model complexity
Lose some control on testing (more on this later)
Solution
This is a perfect case where ActiveSupport::Concern applies. This is
functionality that multiple models can utilize and thus, should be put into a
mixin and included to those models. My initial implementation only made a simple
generator but, it does show how class_eval works and is an easy example to
grok.
app/models/concerns/generatable.rb
12345678910111213141516171819202122
# Partially what I wantedmoduleGeneratableextendActiveSupport::ConcernmoduleClassMethods# @param [String] field the name of the field you want to generate# @param [Proc] generator the lambda or Proc that you wish to use as the# generator# @param [Hash] options# @option options :unique# @return [void]defgeneratablefield,generator,options={}field=field.to_sclass_evaldodefine_method"generate_#{field}"doself.send("#{field}=",generator.call)endendendendend
The next requirement I had to fulfill was generating unique values. This took a
little bit more thought.
moduleGeneratableextendActiveSupport::ConcernmoduleClassMethods# @param [String] field the name of the field you want to generate# @param [Proc] generator the lambda or Proc that you wish to use as the# generator# @param [Hash] options# @option options :unique# @return [void]defgeneratablefield,generator,options={}field=field.to_sifoptions[:unique]class_evaldodefine_method"generate_#{field}"dobeginself.send("#{field}=",generator.call)endwhileself.class.exists?(field.to_sym=>self.send(field))self.send(field)endendelseclass_evaldodefine_method"generate_#{field}"doself.send("#{field}=",generator.call)endendendendendend
This isn’t exactly pretty. Infact, one could argue that this is a bit difficult
to follow due to the if options[:unique] statement. Another possible solution
could be to break this up into three methods like so:
moduleGeneratableextendActiveSupport::ConcernmoduleClassMethods# Appends a generator# @param [String] field the name of the field you want to generate# @param [Proc] generator the lambda or Proc that you wish to use as the# generator# @param [Hash] options# @option options :unique# @return [void]defgeneratablefield,generator,options={}ifoptions[:unique]generatable_uniquefield,generatorelsegeneratable_simplefield,generatorendend# Append a unique generator# @param [String] field the name of the field you want to generate# @param [Proc] generator the lambda or Proc that you wish to use as the# generatordefgeneratable_uniquefield,generatorfield=field.to_sclass_evaldodefine_method"generate_#{field}"dobeginself.send("#{field}=",generator.call)endwhileself.class.exists?(field.to_sym=>self.send(field))self.send(field)endendend# Append a simple generator# @param [String] field the name of the field you want to generate# @param [Proc] generator the lambda or Proc that you wish to use as the# generatordefgeneratable_simplefield,generatorfield=field.to_sclass_evaldodefine_method"generate_#{field}"doself.send("#{field}=",generator.call)endendendendend
Testing
Earlier I mentioned that this makes testig easier and then followed up with a
loss of control in the tests. The Generatable module will need to be tested in
some fashion, whether it is directly testing it via including it on a dummy
model or making a shared example where the test would have it_behaves_like
somewhere.
One day I found myself riding in a car with my family and it was a long road
trip, with little to no internet and realized that my tests required internet
connectivity. I was also heavily dependent upon code examples that I could find.
I’ve been absolutely horrible with testing my code. I would write code, test it
out in the ruby console and then push it live. It felt like I was moving fast. I
was being agile. Wrong! Testing is necessary in order to succeed. Often I
would push code and do the good ole’ cowboy code em’ up. This is a horrible
idea, don’t do that.
Stubs And Mocks
I’ve seen arguments for and against the use of stubs and mocks. I like mocks and
stubs. I do think they are useful, and they have their place. Many people find
themselves over stubbing and over mocking. This can lead to brittle tests.
I suppose it is tempting, if the only tool you have is a hammer, to treat
everything as if it were a nail. Abraham H. Maslow
Corey Haines has a good presentation called Yay Mocks!.
Definitely worth the time to watch.
Using stubs and mocks in a restrained fashion will result in maximum payoff. I
attempted to stub and mock everything in a controller unit test. This turned out
to be a hellish nightmare to maintain. I did discover some cool logical
control flow that I could utilize. I could force CanCan
to raise exceptions and cause some fun errors in the controller.
describeThreadsControllerdodescribe'#create'docontext'when a user is signed in'dobefore{sign_inuser}context'and is authorized'dobefore{controller.stub(:authorize!).and_return(true)}context'and the thread is created'dobeforedopost:create,thread:{title:'Some Title'}endit{shouldrespond_with302}it{shouldset_the_flash[:success]}it'should create the thread'doexpect(Thread.count).toeq(1)endendcontext'and the thread is not created'dobeforedoThread.any_instance.stub(:valid?).and_return(false)post:create,thread:{title:'Some Title'}endit{shouldrespond_with200}it{shouldset_the_flash[:error]}it'should not create a thread'doexpect(Thread.count).toeq(0)endendendcontext'and is not authorized'dobeforedocontroller.stub(:authorize!).and_raise(CanCan::AccessDenied)post:create,thread:{title:'Some Title'}endit{shouldrespond_with403}it{shouldset_the_flash[:error]}it'should not create a thread'doexpect(Thread.count).toeq(0)endendendcontext'when a user is not signed in'dobefore{post:create,thread:{title:'Some Title'}}it{shouldrespond_with403}endendend
Testing permissions should not apply to controller tests. This ia a boundary and
should be stubbed. You can unit test your permissions in another set of tests,
but they do not belong in the controller test.
Boundaries
I strongly urge you to watch Boundaries
by Gary Bernhardt. Understanding where an object’s boundaries lie, is key for
test isolation. Ruby has a nice open class principle that allows for stubing and
mocking. Earlier I pointed out that in a controller permissions were considered
a boundary.
No Internet
Now for the fun story. I had no internet on my laptop while we traveled down the
highway. I was writing code and then realized I should run my tests real quick
to ensure I didn’t cause a massive problem. What I found out to my surprise is
that more than half of our test suite failed, due to the need to talk to a 3rd
party API. Ooops.
All I had was my phone and even then all I had was spotty coverage. I remembered
seeing the two videos mentioned above, and thought to myself, “This is similar
to what TDD is right?” I was right, as I went through and looked at why the
tests were failing, I noticed that they were failing in places that were
difficult to test. On top of the areas difficult to test, it was often over
stepping boundaries.
I used my phone to look up RSpec Mocks
and tried out a few examples to make sure I understood what was going on.
And when the test greened up, a sudden grin appeared on my face. I realized that
I could do anything. I have never once been happy about testing in Java or PHP
and I couldn’t help but be really excited when I started seeing some tests come
up green.
I started to stub and mock places where I was making calls to an API. Many of
these tests, I simulated errors that could be encountered while interacting with
the API.
I realized that there were parts of our application that were extremely
difficult to isolate. This was indicative of ”code smell”. I wrote some tests
on how I wanted the peices to interact, then made the peices interact to make
the tests green.
Being able to simulate errors from the API was a HUGE help. It allowed me to
see potential errors and handle them appropriately rather than show a big ole’
500 error page to our customer.
Resources
Here is a list of resources that I utilize all the time. In fact, they are
bookmarked and I often visit them.
I have been hard at work over my birthday to now. I’ve spent the better part of
20 hours with my dad working on my new standing desk. Where I was working before
was extremely cluttered and an upgrade to my life was needed.
I wasn’t able to take pictures from the initial start of the project, due to us
having to do multiple things at once before Home Depot closed. However, after we
settled down, I had Amanda come out and take pictures as we were working on the
desk.
We built the desk using Oak tongue and groove boards. My parents finished an
interior remodel of their house about two years ago. A lot of oak boards were
left over and my dad suggested we use it instead of buying a new sheet of Birch.
I hate glossy polyurethane. Even a satin finish still bothers me some. For me
wood isn’t supposed to be reflective. I opted for a Matte finish with a Dark
Walnut stain.
Staining was probably the best feeling in the world. I was so close to being
done!
After three layers of polyurethane and some serious tender love and care. I took
it home with me to assemble! This table is super heavy due to all the Oak that
we used.
Finally! I have graduated from the University of Texas at San Antonio with a
Bachelor’s degree in Computer Science. I waited for 4.5 years (9 semesters) to
finally reach this point in my life. Though now I wonder what awaits me in the
future.
Now I’m able to work Full Time at ZippyKid where I
help build and maintain the deployment system. Ever since I started working here
in January 2012, I’ve grown a lot and I have learned a TON.
Things I’ve learned:
Rails is not the answer to all questions
Venturing out of your comfort zone is great
Good OO design is not easy
There is no silver bullet
No one really knows what they are doing in software engineering
I despise consuming 3rd party APIs for one reason, speed. Many of the APIs I
have to consume on a daily basis, respond slower than it would take to deliver
an email. But, their integration with our application is absolutely critical.
They are so critical infact, that if an error should happen during any point of
the commissioning, then I need to be notified and or the job needs to be
reattempted at a later date.
On many occassions, I have simply split the 3rd party API object into its own
Rails model that is persisted in the database with a state_machine or some
sort of identifier on it that lets the application know if it is in an erroneous
state.
Billing is a prime example of this:
app/models/subscription.rb
1234567891011121314151617181920
# Table: subscriptions# - user_id# - subscription_idclassSubscriptionbelongs_to:userdefsubscribe# subscribe to api here# set subscription_id = id in apienddefunsubscribe# unsubscribe from api here# invoke .destroyenddefupdate_billing_information# interact with api hereendend
I mentioned earlier that I would use a state machine on models that interact
with APIs. However, in this example, if the subscription_id is not set, then
the Subscription will be in an erroneous state.
I prefer to make job classes, only because they allow for me to separate my
backgrounding code, from my model code. Plus, there is the possibility that you
are sticking too much data into the delayed job queue by doing
User.delay.send_some_email or @user.delay.send_some_email is even worse. It
will turn that ruby object into a YAML formatted object and stick it into the
queue.
The following is the best way, IMO to do backgrounding with Delayed Job. Just
put a few simple values in and it keeps the queue lean.
I would then invoke the Delayed::Job queue by using an observer. Now this part
you don’t have to necessarily put into an observer, but it makes for a cleaner
model. As an aside, I store my observers in app/observers, it just makes it a
little easier to organize my thoughts.
We have decoupled the 3rd party interaction from the controller and put it into
a background job, where it will be handled. However, in this straw man example I
did not show a proper way to handle failure. If I was going to actually do this
with a payment system, I would definitely notify the user that something is up
and make them reattempt the sign up again.
I discovered the Flight micro-php framework
this week. I found it because I was looking for a simple PHP 5.3+ framework that
didn’t have a large amount of extra cruft. I had a project to do for my Database
class at UTSA where we had to utilize an Oracle database. Not exactly my choice
for databases, but we had to make do with what we were given.
It is a very sparse framework that looks very similar to
Sinatra except it is even lighter. Flight
provided me ways to easily extend and customize.
Flight doesn’t come with a Database class. So you will have to roll your own
or use an ORM or use Mike Cao’s Sparrow
library. I haven’t had a chance to play with it yet, but it looks very
promising. Unfortunately, it does not support Oracle, so I had to roll my own
implementation, and it’s not pretty by any means.
I look forward to playing with this library some more, and hopefully building
something with it soon.
Here is my project if you want to know
more. It was built for a school project so keep that in mind while you are
looking at it in horror.
I am going to entertain you with an anecdote or two. I was recently talking with
one of my friends about what they should do for their next side project. They
didn’t know what to do or where to start. The only real sage advice I could give
them was to:
Build something. Learn from it, reflect on what you just built and work on
another project
I have been doing this for so long that it’s almost second nature. I hardly
think about the process anymore. I’ve started so many side projects that have
failed miserably, but I have learned a trick or two that I applied to other
on-going projects.
I’ve gotten hung up on few of my projects to the point I couldn’t solve the
issue at hand. I didn’t know what to do and thought I needed to take a break to
work on one of my side projects Starship Gunner. I was playing around with
how to organize my background jobs and setting up a decent test suite that I had
an epiphany. For some reason, the problem I was hung up on just made sense.
I remember sitting in my Advanced Programming class and not understanding what
the hell * and & meant in C. I thought it was all just heresy and voodoo
magic. That same semester I ran my car out of oil and had to pull my engine and
replace parts. Mind you, I had no prior experience with cars. I just started
hacking away at the problem and then got the motor put back into the car.
When that happened, I realized that I could do anything I wanted to. I can build
any application that I wanted to. I could put any parts on my car that I wanted
to. Suffice to say that I could put any bit of code on my applications. And the
* and & weren’t enigmas anymore. I picked up on pointer arithmetic pretty
quickly and things started working out.
TL; DR. I rebuilt the engine on my 1972 BMW. I realized that I can apply
the knowledge I gained from that project to any project I encounter. You should
try this approach, it’ll pay off.