Tag: RoR

Ruby programmers/project managers/CEOs Y U NO enforce code quality?

Introduction

This post is a result of recent events with one of the companies that I work with. Sometimes, I help people because I want, sometimes because they pay me, mostly both ;). This time I've been helping one company decide on how to outsource their software development. They have many applications, some of them are old, some of them are younger, they constantly build something new as well - a never-ending story. I didn't want to get into the sole "picking the right company" part of this deal. I was not responsible for financial or any other non-programming part of this process, so instead of recommending companies I focused on something else: How to ensure the same (or better) quality, without having a dedicated team working close to all other people related to a given project.

I've spent some time studying their software, what they do, how they work and what their goals are. Also I've talked with their developers about their feelings and thoughts about all of this. I've spoken to other employees as well, and one thing struck me:

(Obrazek JPEG, 480×480 pikseli)

No one really cares! As long as it works, why would they? Programmers didn't have enough time (and sometimes knowledge), project managers didn't care as long as it worked (or also didn't know how to measure things like that), CEO thought that since there are so many apps, it is impossible to do this without a big overhead (and that this would cost a lot of money).

Low quality is just a way to postpone costs that later will be much higher

I can understand why CEOs can think like that. What I don't understand is why project managers and CTOs don't explain this to them. If CEO doesn't care, no one will be able to. And this will turn against everyone one day. Many poorly written projects are hard to give away. The more complicated they are, the more price grows.

Quality always costs. There's no doubt about that. But if you ensure it from the beginning, the further you go with your project the more you will gain. And I'm not talking only about money. Keep in mind, that people don't like other peoples code. And working with poorly written code is what they hate. Low quality equals a much higher rotation rate in teams that have to work with such code. Also keep in mind that everything changes. Programmers change their jobs, some companies are being closed, some are being created, and if you want to outsource stuff, you need to always be aware of that. The only thing that should not change from your perspective is your code quality.

It's always expensive to transfer knowledge and know-how

Outsourcing is not easy. Your team needs to transfer all the know-how to some other places and people. The more code there is, the harder it gets. But what could you do to make it go faster, easier and cheaper?

Ensure code quality!

You could ensure some code and documentation standards across all the firms that will work on your software. That way you can diversify across few that will be able to work with others code. If all of them share same principals, their work quality should be similar, their code should be readable for all of them and you could rotate across those companies according to your business needs. Further more you could even specify in the contract what you expect and what they need to obtain in terms of quality. Programmers will still make mistakes, things will still not work the way you wanted but even then standards will allow other people to fix those issues without any unexpected impact on the whole project. If you set the standards, programmers will have a bit less freedom in how they work and what they do. It's not always good but it is one of those nonfinancial costs you need to take.

Few tools you can use in order to move forward

Some "code related" tools that can help you out with Ruby based software:

  • Rspec (or any other test framework),
  • Simplecov,
  • YARD,
  • Rubocop.

Rspec (or any other test framework)

This is quite obvious. Tests are must be for any application that we want to maintain. Working with not tested software can be a nightmare. But how to measure tests/specs quality?

Simplecov to the rescue

We could (and we should!) use Simplecov. SimpleCov is a code coverage analysis tool for Ruby.

687474703a2f2f636f6c737a6f776b612e6769746875622e636f6d2f73696d706c65636f762f6465766973655f726573756c742d302e352e332e706e67

Picture taken from official Simplecov Github page

It will show you any flaws of your test suites. Not tested classes, methods, cases. It will also generate some useful statistics. If you require usage of this library from your contractors, you can include coverage level as one of parts of your contract. That way they will be obligated to keep tests up and running all the time. Of course they will have to agree on that, because if you force them to use it, they may just write tests that will "pass" Simplecov statistics, delivering you green, low quality tests/specs.

YARD - Yay! A Ruby Documentation Tool

I like self-explaining code. Ruby code can be written both in good and bad way. Unfortunately self-explaining code won't answer all the questions. For example, it won't answer why something is done that way, where there might be a (theoretically) better solution. Also an outsourced code often changes maintainers. None of them will pass all the knowledge to a next one. In cases like that I recommend full YARD documentation. At least on a module/class and public methods level.YARD is a documentation generation tool for the Ruby programming language. It enables the user to generate consistent, usable documentation that can be exported to a number of formats very easily

Some programmers write comments, some don't. But even if they do, without standardization, it's hard to read and maintain. If you use YARD, none of this will happen. You will end-up with a consistent, standardized documentation that can be viewed both online and offline. You can also easily monitor YARD doc coverage level by running:

yard stats --list-undoc

For example, you may get such result:

Files:          21
Modules:         5 (    0 undocumented)
Classes:        25 (    0 undocumented)
Constants:       8 (    1 undocumented)
Methods:        51 (    0 undocumented)
 98.88% documented

Undocumented Objects:

(in file: app/models/credit_card.rb)

Then it is quite clear that in file credit_card.rb some elements are undocumented and that developers should update docs. As you can see, YARD allows us to easily measure and monitor comments quality.

RuboCop - A Ruby static code analyzer, based on the community Ruby style guide

RuboCop is a Ruby static code analyzer. Out of the box it will enforce many of the guidelines outlined in the community Ruby Style Guide. So, instead of having code written in many ways, you can define a file called .rubocop that will contain all the rules about Ruby code style. That way, you can ensure same standards across many applications/dev teams/companies. It will notify you if anything is not as it should be.

.....C.................................................C......................C......

Offenses:

app/controllers/portal/base_controller.rb:40:3: 
C: Cyclomatic complexity for respond_with is too high. [8/6]
  def respond_with(*resources, &block)
  ^^^
app/controllers/portal/base_controller.rb:40:3: 
C: Method has too many lines. [18/15]
  def respond_with(*resources, &block)
  ^^^
app/controllers/application_controller.rb:52:3: 
C: Cyclomatic complexity for respond_with is too high. [8/6]
  def respond_with(*resources, &block)
  ^^^

Summary

Code quality is really important. It's also a topic for more than a blog post, but I hope you will consider this article useful. Tools that I've presented won't replace good developers but they can help you out omit troubles. If you use them wisely, you can ensure similar code quality in many dev teams.

Upgrading to Rails 4.1 from Rails 4.0 – Ruby on Rails

Upgrading to Ruby on Rails 4.1 was much easier than moving from 3.2 to 4.0. Maybe because I try to keep all the apps up-2-date, maybe because Rails guys didn't change much stuff ;) (or maybe both). Either way, lets get through it.

Paperclip - String based terminators are deprecated, please use a lambda

DEPRECATION WARNING: String based terminators are deprecated, please use a lambda. 
(called from has_attached_file at app/config/initializers/paperclip_extensions.rb:22)

Well this one is really simple - just update Paperclip gem:

bundle update paperclip

More about this issue here.

The ability to pass in strings as a class name to set_fixture_class
will be removed

Next deprecation warning:

DEPRECATION WARNING: The ability to pass in strings as a class name to `set_fixture_class`
 will be removed in Rails 4.2. Use the class itself instead. 
(called from block in initialize at gems/activerecord-4.1.0/lib/active_record/fixtures.rb:465)

It you use Rspec you probably won't see this issue at all (or if you don't use fixtures). One of my apps unfortunately still does. Solution to this is really simple. Instead of:

set_fixture_class scanlation_categories:  'Scanlation::Category'
set_fixture_class scanlation_chapters:    'Scanlation::Chapter'
set_fixture_class scanlation_pages:       'Scanlation::Page'

use the class name itself (not its string version):

set_fixture_class scanlation_categories:  Scanlation::Category
set_fixture_class scanlation_chapters:    Scanlation::Chapter
set_fixture_class scanlation_pages:       Scanlation::Page

NameError: undefined method `_run_suite' for class `Test::Unit::Runner'

MiniTest::Unit::TestCase is now Minitest::Test. From /unit/testcase.rb:8:in `<module:Unit>'
rake aborted!
NameError: undefined method `_run_suite' for class `Test::Unit::Runner'
gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `block in require'
gems/activesupport-4.1.0/lib/active_support/dependencies.rb:232:in `load_dependency'
gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
app/test/test_helper.rb:23:in `<top (required)>'

Just get rid of this line from your test/test_helper.rb file:

require 'test/unit'

cannot load such file -- polyamorous (LoadError)

in `require': cannot load such file -- polyamorous (LoadError)
from /activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `block in require'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:232:in `load_dependency'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
from /ransack/lib/ransack/adapters/active_record/context.rb:3:in `<top (required)>'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `block in require'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:232:in `load_dependency'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
from gems/ransack-d51c78f9071f/lib/ransack/adapters/active_record.rb:4:in `<top (required)>'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `block in require'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:232:in `load_dependency'
from /activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
from gems/ransack-d51c78f9071f/lib/ransack.rb:24:in `<top (required)>'
from /bundler-1.5.2/lib/bundler/runtime.rb:76:in `require'
from /bundler-1.5.2/lib/bundler/runtime.rb:76:in `block (2 levels) in require'
from /bundler-1.5.2/lib/bundler/runtime.rb:72:in `each'
from /bundler-1.5.2/lib/bundler/runtime.rb:72:in `block in require'
from /bundler-1.5.2/lib/bundler/runtime.rb:61:in `each'
from /bundler-1.5.2/lib/bundler/runtime.rb:61:in `require'
from /bundler-1.5.2/lib/bundler.rb:131:in `require'
from app/config/application.rb:6:in `<top (required)>'
from app/config/environment.rb:1:in `require'
from app/config/environment.rb:1:in `<top (required)>'
from app/spec/spec_helper.rb:21:in `require'
from app/spec/spec_helper.rb:21:in `<top (required)>'
from app/spec/controllers/portal/announcements_controller_spec.rb:1:in `require'
from app/spec/controllers/portal/announcements_controller_spec.rb:1:in `<top (required)>'
from /rspec-core-3.0.0.beta2/lib/rspec/core/configuration.rb:932:in `load'
from /rspec-core-3.0.0.beta2/lib/rspec/core/configuration.rb:932:in `block in load_spec_files'
from /rspec-core-3.0.0.beta2/lib/rspec/core/configuration.rb:932:in `each'
from /rspec-core-3.0.0.beta2/lib/rspec/core/configuration.rb:932:in `load_spec_files'
from /rspec-core-3.0.0.beta2/lib/rspec/core/command_line.rb:21:in `run'
from /rspec-core-3.0.0.beta2/lib/rspec/core/runner.rb:100:in `run'
from /rspec-core-3.0.0.beta2/lib/rspec/core/runner.rb:31:in `invoke'
from /rspec-core-3.0.0.beta2/exe/rspec:4:in `<top (required)>'
from /home/mencio/.rvm/gems/ruby-2.1.0@senpuu/bin/rspec:23:in `load'
from /home/mencio/.rvm/gems/ruby-2.1.0@senpuu/bin/rspec:23:in `<main>'
from /home/mencio/.rvm/gems/ruby-2.1.0@senpuu/bin/ruby_executable_hooks:15:in `eval'
from /home/mencio/.rvm/gems/ruby-2.1.0@senpuu/bin/ruby_executable_hooks:15:in `<main>'

To remove this issue, upgrade your Ransack, MetaSearch and Squeel gems to newest versions and/or add this to your Gemfile:

gem 'polyamorous', github: 'activerecord-hackery/polyamorous'

ActionView::Template::Error:
 undefined method `reverse!' for #<ActiveRecord::Relation []>
Shared Example Group: "has valid single" 
called from ./spec/controllers/episodes_controller_spec.rb:13
./lib/system/active_record/nearable.rb:54:in `near'
./app/views/portal/episodes/show.html.haml:43:in `block in _app_views_episodes_show_html_haml'
./app/views/portal/episodes/show.html.haml:1:in `_app_views_episodes_show_html_haml'
./app/controllers/application_controller.rb:68:in `respond_with'
./app/controllers/portal/base_controller.rb:64:in `respond_with'
./app/controllers/portal/episodes_controller.rb:13:in `show'
./spec/support/macros/controllers/actions.rb:80:in `block (4 levels) in <module:Actions>'

With ActiveRecord 4.1, you can't call reverse! directly on ActiveRecord::Relation. Example:

# This will throw an error
@articles = Article.limit(10).order('created_at DESC').reverse!

Instead you have to cast ActiveRecord::Relation to an array:

@articles = Article.limit(10).order('created_at DESC').to_a.reverse!

Keep in mind, that casting with to_a will deprive you from all benefits of lazy loading with Rails relations so use it carefully. But on the other hand, reverse! on ActiveRecord::Relation did the same, so if you used it and it was ok, than feel free ;)

Undefined method `graft' for class ActiveRecord::Associations::JoinDependency

gems/activesupport-4.1.0/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method': 
undefined method `graft' for class `ActiveRecord::Associations::JoinDependency' (NameError)
activesupport-4.1.0/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method_chain'
from gems/polyamorous-0.6.4/lib/polyamorous/join_dependency.rb:7:in `block in included'
from gems/polyamorous-0.6.4/lib/polyamorous/join_dependency.rb:5:in `class_eval'
from gems/polyamorous-0.6.4/lib/polyamorous/join_dependency.rb:5:in `included'
from gems/polyamorous-0.6.4/lib/polyamorous.rb:20:in `include'
from gems/polyamorous-0.6.4/lib/polyamorous.rb:20:in `<top (required)>'
from gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
from gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `block in require'
from gems/activesupport-4.1.0/lib/active_support/dependencies.rb:232:in `load_dependency'
from gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
from gems/ransack/lib/ransack/adapters/active_record/context.rb:3:in `<top (required)>'
from gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'
from gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `block in require'
from gems/activesupport-4.1.0/lib/active_support/dependencies.rb:232:in `load_dependency'
from gems/activesupport-4.1.0/lib/active_support/dependencies.rb:247:in `require'

Just install the newest version of ransack gem.

gem 'ransack', github: 'ernie/ransack'

Scoped order and limit are ignored, it's forced to be batch order and batch size

This is quite logical - you can't have some of orders when batching (for example a RAND()). That's why ActiveRecord is ignoring it. Just keep that in mind ;)

Example:

Token.2.1.0 :001 > Token.order('RAND()').find_each{}
W, [2014-05-09T11:26:46.601539 #11569]  WARN -- : 
Scoped order and limit are ignored, it's forced to be batch order and batch size
D, [2014-05-09T11:26:46.698603 #11569] DEBUG -- :   
Token Load (3.7ms)  SELECT  "accounts".* FROM "accounts"   ORDER BY "accounts"."id" ASC LIMIT 1000

Other issues

Well to be honest I didn't have any more issues. I've decided to remove Squeel gem from all of my projects, since it is not currently maintained. Thanks to that I've finally got rid of this irritating deprecation warning:

DEPRECATION WARNING: Core extensions are deprecated and will be removed in Squeel 2.0.
 (called from /app/config/initializers/squeel.rb:2:in `block in <top (required)>')

Summary

Rails 4.1 is not a big step, although it is a required one if you want to upgrade to 4.2 in the future. If you have decent test coverage level, you should not have big issues with this upgrade.

Copyright © 2024 Closer to Code

Theme by Anders NorenUp ↑