Best Practices
## Learning Objectives
- Write idiomatic Ruby code
- Follow Ruby style guide conventions
- Use Ruby's strengths effectively
- Avoid common pitfalls
## Ruby Style Guide
### Naming Conventions
```ruby
# Variables and methods: snake_case
first_name = "Alice"
def calculate_total
end
# Classes and modules: CamelCase
class UserAccount
end
module PaymentProcessing
end
# Constants: SCREAMING_SNAKE_CASE
MAX_RETRY_COUNT = 3
DEFAULT_TIMEOUT = 30
# Booleans: end with ? or is_
valid? = true
is_active = true
has_children = false
# Bang methods: end with !
def sort!
@data.sort!
end
# Dangerous methods: end with !
def delete!
@record.destroy
end
```
### Indentation and Spacing
```ruby
# Use 2 spaces for indentation (not tabs)
def method_name
if condition
do_something
end
end
# Spaces around operators
total = a + b
result = x * y / z
# No spaces around parentheses in method definitions
def greet(name)
"Hello, #{name}"
end
# Space after commas
array = [1, 2, 3]
hash = { name: "Alice", age: 30 }
```
### Line Length
```ruby
# Keep lines under 120 characters
# If needed, break with continuation
long_method_name(arg1, arg2, arg3,
arg4, arg5)
# Or use backslash
long_result = some_method(arg1) +
another_method(arg2)
```
## Idiomatic Ruby
### Prefer Enumerable Methods
```ruby
# Bad
for n in numbers
puts n
end
# Good
numbers.each { |n| puts n }
# Bad
result = []
numbers.each { |n| result << n * 2 }
# Good
result = numbers.map { |n| n * 2 }
# Bad
found = nil
items.each { |item| found = item if item.valid? }
# Good
found = items.find { |item| item.valid? }
```
### Use Symbol to Proc
```ruby
# Long form
names.map { |name| name.upcase }
# Idiomatic
names.map(&:upcase)
```
### Use ||= Sparingly
```ruby
# Can cause issues with falsy values
value = false
value ||= default # value becomes default (wrong!)
# Safer approach
value = default if value.nil?
# Or
value = value ? value : default
# Or (Ruby 2.3+)
value &&= value # Only assigns if truthy
```
### Parallel Assignment
```ruby
# Swap variables idiomatically
a, b = b, a
# Avoid when not necessary
x = 1
y = 2
z = x + y # Just use separate lines
```
### Conditionals
```ruby
# Ternary for simple cases
status = passed ? "Success" : "Failed"
# Guard clauses
def process(data)
return unless data.valid?
# ... main logic
end
# Single-line modifier
puts "debug" if debug_mode
```
## Common Pitfalls
### Mutating Constants
```ruby
# Bad
DATA = [1, 2, 3]
DATA << 4 # Modifies the original!
# Good - freeze
DATA = [1, 2, 3].freeze
# Or clone when needed
modified = DATA.dup << 4
```
### Shadowing Variables
```ruby
# This shadows outer 'name' accidentally
name = "Alice"
[1, 2, 3].each do |name|
# Now name is 1, 2, 3 in each iteration
end
puts name # "Alice" - still works but confusing
```
### Premature Optimization
```ruby
# Don't sacrifice readability for micro-optimizations
# Bad
result = map { |e| e.to_s } * ""
# Good and clear
result = map(&:to_s).join
```
### Monkey Patching
```ruby
# Risky - can break other code
class String
def is_email?
self =~ /@/
end
end
# Safer - use refinements
module StringExtensions
refine String do
def is_email?
self =~ /@/
end
end
end
```
## Code Organization
### Single Responsibility
```ruby
# Bad - does multiple things
def process_user(user)
validate(user)
save(user)
send_email(user)
log(user)
end
# Good - separate concerns
def register_user(user)
validate(user)
save(user)
end
def welcome_user(user)
send_email(user)
log(user)
end
```
### Method Length
```ruby
# Bad - method does too much
def import_and_process_data
# 100 lines of code
end
# Good - break into smaller methods
def import_data
end
def process_data
end
```
### Class Length
```ruby
# Bad - God object
class UserManager
def create_user
end
def delete_user
end
def authenticate
end
def send_email
end
def generate_report
end
# 50 more methods...
end
# Good - Single Responsibility
class UserService
def create_user
end
end
class AuthenticationService
def authenticate
end
end
class NotificationService
def send_email
end
end
```
## Documentation
### Comments
```ruby
# Good comment - explains WHY, not WHAT
# Retry logic needed because external API can be flaky
MAX_RETRIES = 3
# Bad comment - redundant
# Increment counter by 1
counter += 1
```
### RDoc/YARD
```ruby
# @param name [String] the user's name
# @param age [Integer] the user's age
# @return [User] the created user
# @raise [ArgumentError] if name is blank
def create_user(name:, age:)
end
```
## Performance Tips
### Lazy Evaluation
```ruby
# Bad - creates full array
data = (1..1_000_000).map { |n| heavy_computation(n) }.select { |n| n.valid? }
# Good - processes one at a time
data = (1..1_000_000).lazy.map { |n| heavy_computation(n) }.select { |n| n.valid? }.first(100)
```
### String Building
```ruby
# Bad - creates intermediate strings
html = ""
parts.each { |part| html += part }
# Good
html = parts.join
```
### Use bang methods when appropriate
```ruby
# When you don't need the original
list.sort! # More efficient than list = list.sort
```
## Testing Best Practices
```ruby
# Test behavior, not implementation
# Bad
expect(user.instance_variable_get(:@name)).to eq("Alice")
# Good
expect(user.name).to eq("Alice")
# Use descriptive test names
# Bad
it "test1" do
end
# Good
it "returns the user's full name when first and last are present" do
end
# One assertion per test (generally)
# But compound assertions are okay
expect(user).to have_attributes(name: "Alice", age: 30, valid: true)
```
## Security Best Practices
### Input Validation
```ruby
# Always validate input
def process_order(order_id)
raise ArgumentError, "Invalid order ID" unless order_id.is_a?(Integer)
end
# Sanitize before SQL (use parameterized queries)
# User.where("name = ?", params[:name]) # With ActiveRecord
```
### Avoid eval
```ruby
# Dangerous - never use with user input
eval(user_input)
# Safer alternatives
# For code: parse with Ripper
# For data: use JSON/YAML
```
## Summary
- Follow Ruby naming conventions: snake_case, CamelCase, SCREAMING_SNAKE_CASE
- Use Enumerable methods over manual loops
- Prefer `map`, `select`, `find` over `each` with conditionals
- Use guard clauses for early returns
- Keep methods short and focused
- Write tests for behavior, not implementation
- Use bang methods when mutation is intended
- Avoid monkey patching without refinements
- Document WHY, not WHAT
- Keep lines under 120 characters
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →