Rails app template

Bullet Train

Add Avo 3 to your Bullet Trains app blazing fast!

I've been working with Rails for almost a decade now, and I've tried every popular admin panel gem out there.
Avo is hands-down the best solution we've found to quickly iterating on products and building tools to support and administrate our platforms.
David Lormor
David Lormor
CTO, Wyndy
Run the following command to quickly apply this app template

If you think this template needs changes please 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