{"id":14955706,"url":"https://github.com/codica2/rails-app-best-practice","last_synced_at":"2025-10-01T01:31:19.729Z","repository":{"id":47160650,"uuid":"157354074","full_name":"codica2/rails-app-best-practice","owner":"codica2","description":"Rails app structure example","archived":false,"fork":false,"pushed_at":"2021-09-10T14:33:41.000Z","size":37,"stargazers_count":42,"open_issues_count":0,"forks_count":14,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-14T11:46:04.973Z","etag":null,"topics":["project-structure","rails-application","ruby","ruby-on-rails"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codica2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-13T09:27:44.000Z","updated_at":"2024-05-24T21:56:20.000Z","dependencies_parsed_at":"2022-08-29T10:11:31.360Z","dependency_job_id":null,"html_url":"https://github.com/codica2/rails-app-best-practice","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codica2%2Frails-app-best-practice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codica2%2Frails-app-best-practice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codica2%2Frails-app-best-practice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codica2%2Frails-app-best-practice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codica2","download_url":"https://codeload.github.com/codica2/rails-app-best-practice/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234808945,"owners_count":18890088,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["project-structure","rails-application","ruby","ruby-on-rails"],"created_at":"2024-09-24T13:11:35.620Z","updated_at":"2025-10-01T01:31:19.405Z","avatar_url":"https://github.com/codica2.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rails Structure Sample\n\n![Rails Structure](https://www.iconsdb.com/icons/preview/icon-sets/web-2-ruby-red/tree-80-xxl.png)\n\n## Description\n\nRuby on Rails is one of our favourite frameworks for web applications development. We love it because of agile and interesting development process, high performance and, of course, ruby programming language.\nBelow you can find an example of a project`s structure, and its main components\n\n## File structure\n\n```\nproject_name\n  ├── app\n  |   ├── assets =\u003e images, fonts, stylesheets, js\n  |   ├── controllers\n  |   ├── decorators\n  |   ├── helpers\n  |   ├── mailers\n  |   ├── models\n  |   ├── performers\n  |   ├── policies\n  |   ├── presenters\n  |   ├── serializers\n  |   ├── services\n  |   ├── uploaders\n  |   ├── views\n  |   └── workers =\u003e workers for running processes in the background\n  ├── bin     =\u003e  contains script that starts, update, deploy or run your application.\n  ├── config  =\u003e configure your application's routes, database, and more\n  ├── db      =\u003e contains your current database schema and migrations\n  ├── lib     =\u003e extended modules for your application\n  ├── log     =\u003e app log files\n  ├── public  =\u003e The only folder seen by the world as-is. Contains static files and compiled assets.\n  ├── spec    =\u003e Unit tests, features, and other test apparatus.\n  ├── tmp     =\u003e Temporary files (like cache and pid files).\n  └── vendor  =\u003e third-party code\n```\n\n## Controllers\n\nAction Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output.\n\nThere are a lot of opinions on what a controller should do. A common ground of the responsibilities a controller should have include the following:\n\n**Authentication and authorization** — checking whether the entity (oftentimes, a user) behind the request is who it says it is and whether it is allowed to access the resource or perform the action.\n\n**Data fetching** — it should call the logic for finding the right data based on the parameters that came with the request. In the perfect world, it should be a call to one method that does all the work. The controller should not do the heavy work, it should delegate it further.\n\n**Template rendering** — finally, it should return the right response by rendering the result with the proper format (HTML, JSON, etc.). Or, it should redirect to some other path or URL.\n\n\n```ruby\nclass BooksController \u003c ApplicationController\n\n  def show\n    @book = Book.find(params[:id])\n  end\n\n  def create\n    @book = Book.new(book_params)\n\n    if @book.save\n      redirect_to action: 'list'\n    else\n      @subjects = Subject.all\n      render action: 'new'\n    end\n  end\n\n  private\n\n  def book_params\n    params.require(:books).permit(:title, :price, :subject_id, :description)\n  end\n\nend\n```\n\n```ruby\nclass ListingsController \u003c ApiController\n\n  def index\n    listings = Current.user.listings\n\n    render json: ListingSerializer.new(listings).serializable_hash\n  end\n\n  def create\n    authorize Listing\n\n    command = Listings::Create.call(Current.user, params[:listing])\n\n    if command.success?\n      render json: ListingSerializer.new(command.result).serializable_hash\n    else\n      render json: command.errors\n    end\n  end\n\n  def destroy\n    listing = Current.user.listings.find(params[:id])\n\n    authorize listing\n\n    command = Listings::Delete.call(listing)\n\n    if command.success?\n      head :ok\n    else\n      render json: command.errors\n    end\n  end\n\nend\n```\n\n[Examples](app/controllers)\n\n[Documentation](https://edgeguides.rubyonrails.org/action_controller_overview.html)\n\n## Decorators\n\nDecorators adds an object-oriented layer of presentation logic to your Rails application\n\n```ruby\nclass ListingDecorator \u003c Draper::Decorator\n\n  delegate_all\n\n  def creation_date\n    object.created_at.strftime('%d/%m/%Y')\n  end\n\nend\n```\n\n[Examples](app/decorators)\n\n[Documentation](https://github.com/drapergem/draper)\n\n## Helpers\n\nHelpers in Rails are used to extract complex logic out of the view so that you can organize your code better.\n\n```ruby\nmodule PartnersHelper\n\n  def partner_activation(partner)\n    partner.active? ? t('partners.extend_activation') : t('partners.activation')\n  end\n\n  def partner_edit_page_title(redirect_url)\n    redirect_url.present? ? I18n.t('partners.upgrade_my_profile') : I18n.t('partners.edit_my_profile')\n  end\n\nend\n```\n\n[Examples](app/helpers)\n\n[Documentation](https://api.rubyonrails.org/classes/ActionController/Helpers.html)\n\n## Models\n\nActive Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database.\n\n```ruby\n# == Schema Information\n#\n# Table name: colors\n#\n#  id         :integer          not null, primary key\n#  created_at :datetime         not null\n#  updated_at :datetime         not null\n#\n\nclass Color \u003c ApplicationRecord\n\n  # == Constants ============================================================\n\n  # == Attributes ===========================================================\n  attribute :name\n\n  # == Extensions ===========================================================\n  translates :name, fallbacks_for_empty_translations: true\n  globalize_accessors\n\n  # == Relationships ========================================================\n  has_many :vehicle_listings, dependent: :nullify\n\n  # == Validations ==========================================================\n  validates(*Color.globalize_attribute_names, presence: true)\n\n  # == Scopes ===============================================================\n\n  # == Callbacks ============================================================\n\n  # == Class Methods ========================================================\n\n  # == Instance Methods =====================================================\n\nend\n```\n\n[Examples](app/models)\n\n[Documentation](https://guides.rubyonrails.org/active_model_basics.html)\n\n[Ruby on Rails Model Patterns and Anti-patterns](https://blog.appsignal.com/2020/11/18/rails-model-patterns-and-anti-patterns.html)\n\n## Form Objects\n\nUse [form object](https://medium.com/selleo/essential-rubyonrails-patterns-form-objects-b199aada6ec9) pattern to make your models more lightweight.\nSingle responsibility principle helps us make the best design decisions about what the class should be responsible for. Your model is a database table, it represents a single entry in the code, so there is no reason to worry and user action.\n\nForm Objects are responsible for the presentation of the form in your application. Each form field is an attribute in a class and can be checked through validation, which will give us clean data and they will go further along the chain. This can be your model, defining a table in the database or, for example, a search form.\n\nHere are an examples of [fat](app/models/investor_fat.rb) and [refactored](app/models/investor.rb) models.\n\nUsage Form Object in controller:\n\n```ruby\n\nclass Admin::InvestorsController \u003c CommonAdminController\n\n  include Sorting\n\n  load_resource :investor, except: %i[index new create]\n\n  def index\n    @investors = Investor.real.includes(:investor_group, :address)\n  end\n\n  def new\n    @investor_form = InvestorForm.new\n  end\n\n  def edit\n    @investor_form = InvestorForm.new(@investor)\n  end\n\n  def create\n    params[:investor][:password] = generate_devise_password\n    @investor_form = InvestorForm.new\n\n    if @investor_form.submit(params)\n      flash[:success] = 'Investor was successfully created.'\n      redirect_to admin_investor_path(@investor_form.id)\n    else\n      render :new\n    end\n  end\n\n  def update\n    @investor_form = InvestorForm.new(@investor)\n\n    if @investor_form.submit(params)\n      flash[:success] = 'Investor was successfully updated.'\n      redirect_to admin_investor_path(@investor)\n    else\n      render :edit\n    end\n  end\n\n```\n\n[Examples](app/forms/investor_form.rb)\n\n[Documentation](https://thoughtbot.com/blog/activemodel-form-objects)\n\n## Performers\n\nThe performer pattern creates seperation for the methods in your model that are view related. The performers are modules and are included into the corresponding model.\n\n```ruby\nmodule InvestmentDataPerformer\n\n  def self.included(base)\n    base.extend ClassMethods\n  end\n\n  module ClassMethods\n\n  end\n\n  def non_final_profit\n    return nil unless investment.non_final_profit\n    investment.non_final_profit * holding / 100\n  end\n\n  def final_distribution\n    return unless investment.distribution.present?\n    investment.distribution * holding / 100\n  end\n\nend\n```\n\n[Examples](app/performers)\n\n[Documentation](https://github.com/jwipeout/performer-pattern)\n\n## Serializers\n\nIn Ruby serialization does nothing else than just converting the given data structure into its valid JSON.\n\n```ruby\nclass AttachmentSerializer\n\n  include JSONAPI::Serializer\n\n  attributes :id, :name, :description\n\n  attribute :url, \u0026:attachment_url\n\n  attribute :type do |obj|\n    obj.attachment.mime_type\n  end\n\n  attribute :name do |obj|\n    obj.attachment['filename']\n  end\n\nend\n```\n\n[Examples](app/serializers)\n\n[Documentation](https://github.com/jsonapi-serializer/jsonapi-serializer)\n\n## Services\n\nService Object can be a class or module in Ruby that performs an action. It can help take out logic from other areas of the MVC files.\n\n```ruby\nclass Subscribe \u003c BaseService\n\n  attr_reader :email\n\n  def initialize(email)\n    @email = email\n  end\n\n  def call\n    list.members.create(body: { email_address: email, status: 'subscribed' })\n  end\n\n  private\n\n  def list\n    gibbon_request.lists(ENV['MAILCHIMP_LIST_ID'])\n  end\n\n  def gibbon_request\n    Gibbon::Request.new(api_key: ENV['MAILCHIMP_API_KEY'])\n  end\n\nend\n```\n\n[Examples](app/services)\n\n[Documentation](https://github.com/nebulab/simple_command)\n\n## Presenters\n\nPresenters give you an object oriented way to approach view helpers.\n\n```ruby\nclass QuarterlyDistributionPresenter \u003c BaseDividendYearPresenter\n\n  QUARTERS = %w[Q1 Q2 Q3 Q4].freeze\n\n  def dividends_year\n    QUARTERS.map do |quarter|\n      value_decorator(quarter)\n    end\n  end\n\n  def select_dividend_by(dividend_year)\n    CalendarQuarter.from_date(dividend_year).quarter\n  end\n\nend\n```\n\n[Examples](app/presenters)\n\n[Documentation](http://nithinbekal.com/posts/rails-presenters/)\n\n## Policies\n\nPundit provides a set of helpers which guide in leveraging regular Ruby classes and object oriented design patterns to build a simple, robust and scalable authorization system.\n\n```ruby\nclass ProductPolicy \u003c ApplicationPolicy\n\n  def create?\n    user_active?\n  end\n\n  def show?\n    user_active? \u0026\u0026 record.active?\n  end\n\n  def destroy?\n    show?\n  end\n\nend\n\n```\n\n[Examples](app/policies)\n\n[Documentation](https://github.com/varvet/pundit)\n\n## Facades\n\nFacades provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.\nOne of the responsibilities that is being thrown at the controller is to prepare the data so it can be presented to user. To remove the responsibility of preparing the data for the view we're using Facade pattern.\n\n```ruby\nclass Admin\n\n  class DocumentsFacade\n\n    attr_reader :loan, :investor\n\n    def initialize(loan_id, investor_id)\n      @loan     = Loan.find(loan_id)\n      @investor = Investor.find(investor_id)\n    end\n\n    def lender?\n      investor.present?\n    end\n\n    def documents\n      Document.where(investment_id: loan.id, investor_id: investor.id)\n    end\n\n    def lenders\n      loan.lenders.order(:name).uniq.collect { |l| [l.name, l.id] }\n    end\n\n  end\n\nend\n```\n\nNow we can reduce our controller down to:\n\n```ruby\nclass DocumentsController \u003c LoansController\n\n  def index\n    add_breadcrumb 'Documents'\n    @documents_data = Admin::DocumentsFacade.new(params[:loan_id], params[:investor_id])\n  end\n\nend\n```\n\nAnd inside our view we will call for our facade:\n\n```slim\n= render ‘documents/form’, document: @document_data.new_document\n```\n\n[Examples](app/facades)\n\n[Documentation](https://medium.com/kkempin/facade-design-pattern-in-ruby-on-rails-710aa88326f)\n\n## Workers\n\nAt Codica we use sidekiq as a full-featured background processing framework for Ruby. It aims to be simple to integrate with any modern Rails application and much higher performance than other existing solutions.\n\n```ruby\nclass PartnerAlertWorker\n\n  include Sidekiq::Worker\n\n  def perform(id)\n    @partner = Partner.find(id)\n    PartnerMailer.alert(@partner).deliver_now\n  end\n\nend\n```\n\n[Examples](app/workers)\n\n[Documentation](https://github.com/mperham/sidekiq/wiki)\n\n## Spec\n\nSpec folder include the test suites, the application logic tests.\n\n```ruby\nrequire 'rails_helper'\n\nRSpec.feature 'Static page', type: :feature do\n  let(:listing) { create :listing }\n\n  scenario 'unsuccess payment' do\n    visit payment_info_error_path(listing: listing.id)\n    expect(page).to have_content I18n.t('unsuccess_payment_header')\n  end\n\n  scenario 'success payment' do\n    visit payment_info_success_path(promotion: 'on_homepage', price: '30')\n    expect(page).to have_content I18n.t('success_payment_header')\n  end\n\nend\n```\n\n[Examples](https://github.com/codica2/rspec-samples)\n\n[Documentation](https://relishapp.com/rspec/)\n\n## License\nrails-app-best-practice is Copyright © 2015-2019 Codica. It is released under the [MIT License](https://opensource.org/licenses/MIT).\n\n## About Codica\n\n[![Codica logo](https://www.codica.com/assets/images/logo/logo.svg)](https://www.codica.com)\n\nWe love open source software! See [our other projects](https://github.com/codica2) or [hire us](https://www.codica.com/) to design, develop, and grow your product.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodica2%2Frails-app-best-practice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodica2%2Frails-app-best-practice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodica2%2Frails-app-best-practice/lists"}