Rails app template

Bullet Train

Add Avo 3 to your Bullet Trains app blazing fast!

Avo makes it fun to build internal tools. Everyone loves our engineering team for consistently shipping actions, views, and filters to make administrative tasks easier.
Steven Traversi
Steven Traversi
Head of Engineering at Change
Run the following command to quickly apply this app template

Do you think this template needs changes? Submit a PR here.

Full template

# Template info:
#   Link: https://avohq.io/templates/bullet-train
#   Repository: https://github.com/avo-hq/bullet-train-app-template

# List of all Avo files with path and contents
files = {"app/avo/cards/users_count.rb"=>"class Avo::Cards::UsersCount < Avo::Cards::MetricCard\n  self.id = \"users_count\"\n  self.label = \"Users count\"\n  self.description = \"Count of registered users\"\n\n  def query\n    result ::User.all.count\n  end\nend\n", "app/avo/dashboards/overview.rb"=>"class Avo::Dashboards::Overview < Avo::Dashboards::BaseDashboard\n  self.id = \"overview\"\n  self.name = \"Overview\"\n  self.grid_cols = 4\n\n  def cards\n    card Avo::Cards::UsersCount\n  end\nend\n", "app/avo/resources/invitation.rb"=>"class Avo::Resources::Invitation < Avo::BaseResource\n  self.title = :email\n  self.includes = []\n  self.search = {\n    query: -> { query.ransack(id_eq: params[:q], email_cont: params[:q], m: \"or\").result(distinct: false) },\n    item: -> {\n      {\n        title: record.title\n      }\n    }\n  }\n\n  def fields\n    field :id, as: :id\n    field :email, as: :text\n    field :uuid, as: :text\n    field :team, as: :belongs_to, searchable: true\n    field :from_membership, as: :belongs_to, searchable: true\n    field :membership, as: :has_one\n  end\nend\n", "app/avo/resources/membership.rb"=>"class Avo::Resources::Membership < Avo::BaseResource\n  self.title = :name\n  self.includes = []\n  self.search = {\n    query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], user_email_cont: params[:q], role_cont: params[:q], m: \"or\").result(distinct: false) },\n    item: -> {\n      {\n        title: record.name,\n        description: \"Membership of \#{team.name}\"\n      }\n    }\n  }\n\n  def fields\n    field :id, as: :id\n    field :name, as: :text, hide_on: :forms\n    field :user_profile_photo_id, as: :text\n    field :user_email, as: :text\n    field :added_by_id, as: :number\n    field :role_ids, as: :tags\n    field :user, as: :belongs_to, searchable: true\n    field :team, as: :belongs_to, searchable: true\n    field :invitation, as: :belongs_to, searchable: true\n    field :added_by, as: :belongs_to, searchable: true\n  end\nend\n", "app/avo/resources/team.rb"=>"class Avo::Resources::Team < Avo::BaseResource\n  self.title = :name\n  self.includes = []\n  self.search = {\n    query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], slug_cont: params[:q], m: \"or\").result(distinct: false) },\n    item: -> {\n      {\n        title: record.name\n      }\n    }\n  }\n\n  def fields\n    field :id, as: :id\n    field :name, as: :text\n    field :slug, as: :text\n    field :being_destroyed, as: :boolean, hide_on: :forms\n    field :time_zone, as: :select, options: -> { view_context.time_zone_options_for_select(Avo::Current.user.time_zone, nil, ActiveSupport::TimeZone) }\n    field :locale, as: :text\n    field :users, as: :has_many, through: :memberships\n    field :memberships, as: :has_many\n    field :invitations, as: :has_many\n  end\nend\n", "app/avo/resources/user.rb"=>"class Avo::Resources::User < Avo::BaseResource\n  self.title = :full_name\n  self.includes = []\n  self.search = {\n    query: -> { query.ransack(id_eq: params[:q], first_name_cont: params[:q], last_name_cont: params[:q], email_cont: params[:q], m: \"or\").result(distinct: false) },\n    item: -> {\n      {\n        title: record.full_name,\n      }\n    }\n  }\n\n  def fields\n    field :id, as: :id\n    field :email, as: :text\n    field :first_name, as: :text\n    field :last_name, as: :text\n    field :time_zone, as: :text\n    field :current_team, as: :belongs_to\n\n    field :teams, as: :has_many, through: :teams\n    field :memberships, as: :has_many\n  end\nend\n", "app/controllers/avo/invitations_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# More information on https://docs.avohq.io/3.0/controllers.html\nclass Avo::InvitationsController < Avo::ResourcesController\nend\n", "app/controllers/avo/memberships_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# More information on https://docs.avohq.io/3.0/controllers.html\nclass Avo::MembershipsController < Avo::ResourcesController\nend\n", "app/controllers/avo/teams_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# More information on https://docs.avohq.io/3.0/controllers.html\nclass Avo::TeamsController < Avo::ResourcesController\nend\n", "app/controllers/avo/users_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# More information on https://docs.avohq.io/3.0/controllers.html\nclass Avo::UsersController < Avo::ResourcesController\nend\n", "config/initializers/avo.rb"=>"# For more information regarding these settings check out our docs https://docs.avohq.io\nAvo.configure do |config|\n  ## == Routing ==\n  config.root_path = \"/admin/avo\"\n  # used only when you have custom `map` configuration in your config.ru\n  # config.prefix_path = \"/internal\"\n\n  # Where should the user be redirected when visting the `/avo` url\n  # config.home_path = nil\n\n  ## == Licensing ==\n  # config.license_key = ENV['AVO_LICENSE_KEY']\n\n  ## == Set the context ==\n  config.set_context do\n    # Return a context object that gets evaluated in Avo::ApplicationController\n  end\n\n  ## == Authentication ==\n  config.current_user_method = :current_user\n  # config.authenticate_with do\n  # end\n\n  ## == Authorization ==\n  # config.authorization_methods = {\n  #   index: 'index?',\n  #   show: 'show?',\n  #   edit: 'edit?',\n  #   new: 'new?',\n  #   update: 'update?',\n  #   create: 'create?',\n  #   destroy: 'destroy?',\n  #   search: 'search?',\n  # }\n  # config.raise_error_on_missing_policy = false\n  # config.authorization_client = :pundit\n\n  ## == Localization ==\n  # config.locale = 'en-US'\n\n  ## == Resource options ==\n  # config.resource_controls_placement = :right\n  # config.model_resource_mapping = {}\n  # config.default_view_type = :table\n  # config.per_page = 24\n  # config.per_page_steps = [12, 24, 48, 72]\n  # config.via_per_page = 8\n  config.id_links_to_resource = true\n  # config.cache_resources_on_index_view = true\n  ## permanent enable or disable cache_resource_filters, default value is false\n  # config.cache_resource_filters = false\n  ## provide a lambda to enable or disable cache_resource_filters per user/resource.\n  # config.cache_resource_filters = ->(current_user:, resource:) { current_user.cache_resource_filters?}\n\n  ## == Customization ==\n  config.app_name = -> { I18n.t \"application.name\" }\n  # config.timezone = 'UTC'\n  # config.currency = 'USD'\n  # config.hide_layout_when_printing = false\n  # config.full_width_container = false\n  # config.full_width_index_view = false\n  # config.search_debounce = 300\n  # config.view_component_path = \"app/components\"\n  # config.display_license_request_timeout_error = true\n  # config.disabled_features = []\n  # config.buttons_on_form_footers = true\n  # config.field_wrapper_layout = true\n\n  ## == Branding ==\n  # config.branding = {\n  #   colors: {\n  #     background: \"248 246 242\",\n  #     100 => \"#CEE7F8\",\n  #     400 => \"#399EE5\",\n  #     500 => \"#0886DE\",\n  #     600 => \"#066BB2\",\n  #   },\n  #   chart_colors: [\"#0B8AE2\", \"#34C683\", \"#2AB1EE\", \"#34C6A8\"],\n  #   logo: \"/avo-assets/logo.png\",\n  #   logomark: \"/avo-assets/logomark.png\",\n  #   placeholder: \"/avo-assets/placeholder.svg\",\n  #   favicon: \"/avo-assets/favicon.ico\"\n  # }\n\n  ## == Breadcrumbs ==\n  # config.display_breadcrumbs = true\n  # config.set_initial_breadcrumbs do\n  #   add_breadcrumb \"Home\", '/avo'\n  # end\n\n  ## == Menus ==\n  # config.main_menu = -> {\n  #   section \"Dashboards\", icon: \"dashboards\" do\n  #     all_dashboards\n  #   end\n\n  #   section \"Resources\", icon: \"resources\" do\n  #     all_resources\n  #   end\n\n  #   section \"Tools\", icon: \"tools\" do\n  #     all_tools\n  #   end\n  # }\n  # config.profile_menu = -> {\n  #   link \"Profile\", path: \"/avo/profile\", icon: \"user-circle\"\n  # }\nend\n"}

if ARGV.include? "--community-edition"
  edition = "community"
elsif ARGV.include? "--pro-edition"
  edition = "pro"
elsif ARGV.include? "--advanced-edition"
  edition = "advanced"
end

unless edition
  # === Fetch the Avo edition ===
  question = <<~QUESTION


    Which version of Avo would you like to install?
    1. Avo Community (default)
    2. Avo Pro
    3. Avo Advanced

    More information about version features here:
    https://avohq.io/pricing


  QUESTION
  puts question

  answer = ask("Which version of Avo would you like to install?", default: "1", limited_to: ["1", "2", "3"])

  edition = case answer
  when "1"
    "community"
  when "2"
    "pro"
  when "3"
    "advanced"
  end
end

# === Add gem to Gemfile ===
case edition
when "community"
  gem "avo", ">= 3.2.3"
when "pro"
  gem "avo-pro", source: "https://packager.dev/avo-hq"
when "advanced"
  gem "avo-advanced", source: "https://packager.dev/avo-hq"
end

gem "ransack"

# === Run bundle install ===
Bundler.with_unbundled_env { run "bundle install" }

file "config/routes/avo.rb", "# Avo admin panel
if defined?(Avo::Engine)
  authenticate :user, lambda { |u| u.developer? } do
    mount Avo::Engine, at: Avo.configuration.root_path
  end
end"

route "draw \"avo\""

# === Copy template files ===
files.each do |path, contents|
  file path, contents
end