Skip to content

Coding Guidelines of the Oregon Digital 2 Project

luisgreg99 edited this page Dec 11, 2019 · 15 revisions

Introduction

Oregon Digital 2 is an application that is build using MOSTLY Ruby on Rails. We try to stick to the Ruby on Rails guidelines for development practices found here. https://github.com/rubocop-hq/rails-style-guide

This style guide is maintained by the rubocop community and is utilized by this project via the rubocop gem.

Rubocop is a gem in which helps enforce a style to maintain consistency between multiple programmers. This style is, however, malleable. If there is a style enforcement that makes no sense, or that we don't follow typically, we can deactivate that in the rubocop.yml file. A simple discussion between the programmers is all it takes to move this forward.

In this document, we will try to lay out the "typical style" that is used throughout the project. Ill try to provide code snippets where necessary and keep it as brief as possible.

Style

Spacing

2 spaces. Not 4. Not 8. Keep it to 2.

  def my_method
    some_code_stuff
  end

Fat Model, Skinny Controller

Keep as much code as possible in the models. Controllers should be short and concise.

Use "Behavior Modules"

Code can be removed from the model and added as a behavior to the model to keep models organized.

class MyModel
  include OregonDigital::MyCoolBehavor
end

Utilize app services where necessary

Mutations, checks, parsing, etc. can be done in small app services found in app/services. Mostly for encapsulation, testing, and DRY code.

If statements should be short

If statements should be no longer than one elsif

if blah
elsif blahblah
else
end

If it is longer than this, use a switch case

case blah
when blah
when ...
...
end

Freeze static strings, arrays, etc.

MYGLOBALSTRING = "mystring".freeze This makes them immutable and ensures their value

Try not to use helpers.

Helpers are more like a way to sweep code under a rug. Use a service in a view instead of a view helper for more explicit and easy to follow code.

Module and Class definitions are capital camel case

class MyClass
end

module MyModule
end

methods are snake case

def my_method
end

Methods should have no more than 3 parameters

If a method needs more than 3, use an options hash or split up functionality more

def my_method(a, b, c)
end

# Or

def my_method(a, opts)
end

Methods no longer than 10 lines long

Methods should be as short as possible. Single Responsibility Principle

If a method has an explicit return, add empty line after

def my_method
  return true if blah?

  more code
end

methods with a ? should always return true or false

Question methods imply truthy value

Use String interpolation

  "mystring#{somevariable}"

not

  "mystring" + somevariable

Single Quotes for string, double quotes for string interpolation

"Hello#{goodbye}" and 'hello'

Use %w %i %r etc for definitions

http://ruby.zigzo.com/2014/08/21/rubys-notation/

Include magic comment at top of file

# frozen_string_literal: true

Comment before class definition

# This class is meant to do this thing
class MyClass
end

Empty Line at the end of file

Always keep an extra empty line at the end of a file. Vim does this automatically by default, so if you use vim you do not need to add an extra empty line.

So how do you tell? Try this one weird trick doctors don't want you to know! Before you commit, do a quick git diff or git diff --cached. Git will alert you if there's no newline at the end of a file:

diff --git a/foo b/foo
new file mode 100644
index 0000000..1910281
--- /dev/null
+++ b/foo
@@ -0,0 +1 @@
+foo
\ No newline at end of file

Indenting for private and protected methods

Dont indent inside of private and protected method sections

private

def my_private_method

not

private
  
  def my_private_method

Spaces inside curly braces

{ hello: 'hello' }

Ruby 1.9 syntax for hashes

{ hello: 'hello' }

not

{ :hello => 'hello' }

Keep blocks as short as possible

Keep them short. Rubocop will yell at you

SOLID

  • S single responsibility principle
  • O Open/Close Principle
  • L Liskov Substitution Principle
  • I Interface Segregation Principle
  • D Dependency Inversion Principle

https://thoughtbot.com/blog/back-to-basics-solid

Test when possible

App services, local objects in /lib/oregon_digital MVC tests. Stuff should be tested. We dont shoot for a specific percentage, but we hover around 90%

Comment when overriding a file

To keep track of overrides we add # OVERRIDE of http://github.com/path/to/override/file.rb above where the override happens. This helps keep track of overrides we have to make. It is also recommended to include a brief description of the override like what is changing and why in addition to the OVERRIDE tag and the path to the base file.

THIS MARKS THE END OF THE CODE STANDARDS

Development Standards

Our development standards are relatively basic

  1. Atomic commits
  • Keep your commits concise
  • Squash your own commits using interactive rebase
  1. Tickets
  • Make sure a ticket exists when working on a problem.
  • File Bug tickets and use labels when possible
  • Descriptive tickets are best tickets (yes I know it doesnt always happen. Try your best)
  1. PRs
  • PRs should address their ticket using References or Fixes and the ticket number
  • PRs should contain QA instructions
  • PRs should be atomic. Fix 1 issue.
  • Descriptive titles for PRs
  • [WIP] in the title of a work in progress PR or use a draft
  • PRs dont belong in the project board
  • Screenshot when possible to show fix. also use licecap for gif creation to show feature.
  1. Docker
  • docker-compose build
  • docker-compose up server
  • docker exec -it od2_server_1 bash
  • docker system prune

OVERRIDES

One issue that we have when working with an open source project that is... close to what we want, but not exact, is we have to override many files. It was outlined in the style guide about certain file overrides. With that being said, sometimes overriding a file cant happen. What do we do in this case? We have one of two options

  1. We create a class of our own under /lib/oregon_digital/ that inherits from that other class you are trying to update. Then in that class you can override the method that needs to be changed. This is a much more safe and useful approach, especially for testing. Plus, if the class changes, we still have our code there, which should continue working.

  2. We override the class directly "like maybe a behavior that is a module" and update it on our side. Modules can be hard to extend and can cause some pain points.

  3. We open up the class in an initializer under config/initializers/hacks using #class_eval and append or update their class there. This is quite dangerous and only should happen as a last ditch effort.