Skip to content

Development Guide

VetheonGames edited this page Oct 10, 2025 · 2 revisions

Development Guide

Comprehensive guide for developers working on Source-License, including setup, architecture patterns, coding standards, and contribution guidelines.

🚀 Getting Started

Development Environment Setup

Prerequisites

  • Ruby: 3.4.7 or higher
  • Git: Version control
  • Database: MySQL, PostgreSQL, or SQLite for development
  • Text Editor: VS Code, RubyMine, or your preferred editor
  • Terminal: Command line access

Quick Setup

  1. Clone and Setup:

    git clone https://github.com/PixelRidgeSoftworks/Source-License.git
    cd Source-License
    bundle install
  2. Environment Configuration:

    cp .env.example .env
    # Edit .env with development settings
  3. Database Setup:

    # For SQLite (development only)
    DATABASE_ADAPTER=sqlite
    DATABASE_NAME=source_license_dev.db
    
    # Run migrations
    ruby -r './lib/database.rb' -e "Database.setup; Database.migrate"
  4. Start Development Server:

    ruby launch.rb
  5. Run Tests:

    ruby run_tests.rb

Development Tools

Code Quality Tools

# RuboCop - Ruby style guide enforcement
bundle exec rubocop

# Auto-fix style issues
bundle exec rubocop -A

# Run with specific configuration
bundle exec rubocop --config .rubocop.yml

Test Coverage

# Run tests with coverage
ruby run_tests.rb

# View coverage report
open coverage/index.html  # macOS
xdg-open coverage/index.html  # Linux

🏗️ Architecture Patterns

MVC Architecture

Source-License follows a modular MVC pattern with clear separation of concerns:

Controllers (lib/controllers/) → Handle HTTP requests and routing
Models (lib/models.rb) → Database entities and business logic
Views (views/) → ERB templates for HTML rendering

Controller Organization

Base Controller Pattern

# lib/controllers/base_controller.rb
module BaseController
  def self.included(base)
    base.extend(ClassMethods)
  end

  # Common helper methods
  def authenticated?
    session[:admin_id] || session[:user_id]
  end

  def require_admin_auth
    redirect '/admin/login' unless admin_authenticated?
  end

  module ClassMethods
    def setup_routes(app)
      # Define routes in each controller
    end
  end
end

Specialized Controllers

# lib/controllers/admin_controller.rb
module AdminController
  include BaseController

  def self.setup_routes(app)
    app.get '/admin' do
      require_admin_auth
      erb :'admin/dashboard', layout: :'layouts/admin_layout'
    end

    app.post '/admin/login' do
      # Authentication logic
    end
  end
end

Model Patterns

Base Model with Common Functionality

# In lib/models.rb
module BaseModelMethods
  def self.included(base)
    base.extend(ClassMethods)
  end

  def before_update
    super
    self.updated_at = Time.now if respond_to?(:updated_at)
  end

  def to_hash_for_api
    values.reject { |k, _| k.to_s.include?('password') }
  end

  module ClassMethods
    # Class-level methods
  end
end

Model Relationships

class User < Sequel::Model
  include BaseModelMethods
  
  set_dataset :users
  one_to_many :licenses
  
  # Instance methods
  def active_licenses
    licenses_dataset.where(status: 'active')
  end
end

Service Layer Pattern

License Generation Service

# lib/license_generator.rb
class LicenseGenerator
  def self.generate_license(product, customer_email, options = {})
    # Generate cryptographically secure license key
    license_key = generate_key(options[:format])
    
    # Create license record
    license = License.create(
      license_key: license_key,
      product_id: product.id,
      customer_email: customer_email,
      max_activations: options[:max_activations] || product.max_activations,
      expires_at: calculate_expiration(product, options)
    )
    
    # Send notification email if configured
    send_license_email(license) if options[:send_email]
    
    license
  end

  private

  def self.generate_key(format = 'XXXX-XXXX-XXXX-XXXX')
    case format
    when 'XXXX-XXXX-XXXX-XXXX'
      generate_segmented_key
    when 'UUID'
      SecureRandom.uuid
    else
      generate_custom_key(format)
    end
  end
end

Security Patterns

Authentication Helpers

# lib/auth.rb
module AuthHelpers
  def admin_authenticated?
    session[:admin_id] && Admin.where(id: session[:admin_id], status: 'active').first
  end

  def current_admin
    @current_admin ||= Admin[session[:admin_id]] if session[:admin_id]
  end

  def require_admin_auth
    unless admin_authenticated?
      session[:return_to] = request.path_info
      redirect '/admin/login'
    end
  end

  def check_admin_permissions(required_permission)
    admin = current_admin
    halt 403 unless admin&.has_permission?(required_permission)
  end
end

API Authentication

# lib/enhanced_auth.rb
module EnhancedAuth
  def authenticate_api_request
    token = extract_jwt_token(request)
    return halt 401, json(error: 'Authentication required') unless token

    payload = decode_jwt_token(token)
    @current_user = User[payload['user_id']] || Admin[payload['admin_id']]
    
    halt 401, json(error: 'Invalid token') unless @current_user
  rescue JWT::DecodeError
    halt 401, json(error: 'Invalid token')
  end

  private

  def extract_jwt_token(request)
    auth_header = request.env['HTTP_AUTHORIZATION']
    auth_header&.match(/Bearer (.+)/)&.[](1)
  end
end

🗄️ Database Development

Migration System

Creating Migrations

# lib/migrations.rb
class DatabaseMigrator
  MIGRATIONS = [
    {
      version: 1,
      description: 'Create initial tables',
      up: -> {
        create_table :users do
          primary_key :id
          String :email, null: false, unique: true
          String :password_hash, null: false
          String :name
          String :status, default: 'active'
          Boolean :email_verified, default: false
          DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
          DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
        end
      },
      down: -> {
        drop_table :users
      }
    }
  ].freeze

  def self.run_migrations
    # Migration execution logic
  end
end

Model Validations

class Product < Sequel::Model
  include BaseModelMethods

  def validate
    super
    errors.add(:name, 'cannot be empty') if !name || name.strip.empty?
    errors.add(:price, 'must be greater than or equal to 0') if !price || price.negative?
    errors.add(:license_type, 'must be one_time or subscription') unless %w[one_time subscription].include?(license_type)
  end

  def before_save
    super
    self.name = name.strip if name
    self.created_at ||= Time.now
  end
end

Query Optimization

Efficient Queries

# Good: Use dataset methods for filtering
def active_licenses_for_product(product_id)
  License.where(product_id: product_id, status: 'active')
         .order(:created_at)
         .limit(100)
end

# Good: Use joins for related data
def licenses_with_products
  License.join(:products, id: :product_id)
         .select_all(:licenses)
         .select_append(:products__name___product_name)
end

# Avoid: N+1 queries
licenses = License.all
licenses.each { |license| puts license.product.name }  # Bad

# Better: Eager loading
licenses = License.eager(:product).all
licenses.each { |license| puts license.product.name }  # Good

🧪 Testing

Test Structure

Source-License uses Minitest for testing with the following structure:

test/
├── test_helper.rb          # Test configuration and helpers
├── factories.rb            # Test data factories
├── app_test.rb            # Application integration tests
├── models_test.rb         # Model unit tests
├── auth_test.rb           # Authentication tests
├── security_test.rb       # Security tests
└── html_reports/          # Coverage reports

Test Configuration

Test Helper Setup

# test/test_helper.rb
require 'minitest/autorun'
require 'minitest/pride'
require 'rack/test'
require 'database_cleaner'

# Set test environment
ENV['APP_ENV'] = 'test'
ENV['DATABASE_ADAPTER'] = 'sqlite'
ENV['DATABASE_NAME'] = ':memory:'

# Load application
require_relative '../app'

class MiniTest::Test
  include Rack::Test::Methods

  def app
    SourceLicenseApp
  end

  def setup
    DatabaseCleaner.start
  end

  def teardown
    DatabaseCleaner.clean
  end
end

Writing Tests

Model Tests

# test/models_test.rb
require_relative 'test_helper'

class ModelsTest < Minitest::Test
  def test_user_creation
    user = User.new(
      email: 'test@example.com',
      password: 'secure_password'
    )
    
    assert user.valid?
    user.save
    assert user.id
    assert user.password_matches?('secure_password')
  end

  def test_license_validation
    product = create_product
    license = License.new(
      license_key: 'TEST-KEY-123',
      product_id: product.id,
      customer_email: 'customer@example.com',
      max_activations: 3
    )
    
    assert license.valid?
    assert_equal 3, license.remaining_activations
  end
end

Controller Tests

# test/app_test.rb
require_relative 'test_helper'

class AppTest < Minitest::Test
  def test_homepage_loads
    get '/'
    assert_equal 200, last_response.status
    assert_includes last_response.body, 'Source-License'
  end

  def test_admin_login_required
    get '/admin'
    assert_equal 302, last_response.status
    follow_redirect!
    assert_includes last_request.url, '/admin/login'
  end

  def test_license_validation_api
    license = create_license
    
    get "/api/license/#{license.license_key}/validate"
    assert_equal 200, last_response.status
    
    data = JSON.parse(last_response.body)
    assert_equal true, data['valid']
    assert_equal 'active', data['status']
  end
end

Security Tests

# test/security_test.rb
require_relative 'test_helper'

class SecurityTest < Minitest::Test
  def test_csrf_protection
    # Test CSRF token validation
    post '/admin/products', {
      name: 'Test Product',
      price: 99.99
    }
    
    assert_equal 403, last_response.status
  end

  def test_sql_injection_protection
    # Test parameterized queries
    get "/api/license/'; DROP TABLE licenses; --/validate"
    assert_equal 404, last_response.status
  end

  def test_xss_protection
    user = create_user(name: '<script>alert("xss")</script>')
    get "/admin/customers/#{user.id}"
    
    refute_includes last_response.body, '<script>'
    assert_includes last_response.body, '&lt;script&gt;'
  end
end

Test Factories

Creating Test Data

# test/factories.rb
module TestFactories
  def create_user(attributes = {})
    defaults = {
      email: "user#{rand(10000)}@example.com",
      password: 'test_password',
      name: 'Test User',
      status: 'active'
    }
    
    User.create(defaults.merge(attributes))
  end

  def create_product(attributes = {})
    defaults = {
      name: 'Test Product',
      description: 'Test product description',
      price: 99.99,
      license_type: 'one_time',
      max_activations: 3
    }
    
    Product.create(defaults.merge(attributes))
  end

  def create_license(attributes = {})
    product = attributes[:product] || create_product
    defaults = {
      license_key: "TEST-#{SecureRandom.hex(4)}".upcase,
      product_id: product.id,
      customer_email: 'customer@example.com',
      status: 'active',
      max_activations: 3,
      activation_count: 0
    }
    
    License.create(defaults.merge(attributes))
  end
end

# Include in test files
class Minitest::Test
  include TestFactories
end

🔧 Configuration Management

Settings System

Settings Schema Definition

# lib/settings/settings_schema.rb
class SettingsSchema
  SCHEMA = {
    'app' => {
      'site_name' => { type: 'string', default: 'Source-License' },
      'site_url' => { type: 'string', required: true },
      'support_email' => { type: 'email', required: true }
    },
    'license' => {
      'default_format' => { type: 'string', default: 'XXXX-XXXX-XXXX-XXXX' },
      'default_max_activations' => { type: 'integer', default: 3, min: 1 },
      'auto_generate' => { type: 'boolean', default: true }
    },
    'payment' => {
      'stripe_publishable_key' => { type: 'string', required: true },
      'stripe_secret_key' => { type: 'string', required: true, secret: true },
      'paypal_client_id' => { type: 'string' },
      'paypal_client_secret' => { type: 'string', secret: true }
    }
  }.freeze
end

Settings Manager Implementation

# lib/settings_manager.rb
class SettingsManager
  def self.get(key, default = nil)
    category, setting_key = key.split('.', 2)
    return default unless category && setting_key

    # Check environment variable override
    env_key = "#{category.upcase}_#{setting_key.upcase}"
    return ENV[env_key] if ENV.key?(env_key)

    # Get from database/cache
    stored_value = get_stored_setting(category, setting_key)
    return stored_value unless stored_value.nil?

    # Return schema default
    schema_default(category, setting_key) || default
  end

  def self.set(key, value)
    category, setting_key = key.split('.', 2)
    validate_setting(category, setting_key, value)
    store_setting(category, setting_key, value)
  end

  private

  def self.validate_setting(category, key, value)
    schema = SettingsSchema::SCHEMA.dig(category, key)
    return unless schema

    case schema[:type]
    when 'integer'
      raise ArgumentError, "Value must be an integer" unless value.is_a?(Integer)
      raise ArgumentError, "Value below minimum" if schema[:min] && value < schema[:min]
    when 'email'
      raise ArgumentError, "Invalid email format" unless value =~ /\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i
    end
  end
end

Environment Configuration

Environment-Specific Settings

# Development environment
configure :development do
  set :logging, true
  set :show_exceptions, true
  set :dump_errors, true
end

# Production environment
configure :production do
  set :logging, false
  set :show_exceptions, false
  set :dump_errors, false
  
  # Enable security middleware
  use SecurityMiddleware
end

# Test environment
configure :test do
  set :logging, false
  set :show_exceptions, false
end

🎨 Frontend Development

Template System

ERB Templates

Source-License uses ERB (Embedded Ruby) templates for rendering HTML:

<!-- views/layouts/main_layout.erb -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= @page_title || 'Source-License' %></title>
    
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <!-- Custom CSS -->
    <style>
        :root {
            --primary-color: <%= customization_get('primary_color', '#007bff') %>;
            --secondary-color: <%= customization_get('secondary_color', '#6c757d') %>;
        }
    </style>
</head>
<body>
    <%= erb :'partials/_navigation' %>
    
    <main class="container mt-4">
        <% if @flash_message %>
            <div class="alert alert-<%= @flash_type || 'info' %> alert-dismissible fade show">
                <%= @flash_message %>
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
            </div>
        <% end %>
        
        <%= yield %>
    </main>
    
    <%= erb :'partials/_footer' %>
    
    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Helper Functions

# lib/helpers.rb
module TemplateHelpers
  def format_currency(amount, currency = 'USD')
    case currency.upcase
    when 'USD'
      "$#{format('%.2f', amount)}"
    when 'EUR'
      "€#{format('%.2f', amount)}"
    else
      "#{amount} #{currency}"
    end
  end

  def format_date(date, format = :short)
    return 'Never' unless date
    
    case format
    when :short
      date.strftime('%m/%d/%Y')
    when :long
      date.strftime('%B %d, %Y at %I:%M %p')
    when :iso
      date.iso8601
    else
      date.strftime(format.to_s)
    end
  end

  def status_badge(status)
    classes = {
      'active' => 'success',
      'inactive' => 'secondary',
      'suspended' => 'warning',
      'revoked' => 'danger',
      'expired' => 'dark'
    }
    
    badge_class = classes[status.to_s] || 'secondary'
    "<span class=\"badge bg-#{badge_class}\">#{status.to_s.capitalize}</span>"
  end

  def card(title = nil, options = {}, &block)
    css_class = "card #{options[:class]}"
    
    content = "<div class=\"#{css_class}\">"
    
    if title
      content << "<div class=\"card-header\">"
      content << "<h5 class=\"card-title mb-0\">#{title}</h5>"
      content << "</div>"
    end
    
    content << "<div class=\"card-body\">"
    content << capture(&block) if block_given?
    content << "</div>"
    content << "</div>"
    
    content
  end
end

Customization System

Dynamic Styling

# lib/customization.rb
class Customization
  def self.get_css_variables
    {
      '--primary-color' => get_setting('appearance.primary_color', '#007bff'),
      '--secondary-color' => get_setting('appearance.secondary_color', '#6c757d'),
      '--font-family' => get_setting('appearance.font_family', 'system-ui'),
      '--border-radius' => get_setting('appearance.border_radius', '0.375rem')
    }
  end

  def self.generate_custom_css
    variables = get_css_variables
    
    css = ":root {\n"
    variables.each do |property, value|
      css << "  #{property}: #{value};\n"
    end
    css << "}\n"
    
    # Additional custom styles
    css << custom_component_styles
    
    css
  end

  private

  def self.custom_component_styles
    """
    .btn-primary {
      background-color: var(--primary-color);
      border-color: var(--primary-color);
    }
    
    .navbar-brand {
      font-family: var(--font-family);
    }
    
    .card {
      border-radius: var(--border-radius);
    }
    """
  end
end

🔐 Security Best Practices

Input Validation and Sanitization

Parameter Validation

# Strong parameter validation
def validate_product_params(params)
  required_fields = %w[name price license_type max_activations]
  missing_fields = required_fields.select { |field| params[field].nil? || params[field].to_s.strip.empty? }
  
  raise ArgumentError, "Missing required fields: #{missing_fields.join(', ')}" unless missing_fields.empty?
  
  # Validate data types
  raise ArgumentError, "Price must be a valid number" unless valid_decimal?(params['price'])
  raise ArgumentError, "Max activations must be a positive integer" unless valid_positive_integer?(params['max_activations'])
  
  # Sanitize strings
  {
    name: sanitize_string(params['name']),
    description: sanitize_html(params['description']),
    price: params['price'].to_f,
    license_type: params['license_type'],
    max_activations: params['max_activations'].to_i
  }
end

def sanitize_string(input)
  return '' unless input
  input.to_s.strip.gsub(/[<>"]/, '')
end

def sanitize_html(input)
  return '' unless input
  # Use a proper HTML sanitizer in production
  input.to_s.gsub(/<script.*?>.*?<\/script>/mi, '')
end

SQL Injection Prevention

# Always use parameterized queries with Sequel
def find_licenses_by_email(email)
  # Good - parameterized query
  License.where(customer_email: email).all
end

def search_licenses(search_term)
  # Good - parameterized LIKE query
  License.where(Sequel.ilike(:customer_email, "%#{search_term}%")).all
end

# Never do this - vulnerable to SQL injection
def bad_search(term)
  DB["SELECT * FROM licenses WHERE customer_email LIKE '%#{term}%'"].all
end

Authentication Security

Password Security

class User < Sequel::Model
  def password=(new_password)
    # Validate password strength
    validate_password_strength(new_password)
    
    # Use BCrypt with appropriate cost
    self.password_hash = BCrypt::Password.create(new_password, cost: 12)
    self.password_changed_at = Time.now
  end

  def password_matches?(password)
    BCrypt::Password.new(password_hash) == password
  rescue BCrypt::Errors::InvalidHash
    false
  end

  private

  def validate_password_strength(password)
    errors = []
    errors << "Password must be at least 12 characters long" if password.length < 12
    errors << "Password must contain uppercase letters" unless password.match?(/[A-Z]/)
    errors << "Password must contain lowercase letters" unless password.match?(/[a-z]/)
    errors << "Password must contain numbers" unless password.match?(/\d/)
    errors << "Password must contain special characters" unless password.match?(/[!@#$%^&*]/)
    
    raise ArgumentError, errors.join('; ') unless errors.empty?
  end
end

Session Security

configure do
  # Secure session configuration
  use Rack::Session::Cookie, {
    key: '_source_license_session',
    secret: ENV.fetch('APP_SECRET'),
    secure: ENV['APP_ENV'] == 'production',  # HTTPS only in production
    httponly: true,                          # Prevent XSS
    same_site: :strict,                      # CSRF protection
    expire_after: 24 * 60 * 60              # 24 hours
  }
end

📚 Code Style Guidelines

Ruby Style Guide

Source-License follows the Ruby Style Guide with these specific conventions:

Naming Conventions

# Use snake_case for variables, methods, and files
user_email = 'user@example.com'
def calculate_license_expiry(product)
end

# Use PascalCase for classes and modules
class LicenseGenerator
end

module PaymentProcessor
end

# Use SCREAMING_SNAKE_CASE for constants
MAX_LICENSE_ACTIVATIONS = 10
DEFAULT_LICENSE_FORMAT = 'XXXX-XXXX-XXXX-XXXX'

Method Organization

class License < Sequel::Model
  # Class methods first
  def self.generate_for_product(product, customer_email)
  end

  # Instance methods grouped by functionality
  # Public methods first
  def activate!(machine_fingerprint)
  end

  def deactivate!(machine_fingerprint)
  end

  def valid?
  end

  # Private methods last
  private

  def calculate_expiration
  end

  def send_notification_email
  end
end

Documentation

# Use YARD documentation format
class LicenseGenerator
  # Generates a new license for the specified product and customer
  #
  # @param product [Product] The product to generate a license for
  # @param customer_email [String] The customer's email address
  # @param options [Hash] Additional options for license generation
  # @option options [Integer] :max_activations Override product default
  # @option options [Time] :expires_at Custom expiration date
  # @option options [String] :format License key format
  # 
  # @return [License] The generated license
  # @raise [ArgumentError] If product or email is invalid
  def self.generate_license(product, customer_email, options = {})
    validate_inputs(product, customer_email)
    
    # Implementation...
  end
end

Database Conventions

Migration Naming

# Use descriptive migration names with version numbers
{
  version: 1,
  description: 'Create initial user and admin tables'
}

{
  version: 2,
  description: 'Add email verification to users'
}

{
  version: 3,
  description: 'Create product and license tables'
}

Model Validations

class Product < Sequel::Model
  def validate
    super
    
    # Always validate required fields
    errors.add(:name, 'cannot be empty') if !name || name.strip.empty?
    errors.add(:price, 'must be present') if price.nil?
    
    # Validate data types and ranges
    errors.add(:price, 'must be greater than or equal to 0') if price&.negative?
    errors.add(:max_activations, 'must be a positive integer') if max_activations && max_activations <= 0
    
    # Validate enums and allowed values
    valid_license_types = %w[one_time subscription]
    errors.add(:license_type, "must be one of: #{valid_license_types.join(', ')}") unless valid_license_types.include?(license_type)
  end
end

🚀 Deployment

Production Considerations

Environment Configuration

# Production environment settings
configure :production do
  # Disable detailed error pages
  set :show_exceptions, false
  set :dump_errors, false
  
  # Enable security middleware
  use SecurityMiddleware
  
  # Configure secure sessions
  use Rack::Session::Cookie, {
    secure: true,        # HTTPS only
    httponly: true,      # No JavaScript access
    same_site: :strict   # CSRF protection
  }
end

Database Optimization

# Production database configuration
configure :production do
  # Connection pooling
  DB.extension :connection_validator
  DB.pool.connection_validation_timeout = 3600
  
  # Query logging (disable in production)
  DB.loggers = [] unless ENV['DEBUG_SQL']
  
  # Prepared statements
  DB.extension :pg_auto_parameterize if DB.adapter_scheme == :postgres
end

Performance Optimization

Caching Strategy

# Simple memory cache for settings
class SettingsCache
  def self.get(key)
    @cache ||= {}
    @cache[key] ||= SettingsManager.get_from_db(key)
  end

  def self.invalidate(key = nil)
    if key
      @cache&.delete(key)
    else
      @cache = {}
    end
  end
end

Database Query Optimization

# Use database indexes for frequent queries
DB.add_index :licenses, :customer_email
DB.add_index :licenses, [:product_id, :status]
DB.add_index :license_activations, [:license_id, :active]

# Optimize common queries
def recent_orders(limit = 50)
  Order.where(created_at: (Time.now - 30*24*60*60)..Time.now)
       .order(Sequel.desc(:created_at))
       .limit(limit)
       .eager(:order_items)
end

🤝 Contributing

Contribution Workflow

  1. Fork and Clone:

    git clone https://github.com/your-username/Source-License.git
    cd Source-License
    git remote add upstream https://github.com/PixelRidgeSoftworks/Source-License.git
  2. Create Feature Branch:

    git checkout -b feature/new-feature-name
  3. Development:

    # Make changes
    bundle exec rubocop  # Check style
    ruby run_tests.rb    # Run tests
  4. Commit and Push:

    git add .
    git commit -m "Add new feature: description"
    git push origin feature/new-feature-name
  5. Pull Request:

    • Create PR on GitHub
    • Include description of changes
    • Reference any related issues

Code Review Guidelines

Review Checklist

  • Code follows style guidelines
  • Tests are included and passing
  • Documentation is updated
  • Security considerations addressed
  • Performance impact considered
  • Database migrations are reversible

Review Comments

# Good: Constructive feedback
# Consider using a constant for this magic number
MAX_RETRIES = 3

# Suggestion: Extract this logic into a helper method
def validate_license_key_format(key)
  # Validation logic here
end

# Question: Should we handle the case where customer_email is nil?

Release Process

Version Management

# Update version in multiple places
# app.rb
APP_VERSION = '1.1.0'

# Gemfile
# Add version to gem specification

# CHANGELOG.md
## [1.1.0] - 2024-01-15
### Added
- New license validation API endpoint
- Improved error handling for payment processing

### Fixed
- Bug in subscription renewal process
- Security issue with admin authentication

This development guide provides comprehensive information for developers working on Source-License. For deployment and production considerations, refer to the Deployment Guide. For API integration, see the API Reference.

Clone this wiki locally