I've been using many blogging platforms so far and each of them is dev-friendly at something while suck at something else. This post is here to help me visualise the perfect combination.
Before I go into features, I want a tool which is simple and without any pain, without anything to learn and remember. It must be optimized for the develoer workflow - creating new drafts should be easy, publishing should be easy. It should be code-oriented.
I want to be able to easily embed my just made screenshots (with shift-cmd-ctrl-4), as it's possible at Blogger, without any effort - just cmd-v to paste it (wysiwyg)
I like the simplicity of markdown and want to be able to use it sometimes, as in nanoc/GH pages
I want to have the post look pretty without any additional effort - medium has it OOTB, custom blogs have it after all the styles applied
medium has a cool title inventor
I want to add fixes quickly and see them live immedietally (like in Blogger, unlike the statically generated blogs)
none of the platform satisfies me with the pre-blog phase - drafts, notes gathering etc
nanoc forces me to come up with some title and it generates the date
no special support for drafts in nanoc, but we can fake it
publish schedule is sometimes cool
Blogger has it OOTB
I love to use iaWriter-like tools, so desktop apps when work on longer posts
markdown makes it easy
Some kind of Slack integration would be cool (Slack driven posts)
team collaboration is important - allow others to fix typos, edit anything
IDE integration is crucial - I want to move some code snippets easily as new drafts/posts
embedding code should just work with syntax coloring
auto-promotion of blogposts would be cool, like with Medium - they distribute your posts magically bringing you some traffic
------
Even though, there's no perfect blogging platform, yet - there are still techniques which can help you blog more in less time.
2015 was the year of mutation testing for me. Luckily, thanks to the effort of Markus Schirp, the Ruby community has the mutant tool.
In short - mutant is totally "production"-ready and may help you code with more confidence, by testing your tests.
What mutant does is it parses your code, builds the Abstrax Syntax Tree and then it makes some mutation, like changing true to false. After that it runs the tests. If the tests still pass, then you have an alive mutant and it's bad.
When it comes to details, mutant supports rspec. Rspec is usually the testing framework of choice for many Ruby devs (or maybe Rails devs?). While I see the value of RSpec, I'm also sceptical about the additional layers of abstractions.
In short - I'm not a fan of rspec. However, I'm a fan of mutant and that was a more important thing to me than using minitest. At Arkency, we've even had situations that projects switched from minitest to rspec, "because mutant".
As for December 2015, there's a pull request which has the minitest integration. It's not released yet, though. So, if you want to use it, you need to use this particular branch.
undefined method `cover_expression' for FuckupsControllerTest:Class (NoMethodError)
(and yes, I do have a project with a controller called FuckupsController)
The thing with the minitest integration is that you need to be more explicit with what the test is actually testing. This helps mutant to understand what it needs to run.
I had to add this to the top of the FuckupsControllerTest:
class FuckupsControllerTest < ActionController::TestCasedef self.cover(expression)
@expression = expressionend def self.cover_expressionunless @expressionfail "Cover expression for #{self} is not specified"end@expressionendcover 'FuckupsController'
In the later steps, I'll move it to some base class and make all the tests inherit from that. In that case, you just need to be explicit with the "cover 'Foo'" thing.
In short it says, that the FuckupsController#index action is 100% mutation-covered.
The minitest integration is not officially released for good reasons. Not many projects were tested with it. You can contribute by trying it on any of your minitest code and see if it works.
All feedback welcome at https://github.com/mbj/mutant/pull/445 ot feel free to ask/comment here and I'll pass it along to the mutant team.
----
PS. We now have sale on our Arkency books - http://blog.arkency.com/products/ - get a 30% discount with the HOLIDAYSALE2015 coupon. One of the books is about refactoring Rails controllers which is a perfect fit for the mutation testing idea.
Chess has always been an important part of my life. I started playing when I was 5 (which means it's now 30 years of my chess "career"), but I got the chess club quite late - when I was 13.
The nice thing with chess is that there's no age limit and ageing is not as big of a problem as in other disciplines. I have a neighbour who is 75 and he's still chess-beating me from time to time!
I see many similarities between chess and programming.
Recently, I've been working on improving my chess to reach the next levels. At the moment I have the title of master candidate (ELO = 2140). My goal is to get to the international master level in the next 5 years.
This year, I started trainings with a professional chess trainer. I have just finished another session. Part of the lesson was very inspiring to me. It was about things that could be also applicable to programming and our "careers".
Let me share some of this with you:
It's you who must control the current chess position and not the other way round. You need to be 100% sure that you know what this position is about. There's no place for "maybe".
When I heard that I immediately knew that was similar to my views about programming, especially about any kind of refactoring. There's no place for "maybe". You need to control the codebase, not the other way rounds. There's no place for "let's run it and see what happens". It's all about confidence.
The next quotes are more about the chess training, but also relate to the moment of playing the game:
Don't move the pieces on the board until you have all prepared in your mind.
You need to see the final position after each variation - you need to see what is your goal. There's no places for surprises.
Be honest with yourself - do you know everything? Do you know what's your target position? Work on it for as long as needed to be certain.
In refactoring, there are some moments when you can just be creative and move code around to see where it "fits". However, most of the time, you need to know where you're going. Why are you moving the codebase from point A to point B? What is the Z point? If you're planning a bigger change - you need to envision what's the shape of the code, after all the changes.
Motivation is a huge part of chess. The same with programming:
Simplifying it a bit - there are two ways of motivating yourself - 1. "I'm the winner", 2. "I just need to make the next good move". It's best to balance those two ways. If you're relying more on the first one - you risk losing the fun and enjoyment. If you're more into 2 then you risk not improving yourself, not having goals.
Whatever is the result of the game, you need to enjoy the process of playing. Excitement is a big part of improvement. There's no place for stress.
My chess work this year reminded me about some things that could be of use in programming:
Programming is a long-term discipline. It's never too late to improve.
Having a trainer/mentor/coach is super-important. They can help you track your goals, spot your weaknesses, focus on your strengths
Set your short-term goals - what do I want to improve at *this week*?
Set your long-term goals - where do I want to be in 5 years from now?
The better you are, the more you enjoy what you do.
The more confidence you acquire, the faster you are.
Frameworks/technologies/libraries/languages - they come and go, as with some chess openings. No need to focus too much on them.
Choose some other programmers who you want to follow, whose style fits where you want to be. I will never be Kasparov or Tal or Shirov, but they are my goals when it comes to the style of playing. The same with programming - I will never be Martin Fowler, Greg Young, Uncle Bob - but they represent the style I want to be better at.
In other news
We've updated Developers Oriented Project Management (our book about working remotely, asynchronously and without project managers) with 40 Slack protips and 2 bonus chapters. I am sure you will find them useful.
Responsible Rails got one new story. Robert wrote how Resque and Ruby together trolled us, leading to orphaned temporary files on utility servers. As usually there is also an advice how to avoid that kind of problems in the future by using certain coding patterns.
I've been using mutation testing recently to cover an existing Rails application with tests. Expect more on this topic soon! Mutation testing is one of my long-term programming improvements.
The order in which requirements are implemented can have a huge impact on the success of the project. I often recommend a radical approach of starting from the middle to address this issue.
What does it mean?
It can work on different levels. It works on the whole project, but it can also work on a single feature.
Don't focus on providing the data
Assume all the data involved in the feature already exists. Don't focus on the way the data is provided. Don't implement the importing feature at the beginning. Focus on what is possible to do with the data in the context of the project (or feature).
Let's say you implement a system which shows the project and its tasks (a project management tool). Starting from the middle would mean focusing on:
how the project/task views looks like, maybe hardcode the HTML/CSS
what you can do with the tasks
prioritise
assign to people
all the consequences of assigning a task (emails, notifications)
complete the task
Consequently, you're NOT focusing on:
how the people are imported to the system (do they need to register? are they imported?)
how the projects are created
it's not a common task to start projects
authentication
persistence
You try to focus on the actions that are in everyday use of this project (potentially).
What if you really need to use the real data?
This obviously depends on the context of the system. Given the above example, you probably have 2 or 3 initial users of the system. Ask them to provide the data in any way they like. It might be Excel, Google Doc, just an email, whatever. You can quickly insert the data to the system, using the console/database.
Thanks to that you will see what kind of data those people *actually* use. Do they have 100 projects or just 3 of them? Do they have 5 employees or 500? This will guide you how to implement the importing, later on.
Most importantly, you don't waste time on implementing the less crucial parts of the app. Most likely, the importing is not your business advantage.
In the first days, you don't even need the proper persistence - focus on manipulating the existing data, but it doesn't need to be persisted (in the first days).
You don't need the proper authentication, yet. Everybody knows you're able to implement authentication, once it's actually the most important part. Focus on the risky parts, not on the ones, you know how to do.
Technical decisions
In Rails, we often don't know, what the app is about, but we know it's going to be Rails, Devise, RSpec, Postgres.
In Java, it's the same, Spring + JPA/Hibernate + Oracle, before we know anything here.
If it's a web app, consider starting with the frontend app, but don't go into Angulars/Embers or any other heavy framework. Make it a lightweight HTML + some simple views. They can be jQuery-based or React. You will change it later.
Starting from the middle is not only a prototyping tool. Starting from the middle is a business thinking tool.
What in our app/feature is the most important/risky part?
How can we fake the other parts? How can we defer some of the less important decisions?
Defer
BTW, one practical tip - when a software project starts, many people care a lot about many things, especially the technical decisions. You may spend days debating rspec vs minitest or rails-api vs sinatra. Somehow, those are the most important decisions at the beginning. Everything changes after 2-3 months. Suddenly, introducing minitest is not a big deal. It's no longer so important that one part of our app will be React-based and not Angular. It still matters, but the discussions are not so heated.
Use it to your advantage - suggest deferring decisions "until the next week/month". Try to move the discussion to the core of the requirements, at this stage of work.
Let's say, you're adding an analytical feature to the app. You want to collect a lot of data about the users behaviour and then implement a dashboard which show nice things.
Collecting analytical data is not really risky (maybe the performance part). Focus on what kind of nice things you want to display. What should be highlighted in red? Do we needs some alerts here? You will quickly realise that those things can guide the collecting phase. It will be easier to decide on the ways the collecting needs to be done. Do we need to collect data in batches? How real-time does it need to be?
Detecting actors early
What's most important, by focusing on how we want to use the data, we need to start thinking in terms of actors. Who is going to use the data? Do we have access to those people? The earlier we try to find them, the better. We try to localise the places, which are not under our (developers) control. Fix them first. Then you're on your own implementing the stuff that is fully under your control.
Sometimes, it's just a small gain. You start the feature by visualising the data. You send it to the people involved. They can start discussing it (often takes days). Meanwhile, you can work on the persistence or authentication foundations. When they're back with some ideas of changes, you will need to do them, but at least you were not fully blocked.
If you did it the other way round - spending 3 days on persistence/backend/authentication and then implementing the main screen - you would need to wait longer now. This is using concurrency to your advantage.
Another example - you have a working system. Now, it's time to make it a SaaS. You need to make it a multi-tenant app. This is a big deal. This is not a 3 days work. There's lots of things to consider. Starting from the middle would require thinking in terms of already existing data. Don't focus (yet) how to let new companies/tenants to register. Try to turn the existing system into a tenant system, from the middle. Does it mean, we need to assume we already have subdomains? How do we persist/isolate the tenants data? Try to hardcode other tenants data. This will all guide you how to turn the app into a SaaS in a safer way.
Starting from the middle is pivot-friendly
If you work in a fast-changing environment there might be situations where a decision to drop the idea appears. You work on a feature X, you spend 1 week on it. Suddenly, the board decides that X is no longer important. We now need Y. If you started from the middle, there's no inconsistent features that you need to disable because they totally don't make sense. Yes, you still need to clean up the code, but at least there's no need to remove the import form, as there was no such thing yet.
I use this technique to blogging. I timebox 30 minutes and I start from the middle. What are the most important messages I want to send? Make them headers. Add 1-2 sentences to explain them. When the time is over, the blogpost is shippable. In fact, it's always shippable. If I have more time, I expand the explanation.
When to stop working in the middle?
Once you have all the unknowns clear - then it's time to go back, to the beginning. We're now totally sure what our app is for. How it solves the users problems. We know the actors. We know the data specifics.
Now it's a good start to implement the missing infrastructure.
In one of my projects, we implemented the whole app as a Single Page App with no backend at all. We collaborated with the actors, the app was tested and validated. Once all the frontend was settled, we knew the data we needed. Implementing the backend took us only 2 days. It was crystal clear. We started with the client needs first, then we knew what we needed. If we started with the backend, we would be guessing about the needs.
This is a lot like TDD, but on a different level.
Gradual migration of persistence
Let's say you start with a JavaScript frontend. You show "the middle" screens. You implement the most important actions (frontend-only). This is enough for the basic testing.
Now the users may want to actually "save" the data somehow, so that when they're back the next day, the data is still there.
You can implement it using the local storage. The simplest technique? Just dump all the data graph to the local storage after every change. It's not the proper way, but it solves the *current* problem.
Later on, the users may want to access the data from different devices. You now need to have some backend. You can even follow the same strategy and just dump the data to the backend as a json blob.
The next requirement is usually to be able to collaborate on this data with other users, so they can work on it at the same time. Now it might be time to be more granular with how the data is persisted. You can now extend the API with more endpoints for different actions.
Ultimately, you may need to make the collaboration smoother by pushing the data to the clients using WebSockets.
Code is not a stone
Starting from the middle is a great exercise for continuous refactoring. All the code keeps changing and you're not afraid to do so. It becomes a natural habit. Your work become a sequence of small steps, gradual improvements.
Outsourcing less important parts
Starting from the middle teaches about outsourcing some work to other tools or even other teams. Do we even need our own authentication? Is Twitter login enough? Do we need our own persistence? Do we need to implement the import forms? Maybe there's already a tool which the users can use to "click" all their data and then export it to xml? Maybe there are some standards in this industry?
Optimise for the developer brain
What I often see is a number of features in the "importing" process. Those are features that may be useful, but only at the beginning of the user lifecycle in the project. However, they complicate the UX forever. You have some checkboxes which make sense to be displayed on each line item, but it's only realistically use in the first days. Even if they're already implemented, I recommend considering to drop them. Code is maintenance cost. Even if the code is not changed that often it brings the mental overhead to load the codebase to the developer brain.
It may sound stupid to optimise the features for the developer brain. However, it's less stupid, when you think that it's the developer who is then able to come up with simpler/faster solutions, thanks to the fact that the whole thing is now simpler.
Summary
Starting from the middle is like a framework of business thinking. It constantly challenges you on the best ways of using your time.
All of the architectural approaches in software engineering aim to solve similar problems. One of the techniques that is often used is the concept of roles.
When you do OOP, you may use interfaces (especially with statically typed languages). Interfaces help us thinking in roles - in this context this is the role the object plays.
The DCI architecture is opposing the current class-oriented thinking. They aim to introduce the full OOP which is object based, not so class-based as we're used to now. To simplify things a bit, in DCI you should be able to implement an interface on an object basis, not on the class level.
For example, if you have an ebook object, you might want to temporarily treat it as "a digital product" in the Shipping context (where it's delivered via email to the customer). Not all ebooks are being delivered at the same time, so there's no point in all of them (at the class level) having the shipping abilities. For the time, when it's important to ship the digital product, the ebook has a new role. When it's used in another context (listed in a catalog?) it doesn't have the role - it doesn't have some methods.
That's the DCI conceptual way of using roles.
Now, what is the DDD way?
In DDD, we use the concept of bounded contexts. They represent the boundaries in which a different language is used. In a way, it's similar to DCI Contexts.
In DDD, we would have the Catalog context and the Shipping context as well. Similarly we will have the concept of ebook and digital products. They are not called roles, here, though. Also, it's probably more of an implementation detail, but most DDD implementations would have different objects (different identities) for the ebook (as in Catalog) and for the digital product (as in Shipping).
The ebook and the digital product represent the same thing and very likely they are aggregate roots in their bounded contexts. Keeping them as separate objects (often, with their own separate persistence) requires some synchronisation mechanism. If the ebook title changes, it may need to be changed in all contexts. In DDD, domain events are one way of ensuring the (often eventual) consistency.
As you see, different kinds of architectures need to solve similar problems. Here, both DDD and DCI use the role-oriented thinking. The implementations are different, though.
Let me start with a disclaimer that I'm new to the Volt framework. I probably get some things wrong. Don't rely on my opinions here :) The reason I'm blogging about it is to document my learning process. Also, by blogging I hope that dear readers will catch my mistakes and correct them in the comments. Thanks to that I'll learn more and can share a better knowledge in the future blog posts :)
I've played with Volt today. For those of you who don't know - Volt is a Ruby web framework. It's a bit unusual, as it's an isomorphic framework. The code is shared between the server and the client. It works in the browser thanks to the Opal - a ruby to javascript compiler (source to source). In a way, it's similar to Meteor.
Opal
I'm quite excited about Opal and the way it can influence the Ruby/Rails community. One year ago, I've recorded my suggestion/prediction that Opal can become the default JavaScript implementation in Rails apps. Here's the short (1 minutes) video of me science-fictioning about it, while walking in the forest near my home :)
I'm not following the Opal progress very closely, but from seeing what's possible in Volt, I feel quite confident about my last-year prediction.
I'll focus on Opal in another blog post. For now, let me come back to Volt.
The wow effect
I came to Rails very early, in 2004. It was due to the "wow effect". Being experienced with PHP, Java, ASP.NET it was amazing to see a result produced in such a short amount of time with so short codebase!
Over time, I learnt a lot about JavaScript frontends. Here is a 12-blog-posts summary of my lessons. I know what it takes to develop a nice Single Page Application (or however you prefer to call it).
This is the Volt wow effect. I'm old enough not be excited too easily with new shiny toys, but Volt does impress me. In almost no time, you've got a rich frontend, which works as you'd expect from a good JavaScript application - fast, interactive. You get the backend basically for free. It all autosyncs with the backend without any effort from your side. What's more, a new Volt app comes with authentication built-in for free.
Part of the wow effect is the fact that you don't need to run any database migrations to add new fields. It all relies on Mongo under the hood, so it's schema-less.
One of my roles is to be a Ruby/Rails teacher / coach and I know how important it is to have a great "wow effect" for the students.
The developer happiness
Another thing that brought me to Rails was its developer oriented approach. The small things like console, like migrations (at that time it was huge), like seeing everything immediately on the screen without restarting the server. It was all there. It was all prepared to be very productive for the developer.
Volt is the same. There's lots of details that work very well. It's hard to mention them all. For me, it was great to see the browser with live reload out of the box. I change some Ruby code and it immediately pushes the code to the browser and the page is reloaded. I know there are more tools like that, but seeing it work with Ruby in the tool chain is just great.
The documentation
I followed the tutorial (try it, you'll get a better picture of it in 15 minutes, than from reading this post ;) ) and browsed through the documentation. All very precise, very simple. I didn't find any inconsistency with the actual behaviour.
The codebase
I only scanned some parts of the code and it seems like very well written code. I was curious how the "reactive DOM" thing works here. It was easy to find the right piece of code and understand how the "watching" works. When I browse the Rails codebase, I often feel lost as it depends on a lot of metaprogramming. I didn't feel the same here (yet).
BTW, yesterday I tried to run Opal with ReactJS but couldn't make it. There's probably no reason, why it wouldn't work, though, given more time. If you know anyone who did that successfully, I'd love to see the result :)
Components
I'm not experienced enough to see all the details, but I love the focus on components, from the beginning. Often, in Rails apps we think that some pieces may be extracted as an engine/component/module/gem *one day* and it rarely happens. Here, we're encouraged to do it from the beginning. The Volt components can be packaged as gems, which is quite interesting.
The architecture of a Volt app
As much as I'm impressed by Volt being a great framework for scaffolding/prototyping (maybe even better than Rails one day?), I also have some doubts.
A lot of my work in the last years was about decoupling legacy Rails applications. I wrote a book about Refactoring Rails apps and I keep collecting techniques which make it easier. Coupling is bad.
Is sharing the code between server and client a coupling? The answer to this question will be very important for me.
In a way, good decoupling may result in great reusability. There are applications, where the server role is simply to keep the data and to ensure basic authentication/authorisation rules. There are however also apps, where the server logic is much different from the client applications.
Is reusability the goal in itself? I don't think so. It's more of a nice side effect of good separation of concerns.
What's the Volt model of code reuse? At the moment, I'm not really prepared to answer this question easily. It seems to be, that at any moment, you can easily switch off the coupling, when required. That's a good sign. It's all Ruby under the hood, so we can do what we want.
Community
In case of frameworks, it's not always their authors vision, which drives how the apps are created. There's many good things in the Rails framework. Rails itself (nor the Rails core team) doesn't force you to write monolithic application - yet, that's what many people do. The main Volt author (Ryan Stout) seems to be very modularity-oriented. From what I see in the Gitter activities, this vision is shared by the early adopters. That's a good sign.
The future?
Arkency (my company) became experts in dealing with legacy Rails apps. We know how to decouple the app, step by step, in a safe manner. Are we going to become "legacy Volt apps experts" in 5 years from now? Time will tell... I have positive feelings about it. It's great to see new players in the Ruby community. There's no way it will become more popular than Rails, however competition is good. Volt is so different from Rails, that the Merb-case is not vere likely here ;)
I'm not recommending starting serious apps with Volt (yet). However, the framework is mature enough to be tried. In only 15 minutes you can build the Todo app from the tutorial. At least it will show you how the process of working can look, in the future and how different it is from your everyday flow.
As for me, I'm off to start another side-project with Volt now. Follow me on Twitter to see how it goes :)
I've collected a number of my blog posts, which together may help you in the transition from Rails to JavaScript (or CoffeeScript) frontends. Many of the posts are about the mental transition you need to make along the way, while other posts are more technical.
I think it's best to read them in the order I put it here, as they present the road I took over the last 5 years.
Over the last 4-5 years, I keep being asked this question - Which JavaScript framework should I use?
2.5 years ago, I had a talk at the RuPy conference, where I'm trying to answer this question. I realised, I've never posted the video to my blog. 2.5 years is like a century in the IT world. Everything has changed, right? I believe that my answer is still correct and all what I said is still true.
Splitting a controller into separate files may be a good idea in certain cases.
Let me start with the cases, when it's NOT a good idea. In case of a simple CRUD controller, this may be an overkill. The same applies to simple, scaffold-like controllers.
Some controllers are more resource-oriented - those may benefit from keeping the actions together in one file. People often use filters for reusing certain rules of accessing the resources here.
Other controllers may be more action-oriented (as opposed to resource-oriented). It's typical for the your-main-model-controllers. In those controllers, you will see more than the standard actions. They will contain interesting logic for 'update', 'create', 'destroy'. Those actions may have different logic. Often, they don't share that much.
The action-oriented controllers may benefit from splitting them into several files. I call them SingleActionControllers.
Please, note that the action-oriented controllers can contain a more complicated set of rules in the controller filters. Often, they create a full algebra of filters, where you need to find your way through the :except clauses and dependencies between filters. Refactoring those places requires a really precise way of working.
Apart from the filters problem, extracting a SingleActionController is easy and consists of the following steps:
1. A new route declaration above the previous (first wins)
post 'products' => 'create_product#create'
resources :products, except: [:create]
2. Create an empty controller CreateProductController which inherits from the previous
3. Copy the action content to the new controller
4. Remove the action from the previous controller
5. Copy the filters/methods that are used by the action to the new controller
6. Make the new controller inherit from the ApplicationController
7. Change routes to add 'except: [:foo_action]'
You can also see a simple example of this refactoring here:
Some time ago, I blogged about "Unit tests vs Class tests", where I described how it differs when you think of a unit more as a collection of classes instead of a single class.
How can we TDD such a unit?
This reddit thread triggered me to explain it a bit more. The title of the thread was interesting: "How does one TDD a user and session model?"
Such question is a good example, how the class tests seem to be the default in the "unit testing" thinking. Instead of asking how to TDD the Authentication unit, we're asked how to TDD some single classes - which often depend on each other.
Here is my response:
One way to do it is to think one level higher. Don't focus on the User and Session classes, but focus on the whole feature/module that you're building. In this case, that's probably Authentication, right?
Think of Authentication as a class, which is a facade. It's very likely, that you will come up with User and Session classes, sooner or later. They will be more like implementation details.
You probably need to implement functions like:
register
logging in
logging out
change_password
change_email
change_login
remember_me
delete_user
Together they can make nice stories. You can start with a simple test like:
(if you don't like exceptions, you can turn it into returning true/false instead)
One by one, you could build the whole feature this way, while being test-driven all the time.
In a Rails app, you will then use this Authentication object from the Rails controllers. This is also a good way of improving Rails controllers.
This approach is sometimes called top-down. You start with the expectation from the higher module (Authentication) and this drives you to implement the lower-level classes like User and Session.
Another approach is called bottom-up, where you start with the User and Session classes from the beginning and don't worry with the wrapping module, yet.
The different approaches may result in different tests. With the top-down approach you end up with module tests, while with bottom-up most likely you end up with a test per class.
When working on my book about refactoring Rails controllers, I've collected a number of "unusual" Rails solutions from real-world projects. They were either sent to me from the readers or I found them in some open-source Rails projects.
One of them was the idea of using instance_variable_get.
instance_variable_get in the Redmine project
I was working on one of the most important recipes from my book - "Explicitly render views with locals". It's important, as it opens the door to many other recipes - it makes them much simpler.
When I was practising this recipe, I tried to use it in many different projects. One of the open-source ones was Redmine. I applied this refactoring to one of the Redmine actions. I've searched through the view to find all @ivars and replaced them with an explicit object reference (passed to the view through the locals collection). This time it didn't work. The tests (Redmine has really high test coverage) failed.
The reason was a helper named error_messages_for(*objects). It's used in many Redmine views.
The idea behind this is to display error messages for multiple @ivars in a view. As you see, it uses the instance_variable_get calls to retrieve the @ivar from the current context. It's only used in cases, when a string is passed and not an @ivar directly.
This situation taught me 3 lessons.
1. I need to test my recipes in as many projects as possible to discover such edge cases.
2. A high test coverage gives a very comfortable situation to experiment with refactorings.
3. Ruby developers are only limited by imagination. The language doesn't impose any constraints. It's both good and bad.
I've extended the recipe chapter with a warning about the instance_variable_get usage:
The lifecycle of a Rails controller
After some time, I was working on another chapter called "Rails controller - the lifecycle". In that chapter I describe step by step what is happening when a request is made, starting from the routes, ending with rendering the view. Each step is associated with a snippet of the Rails code.
Everybody knows that if you have assign an @ivar in a controller, it's automatically available as an @ivar in the view object, even though they are different objects.
The trick here is that the Rails code uses instance_variable_get as well. It grabs all the instance variables and creates their equivalents in the view object using the instance_variable_set. Here is part of the chapter:
Easier debugging in rubygems code
In the rubygems code you will find this piece of code. It's a way of displaying the current state (@ivars) of the object. At first, it may look useful. I'm sure it served well for this purpose. However, the root problem here is not how to display the state. The root problem is that the state is so big here that it needs a little bit of meta programming to retrieve it easily. Solving the root problem (splitting it into more classes?) would probably reduce the need for such tricks. When it's useful?
instance_variable_get is a way of bypassing the object encapsulation. There's a good reason that in Ruby we hide some state using @ivars. Such a hidden state may or may not be exposed in some way. I'd compare instance_variable tricks to using the "send" method.
My rule of thumb is that it's sometimes useful when you build a framework-like code. Code that will be used by other people via some kind of DSL. Rails is such an example. I'm not a big fan of copying the @ivars to the view, but I admit it plays some role in attracting new people to Rails.
However, it's a rare case, that instance_variable_get may be a good idea to use in your typical application.
The temptation to use it is often related to reducing the amount of code. That's a similar temptation to using the "send"-based tricks. I'm guilty of that in several cases. I learnt my lessons here. It might have been "clever" for me, but it was painful for others to understand. It's rarely worth the confusion.
I've seen enough legacy Rails projects to know, that sometimes the instance_variable_set trick is useful in tests. It's still "cheating", but it may be a good temporary step to get the project on the right path. Some modules/objects may be hard to test in isolation in a legacy context. Sometimes, we may need to take an object (like a controller) and set some of its state via instance_variable_set at the beginning of the test. It's similar to stubbing. This way we may be able to build a better test coverage. Test coverage on its own doesn't mean anything. However, good test coverage enables us to refactor the code, so that it no longer needs meta programming tricks.