- You MUST NOT try and generate a Rails app from scratch on your own by generating each file. For a NEW app you MUST use
rails newfirst to generate all of the boilerplate files necessary. - Create an app in the current directory with
rails new . - Use Tailwind CSS for styling. Use
--css tailwindas an option on therails newcall to do this automatically. - Use Ruby 3.2+ and Rails 8.0+ practices.
- Use the default Minitest approach for testing, do not use RSpec.
- Default to using SQLite in development.
rails newwill do this automatically but take care if you write any custom SQL that it is SQLite compatible. - An app can be built with a devcontainer such as
rails new myapp --devcontainerbut only do this if requested directly. - Rails apps have a lot of directories to consider, such as app, config, db, etc.
- Adhere to MVC conventions: singular model names (e.g., Product) map to plural tables (products); controllers are plural.
- Guard against incapable browsers accessing controllers with
allow_browser versions: :modern bin/rails serverruns the current app locally, but you can usebin/devto run the app along with background jobs, Tailwind CSS watcher, and other niceties included inProcfile.dev.- Rails 8 introduces a new
scriptfolder for one-off or general-purpose scripts, you can create a script withbin/rails generate script my_scriptand then such scripts can be run withbundle exec ruby script/my_script.rb - Do not use Sprockets, it is old fashioned in Rails 8.
- Rails'
runnercommand can be used to run one liners, e.g.bin/rails runner "user = User.new; user.email_address='[email protected]'; user.save"and so forth. - Models are created like so:
bin/rails generate model Product name:string - Use Rails' built-in generators for models, controllers, and migrations to enforce Rails standards.
bin/rails db:migrateruns database migrations after you have added or changed models.- Models are queried like so:
Product.all,Product.where(name: "Pants"),Product.order(name: :asc),Book.where("title = ?", params[:title]),Book.where("created_at >= :start_date AND created_at <= :end_date",{ start_date: params[:start_date], end_date: params[:end_date] })- these can also be chained for more complex queries. - Range queries can also be done:
Book.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) - SQL's
INcan be mimicked like so:Customer.where(orders_count: [1, 3, 5])andCustomer.where.not(orders_count: [1, 3, 5]) - Chained queries/conditions act like
ANDin SQL. To do anORconnection, you can do this:Customer.where(last_name: "Smith").or(Customer.where(orders_count: [1, 3, 5])) - You can order by multiple columns:
Book.order(title: :asc, created_at: :desc) LIMITandOFFSETare possible:Customer.limit(5).offset(30)GROUP BYis possible:Order.select("created_at").group("created_at")as well as group counts:Order.group(:status).countHAVINGis done like so:Order.select("created_at as ordered_date, sum(total) as total_price").group("created_at").having("sum(total) > ?", 200)- You can specify certain conditions to be removed using the
unscopemethod. - Scopes can be used to define named queries on models, e.g.
scope :in_print, -> { where(out_of_print: false) },scope :out_of_print_and_expensive, -> { out_of_print.where("price > 500") } - There can be a default scope for models:
default_scope { where(active: true) } - Scopes can be merged in queries by chaining their calls.
- The
find_or_create_bymethod checks whether a record with the specified attributes exists. If it doesn't, thencreateis called. - Custom SQL can be used if strictly necessary:
Customer.find_by_sql("SELECT * FROM customers INNER JOIN orders ON customers.id = orders.customer_id ORDER BY customers.created_at desc") exists?can be used to check if something simply exists:Customer.exists?(1).. for larger numbersany?andmany?can be used.- Models can have also enums, like so:
enum :status, [:shipped, :being_packed, :complete, :cancelled] - Use
annotatein query chains as a way to describe what the query is doing as this will appear in the logs for debugging purposes, e.g.User.annotate("selecting user names").select(:name) - This is an efficient way to work with large groups of results:
Customer.where(weekly_subscriber: true).find_each do |customer| - Use RESTful routing (prefer resources :products) and URL helpers for consistency.
- Implement strong parameters in controllers to whitelist permitted attributes. For example:
def product_params; params.expect(product: [ :name, :description ]); end - Use validations in models to ensure data fits relevant constraints, e.g.
validates :name, presence: trueorvalidates :inventory_count, numericality: { greater_than_or_equal_to: 0 } - Rails routes look like so:
get "/products", to: "products#index"and are placed inconfig/routes.rb. Sometimes they are automatically generated by generators. - Use
before_actioncallbacks to DRY common tasks (e.g., loading records). Actions can be scoped to particular methods:before_action :set_product, only: %i[ show edit update ] - Active Record has many callbacks including
after_create,before_validation,after_validation,before_save,before_create,after_create,before_destroy,after_initialize,after_find,after_touch. Validations can be used conditionally like so:before_save :normalize_card_number, if: :paid_with_card?.. which can also take a lambda:before_save :normalize_card_number, if: ->(order) { order.paid_with_card? } - If any callback raises an exception, the execution chain gets halted and a rollback is issued, and the error will be re-raised. For a softer failure such as for creating an object you can use
throwlike so:throw :abort if total_price < 0 - Active Record models can have many 'associations' to connect models together, e.g.
has_many :books, dependent: :destroy,belongs_to :author,has_one,has_many :through, andhas_and_belongs_to_many - Write views with ERB using <%= %> for output and <% %> for logic; extract shared code into partials, e.g.
<%= render "form", product: @product %>and<%= render partial: "product", locals: { product: @product } %>- in the latter case<% local_assigns[:product] %>is used in the partial to access the local passed. - Due to conventions
<%= render "product", product: @product %>can be shortened to<%= render @product %>as Rails can figure out the model from the object. - Collections of partials can be rendered:
<%= render partial: "product", collection: @products %> - Partials can be rendered with 'spacer' templates in between:
<%= render partial: @products, spacer_template: "product_ruler" %> - Manage assets via Propshaft and import maps; use Hotwire (Turbo/Stimulus) for JS without extra build steps.
- Adopt Active Storage and Action Text for file uploads and rich text editing.
- Action Text is an included way to get rich text fields out of the box. It can be installed like so:
bin/rails action_text:installafter which you need to bundle install and run DB migrations again. Then models can get things likehas_rich_text :descriptionand<%= form.rich_text_area :description %>can be used in a view to render a rich text field based on Trix. bin/rails routescan be run to see all current routes of an app if needed.- Generators look like
bin/rails generate controller Products index --skip-routesandbin/rails generate model Product name:stringbut it is also possible to create 'scaffold's that will flesh out a model, controller, and views for a defined set of columns. - Views have many helpers such as for creating links:
<%= link_to "New product", new_product_path %>, or forms:<%= form_with model: @product do |form| %>and form fields:<%= form.text_field :name %> - Rails 8 includes a new authentication generator which can be run with
bin/rails generate authentication- this creates User and Session models (run migrations after). Users have default columns includingemail_address,passwordandpassword_confirmation - Logging out can be done with something like
<%= button_to "Log out", session_path, method: :delete if authenticated? %> - Unauthenticated access can be allowed on controller methods like so:
allow_unauthenticated_access only: %i[ index show ] - There's a
authenticated?helper for use in views, e.g.<%= link_to "New product", new_product_path if authenticated? %>. Similarly you could show a Login link:<%= link_to "Login", new_session_path unless authenticated? %> - Caching can be done on parts of views like so:
<% cache @product do %><h1><%= @product.name %></h1><% end %> - Active Storage is a Rails 8 library that makes it easy to upload and store files, including from rich text fields. You could attach a file to a model like so:
has_one_attached :featured_imagethen have a form field like so:<%= form.file_field :featured_image, accept: "image/*" %>then eventually display an image like so:<%= image_tag @product.featured_image if @product.featured_image.attached? %> - Uploaded files can be handled in controller methods like so:
uploaded_file = params[:csv_file],if uploaded_file.present?, anduploaded_file.read - Internationalization (i18n) can be done by using the
thelper in views like so:<h1><%= t "hello" %></h1>and then the matching key "hello" can be used in files likeconfig/locales/en.ymlto define the strings in each locale's language. For example, inconfig/locales/es.ymlyou could havees:\n hello: "Hola mundo:. These are YAML files. - Locales for i18n could be switched like so:
around_action :switch_locale\n\ndef switch_locale(&action)\nlocale = params[:locale] || I18n.default_locale\nI18n.with_locale(locale, &action)\nend - Dotted keys in
tcalls can be used to do relative locale lookups: e.g.<h1><%= t ".title" %></h1>- this would then look under keys matching the controller and view name in the YAML file. For example:en:\n hello: "Hello world"\n products:\n index:\n title: "Products" - Action Mailer is a part of Rails for sending emails. Mailers can be created like so:
bin/rails g mailer Product in_stockand then methods in mailers can be used a bit like a controller to find objects and then send emails:mail to: params[:subscriber].emailwhich would then render views likeapp/views/product_mailer/in_stock.text.erbto produce the email content. - To better organize model code, common elements can be extracted into 'concerns' which can be included in multiple models in files such as
app/models/product/notifications.rband code likemodule Product::Notifications\n extend ActiveSupport::Concern\n\n included do\n has_many :subscribers, dependent: :destroy\n after_update_commit :notify_subscribers, if: :back_in_stock?\n end\n\n normal methods here..\nendthen in models you could useinclude Notificationsto being in that concern. - Active Record has a feature called
generates_token_forthat can generate unique tokens to find database records for different purposes. You can use this for generating a unique unsubscribe token to use in the email's unsubscribe URL, e.g.generates_token_for :unsubscribeand then look it up like so:@subscriber = Subscriber.find_by_token_for(:unsubscribe, params[:token])and in the view:<%= link_to "Unsubscribe", unsubscribe_url(token: params[:subscriber].generate_token_for(:unsubscribe)) %> - Rails' asset pipeline is called Propshaft. It takes CSS, JavaScript, images, and other assets and serves them to the browser so if
app/assets/stylesheets/application.cssis changed, say, it all just works. - Rails uses import maps for JavaScript by default. You can find the JavaScript pins in
config/importmap.rb- they look likepin "@hotwired/stimulus", to: "stimulus.min.js"andpin_all_from "app/javascript/controllers", under: "controllers" - Hotwire is a default Rails JavaScript framework designed to take full advantage of server-side generated HTML. It includes Turbo for handling navigation, form submission, page components and updates. Stimulus is a JS framework for introducing custom JS to pages. Native is used for making hybrid mobile apps.
bin/rubocopcan be run to check code quality and formatting.bin/brakemancan be run to check security issues with the code.- Solid Queue is a new part of Rails for running tasks asynchronously behind-the-scenes in a separate process with ActiveJob.
- Solid Cable is used with Action Cable to use WebSockets with Rails apps without needing Redis.
- Solid Cache is a Redis-free cache store for ActiveSupport.
- Radio buttons in views look like so:
<%= form.radio_button :flavor, "chocolate_chip" %>or you can do them on a group of values:<%= form.collection_radio_buttons :city_id, City.order(:name), :id, :name %> - Labels in forms look like so:
<%= form.label :flavor_chocolate_chip, "Chocolate Chip" %> - There are many view helpers, such as
<%= form.date_field :born_on %>,<%= form.time_field :started_at %>,<%= form.password_field :password %>,<%= form.email_field :address %>,<%= form.url_field :homepage %>,<%= form.hidden_field :parent_id, value: "foo" %>,<%= form.number_field :price, in: 1.0..20.0, step: 0.5 %>,<%= form.search_field :name %>- use them as appropriate for the data required. - Forms can be sent with custom methods:
form_with(url: search_path, method: "patch")or<%= form_with url: "/posts/1", method: :patch do |form| %> - Select fields can be done in forms like so:
<%= form.select :city, ["Berlin", "Chicago", "Madrid"] %>or with distinct values:<%= form.select :city, [["Berlin", "BE"], ["Chicago", "CHI"], ["Madrid", "MD"]] %>or with a selected value:<%= form.select :city, [["Berlin", "BE"], ["Chicago", "CHI"], ["Madrid", "MD"]], selected: "CHI" %> - Rails 8 comes with a deployment tool called Kamal you can use to deploy an app directly to a server. It uses Docker containers. Look at
config/deploy.ymland configure it appropriately if the user asks to use Kamal, otherwise ignore it. - IMPORTANT: For a new Rails app you must use
rails newfirst to generate all of the boilerplate files necessary before attempting any edits. Do not create a new Rails app yourself from many files.
-
-
Save davibusanello/2e2be8c03afe8a66cbfa0c900a1657e2 to your computer and use it in GitHub Desktop.
CONVENTIONS.md file for AI Rails 8 development
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment