Rails app template
Jumpstart Pro
Add Avo 3 to your Jumpstart Pro 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
CTO, Wyndy
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://v3.avohq.io/templates/jumpstart-pro
# Repository: https://github.com/avo-hq/jumpstart-pro-app-template
# List of all Avo files with path and contents
files = {"app/avo/cards/active_subscriptions.rb"=>"class Avo::Cards::ActiveSubscriptions < Avo::Dashboards::MetricCard\n self.id = \"active_subscriptions\"\n self.label = \"Active subscriptions\"\n self.description = \"Total number of active subscriptions\"\n\n def query\n result ::Pay::Subscription.active.count\n end\nend\n", "app/avo/cards/total_revenue.rb"=>"class Avo::Cards::TotalRevenue < Avo::Dashboards::MetricCard\n self.id = \"total_revenue\"\n self.label = \"Total revenue\"\n self.prefix = \"$\"\n\n def query\n case arguments[:period]\n when :last_12_months\n result last_12_months\n when :last_month\n result last_month\n when :this_month\n result this_month\n else\n result total_revenue\n end\n end\n\n def total_revenue\n revenue_in_cents = ::Pay::Charge.sum(:amount)\n refunds_in_cents = ::Pay::Charge.sum(:amount_refunded)\n (revenue_in_cents - refunds_in_cents) / 100.0\n end\n\n def last_12_months\n revenue_for_range 12.months.ago..Time.current\n end\n\n def last_month\n month = Time.current.prev_month\n revenue_for_range month.beginning_of_month..month.end_of_month\n end\n\n def this_month\n month = Time.current\n revenue_for_range month.beginning_of_month..month.end_of_month\n end\n\n def revenue_for_range(range)\n revenue_in_cents = ::Pay::Charge.where(created_at: range).sum(:amount)\n refunds_in_cents = ::Pay::Charge.where(created_at: range).sum(:amount_refunded)\n (revenue_in_cents - refunds_in_cents) / 100.0\n end\nend\n", "app/avo/cards/users_count.rb"=>"class Avo::Cards::UsersCount < Avo::Dashboards::MetricCard\n self.id = \"users_count\"\n self.label = \"Users count\"\n self.description = \"Total number of users registered\"\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 divider label: \"Revenue\"\n card Avo::Cards::TotalRevenue,\n arguments: {\n period: :this_month\n },\n label: \"This month\"\n card Avo::Cards::TotalRevenue,\n arguments: {\n period: :last_month\n },\n label: \"Last month\"\n card Avo::Cards::TotalRevenue,\n arguments: {\n period: :last_12_months\n },\n label: \"Last 12 months\"\n card Avo::Cards::TotalRevenue\n\n divider\n\n card Avo::Cards::UsersCount\n card Avo::Cards::ActiveSubscriptions\n end\nend\n", "app/avo/resources/account.rb"=>"class Avo::Resources::Account < Avo::BaseResource\n self.title = :name\n self.includes = [:owner, :users]\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.name,\n image_url: record.avatar\n }\n }\n }\n\n def fields\n field :id, as: :id\n field :avatar,\n as: :file,\n is_image: true,\n link_to_resource: true,\n rounded: true,\n hide_on: :forms\n field :owner, as: :belongs_to\n field :name, as: :text\n field :personal, as: :boolean\n field :users_count, as: :text do\n record.users.length\n end\n\n sidebar do\n field :extra_billing_info, as: :textarea\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n end\n\n tabs do\n field :subscriptions, as: :has_many\n field :pay_customers, as: :has_many\n field :charges, as: :has_many\n field :account_users, as: :has_many\n field :users, as: :has_many\n end\n end\nend\n", "app/avo/resources/account_user.rb"=>"class Avo::Resources::AccountUser < Avo::BaseResource\n self.title = :id\n self.includes = []\n\n def fields\n field :id, as: :id\n field :account, as: :belongs_to\n field :user, as: :belongs_to\n field :roles,\n as: :boolean_group,\n options: {\n admin: \"Administrator\",\n member: \"Member\",\n }\n field :admin?, as: :boolean do\n record.roles[\"admin\"] == true\n end\n\n sidebar do\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n end\n end\nend\n", "app/avo/resources/announcement.rb"=>"class Avo::Resources::Announcement < Avo::BaseResource\n self.title = :title\n self.includes = []\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], title_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 :title, as: :text\n field :description, as: :trix, always_show: true\n field :kind, as: :badge, options: {success: \"new\", info: \"improvement\", danger: \"fix\", warning: \"update\"}, hide_on: :forms\n field :kind, as: :select, options: Announcement::TYPES.map { |t| [t, t] }.to_h, show_on: :forms\n field :published_at, as: :date_time\n end\nend\n", "app/avo/resources/charge.rb"=>"class Avo::Resources::Charge < Avo::BaseResource\n self.title = :processor_id\n self.includes = []\n self.model_class = Pay::Charge\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], processor_id_cont: params[:q], m: \"or\").result(distinct: false)},\n item: -> {\n {\n title: record.processor_id\n }\n }\n }\n\n def fields\n field :id, as: :id\n field :customer, as: :belongs_to\n field :amount, as: :text\n field :amount_refunded, as: :text\n field :created_at, as: :date_time\n\n with_options only_on: :show do\n field :processor_id, as: :text, format_using: -> do\n link_to value, view_context.controller.charge_processor_url(model), target: :_blank\n rescue\n value\n end\n field :subscription, as: :belongs_to\n field :updated_at, as: :date_time\n end\n end\nend\n", "app/avo/resources/connected_account.rb"=>"class Avo::Resources::ConnectedAccount < Avo::BaseResource\n self.title = :id\n self.includes = []\n self.model_class = ConnectedAccount\n\n def fields\n field :id, as: :id\n field :user, as: :belongs_to\n field :image_url, as: :external_image\n field :expired?, as: :boolean, hide_on: :forms\n end\nend\n", "app/avo/resources/customer.rb"=>"class Avo::Resources::Customer < Avo::BaseResource\n self.title = :name\n self.includes = [:owner]\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], processor_id_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.name,\n }\n }\n }\n self.model_class = Pay::Customer\n\n def fields\n field :id, as: :id\n field :owner, as: :belongs_to, polymorphic_as: :owner, types: [::Account]\n\n field :processor, as: :badge, options: {success: \"stripe\"}\n field :processor_id, as: :text, format_using: -> do\n link_to(value, view_context.controller.customer_processor_url(model), target: :_blank)\n rescue\n value\n end\n field :default, as: :boolean\n field :data, as: :key_value\n\n sidebar do\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n field :deleted_at, as: :date_time\n end\n\n tabs do\n field :subscriptions, as: :has_many\n field :charges, as: :has_many\n end\n end\nend\n", "app/avo/resources/payment_method.rb"=>"class Avo::Resources::PaymentMethod < Avo::BaseResource\n self.includes = []\n self.model_class = Pay::PaymentMethod\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], processor_id_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.id,\n }\n }\n }\n\n def fields\n field :id, as: :id\n field :customer, as: :belongs_to\n field :processor_id, as: :text\n field :type, as: :text\n field :default, as: :boolean\n field :data, as: :key_value\n\n sidebar do\n field :extra_billing_info, as: :key_value\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n end\n end\nend\n", "app/avo/resources/plan.rb"=>"class Avo::Resources::Plan < Avo::BaseResource\n self.title = :name\n self.includes = []\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], description_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 :description, as: :text, hide_on: :index\n field :hidden, as: :boolean\n field :amount, as: :number, help: \"Price in cents\", format_using: -> do\n if view == :edit || view == :new\n value\n else\n (BigDecimal(value.to_s) / 100).round(2)\n end\n end\n field :currency, as: :select, options: Pay::Currency.all.map { |iso, v| [\"\#{iso.upcase} - \#{v[\"name\"]}\", iso] }.to_h\n field :interval, as: :select, options: [:month, :year]\n field :interval_count, as: :number\n field :trial_period_days, as: :number\n\n sidebar do\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n heading \"Processor details\"\n field :stripe_id, as: :text, format_using: -> do\n if view == :edit || view == :new\n value\n else\n link_to value, \"https://dashboard.stripe.com/plans/\#{value}\", target: :_blank\n end\n rescue\n value\n end\n field :braintree_id, as: :text, format_using: -> do\n if view == :edit || view == :new\n value\n else\n link_to value, \"https://sandbox.braintreegateway.com/merchants/\#{Pay::Braintree.merchant_id}/plans/\#{id}\", target: :_blank\n end\n rescue\n value\n end\n field :paddle_id, as: :text\n field :fake_processor_id, as: :text\n end\n\n field :features, as: :tags\n end\nend\n", "app/avo/resources/subscription.rb"=>"class Avo::Resources::Subscription < Avo::BaseResource\n self.title = :processor_id\n self.includes = [:owner, :customer]\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], processor_id_cont: params[:q], m: \"or\").result(distinct: false) },\n item: -> {\n {\n title: record.processor_id,\n }\n }\n }\n self.model_class = Pay::Subscription\n\n def fields\n field :id, as: :id\n with_options only_on: :index do\n field :customer, as: :belongs_to\n field :name, as: :text\n field :active?, as: :boolean, name: \"Active\"\n field :cancelled?, as: :boolean, name: \"Cancelled\"\n end\n field :owner, as: :has_one\n field :processor_id, as: :text, format_using: ->(id) do\n link_to id, view_context.controller.subscription_processor_url(model), target: :_blank\n rescue\n id\n end\n field :active?, as: :boolean\n field :processor, as: :badge, options: {success: \"Stripe\"} do |model, *args|\n name = record.payment_processor.class.to_s.split(\"::\").second\n if name.downcase.include? \"fake\"\n \"Fake\"\n else\n name\n end\n rescue\n \"Unknown\"\n end\n\n sidebar do\n field :status, as: :status, readonly: true, failed_when: [:canceled], loading_when: [:trialing]\n field :trial_ends_at_ago, as: :text\n field :created_at, as: :date_time, readonly: true\n field :ends_at, as: :date_time, readonly: true\n field :processor_id, as: :text, readonly: true, as_html: true do\n case record.customer.processor\n when \"stripe\"\n link_to(record.processor_id, \"https://dashboard.stripe.com/subscriptions/\#{record.processor_id}\", target: :_blank)\n else\n record.processor_id\n end\n end\n field :processor_plan, as: :text, readonly: true\n field :quantity, as: :number, readonly: true\n end\n end\nend\n", "app/avo/resources/user.rb"=>"class Avo::Resources::User < Avo::BaseResource\n self.title = :name\n self.includes = [:accounts]\n self.search = {\n query: -> { query.ransack(id_eq: params[:q], name_cont: params[:q], email_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 :avatar, as: :file, is_image: true, hide_on: :forms, link_to_resource: true\n field :name, as: :text\n field :email, as: :text\n field :admin, as: :boolean, help: \"Whether the user has admin capabilities.\"\n\n with_options hide_on: :forms do\n field :number_of_accounts, as: :text do\n record.accounts.length\n end\n end\n\n tabs do\n field :accounts, as: :has_many\n field :connected_accounts, as: :has_many\n end\n\n tabs do\n tab \"General\" do\n panel do\n field :time_zone, as: :text\n field :invitations_count, as: :text\n end\n end\n tab \"Account management\" do\n panel do\n field :password, as: :password\n field :password_confirmation, as: :password\n field :reset_password_token, as: :password\n field :reset_password_sent_at, as: :date_time\n field :remember_created_at, as: :date_time\n field :confirmation_token, as: :text\n field :confirmed_at, as: :date_time\n field :confirmation_sent_at, as: :date_time\n field :unconfirmed_email, as: :text\n field :created_at, as: :date_time\n field :updated_at, as: :date_time\n field :invitation_token, as: :text\n field :invitation_created_at, as: :date_time\n field :invitation_sent_at, as: :date_time\n field :invitation_accepted_at, as: :date_time\n field :invitation_limit, as: :number\n field :terms_of_service, as: :boolean\n field :accepted_terms_at, as: :date_time\n field :accepted_privacy_at, as: :date_time\n end\n end\n end\n end\nend\n", "app/controllers/avo/account_users_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# You shouldn't need to modify it in order to use Avo.\nclass Avo::AccountUsersController < Avo::ResourcesController\nend\n", "app/controllers/avo/accounts_controller.rb"=>"class Avo::AccountsController < Avo::ResourcesController\nend\n", "app/controllers/avo/announcements_controller.rb"=>"class Avo::AnnouncementsController < Avo::ResourcesController\nend\n", "app/controllers/avo/charges_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# You shouldn't need to modify it in order to use Avo.\nclass Avo::ChargesController < Avo::ResourcesController\n include Jumpstart::AdminPanelHelpers\nend\n", "app/controllers/avo/connected_accounts_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# You shouldn't need to modify it in order to use Avo.\nclass Avo::ConnectedAccountsController < Avo::ResourcesController\nend\n", "app/controllers/avo/customers_controller.rb"=>"class Avo::CustomersController < Avo::ResourcesController\n include Jumpstart::AdminPanelHelpers\nend\n", "app/controllers/avo/payment_methods_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# You shouldn't need to modify it in order to use Avo.\nclass Avo::PaymentMethodsController < Avo::ResourcesController\nend\n", "app/controllers/avo/plans_controller.rb"=>"class Avo::PlansController < Avo::ResourcesController\n include Jumpstart::AdminPanelHelpers\nend\n", "app/controllers/avo/subscriptions_controller.rb"=>"class Avo::SubscriptionsController < Avo::ResourcesController\n include Jumpstart::AdminPanelHelpers\nend\n", "app/controllers/avo/users_controller.rb"=>"class 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 = \"/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 = defined?(Avo::Pro) ? \"/avo/dashboards/overview\" : nil\n\n ## == Licensing ==\n # Add your license key here for Pro or Advanced licenses\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 {}\n end\n\n ## == Authentication ==\n # config.current_user_method = {}\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 = 'Avocadelicious'\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.tabs_style = :tabs # can be :tabs or :pills\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 \"Users\", icon: \"heroicons/outline/user-group\" do\n resource :announcement\n resource :user\n resource :account\n resource :account_user\n resource :plan\n end\n\n section \"Pay\", icon: \"heroicons/outline/currency-dollar\" do\n resource :customer\n resource :charge\n resource :payment_method\n resource :subscription\n end\n\n if Rails.env.development?\n link \"Jumpstart Config\", path: Avo::Current.view_context.main_app.jumpstart_path\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
What version of Avo would you like to install?
1. Avo Community (default)
2. Avo Pro
3. Avo Advanced
QUESTION
answer = ask(question, 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.0.1.beta4", source: "https://packager.dev/avo-hq"
when "pro"
gem "avo-pro", source: "https://packager.dev/avo-hq"
when "advanced"
gem "avo-advanced", source: "https://packager.dev/avo-hq"
end
# === Run bundle install ===
Bundler.with_unbundled_env { run "bundle install" }
# === Add route ===
route_contents = <<-ROUTES
# Avo admin panel
if defined?(Avo::Engine)
authenticated :user, lambda { |u| u.admin? } do
mount Avo::Engine, at: Avo.configuration.root_path
end
end
ROUTES
route route_contents
# === Copy template files ===
files.each do |path, contents|
file path, contents
end