From f3d0b1b811b8e430caf5e22926d34e1a5cdaea7c Mon Sep 17 00:00:00 2001 From: Adrian Marin Date: Tue, 1 Apr 2025 16:42:58 +0300 Subject: [PATCH] enhancement: initial LLM rules --- .cursor/rules/avo.mdc | 37 +++++++ .cursor/rules/execution-context.mdc | 123 ++++++++++++++++++++++ .cursor/rules/ruby-general.mdc | 66 ++++++++++++ lib/generators/avo/rules_generator.rb | 15 +++ lib/generators/avo/templates/rules/avo.tt | 26 +++++ 5 files changed, 267 insertions(+) create mode 100644 .cursor/rules/avo.mdc create mode 100644 .cursor/rules/execution-context.mdc create mode 100644 .cursor/rules/ruby-general.mdc create mode 100644 lib/generators/avo/rules_generator.rb create mode 100644 lib/generators/avo/templates/rules/avo.tt diff --git a/.cursor/rules/avo.mdc b/.cursor/rules/avo.mdc new file mode 100644 index 000000000..972e29470 --- /dev/null +++ b/.cursor/rules/avo.mdc @@ -0,0 +1,37 @@ +--- +description: Descriptor +globs: +alwaysApply: true +--- +# Resources + +The Avo CRUD functionality uses the concept of a resource. A resource belongs to a model and a model may have multiple resources. +The model is how Rails talks to the database and the resource is how Avo talks to Rails and knows how to fetch and manipulate the records. + +Each resource is a class defined in a file. They inherit the `Avo::BaseResource` class which inherits `Avo::Resources::Base`. `Avo::BaseResource` is empty so the user can override anything they want on a global level in theyr own app. + +A resource has a multitude of options which are usually declared using the `self.OPTION_NAME = ` format. They can take a simple value like a string, boolean, symbol, hash or array or they can take an `ExecutionContext` which will give the developer much more control over what they can return from it. + +## Fields + +Fields are declared using the `field` method inside a `fields` method in a resource like so: + +```ruby +class Avo::Resources::Team < Avo::BaseResource + def fields + field :id, as: :id + field :name, as: :text + field :description, as: :textarea + end +end +``` + +All fields have a name which is the first argument and the type of the field, as the `as:` argument. + +Most fields support optons that are universal for all fields. Some examples are: readonly, disabled, help, format_using, update using, hide_on, show_on, and others. + +Some fields have their own proprietary options, like `rows` for `textarea` fields and `options` for `select` fields. + +All field options: + + diff --git a/.cursor/rules/execution-context.mdc b/.cursor/rules/execution-context.mdc new file mode 100644 index 000000000..d813032bd --- /dev/null +++ b/.cursor/rules/execution-context.mdc @@ -0,0 +1,123 @@ +--- +description: Descriptor +globs: +alwaysApply: true +--- +# Execution context + +Avo enables developers to hook into different points of the application lifecycle using blocks. +That functionality can't always be performed in void but requires some pieces of state to set up some context. + +Computed fields are one example. + +```ruby +field :full_name, as: :text do + "#{record.first_name} #{record.last_name}" +end +``` + +In that block we need to pass the `record` so you can compile that value. We send more information than just the `record`, we pass on the `resource`, `view`, `view_context`, `request`, `current_user` and more depending on the block that's being run. + +## How does the `ExecutionContext` work? + +The `ExecutionContext` is an object that holds some pieces of state on which we execute a lambda function. + +```ruby +module Avo + class ExecutionContext + + attr_accessor :target, :context, :params, :view_context, :current_user, :request + + def initialize(**args) + # If target don't respond to call, handle will return target + # In that case we don't need to initialize the others attr_accessors + return unless (@target = args[:target]).respond_to? :call + + args.except(:target).each do |key,value| + singleton_class.class_eval { attr_accessor "#{key}" } + instance_variable_set("@#{key}", value) + end + + # Set defaults on not initialized accessors + @context ||= Avo::Current.context + @params ||= Avo::Current.params + @view_context ||= Avo::Current.view_context + @current_user ||= Avo::Current.current_user + @request ||= Avo::Current.request + end + + delegate :authorize, to: Avo::Services::AuthorizationService + + # Return target if target is not callable, otherwise, execute target on this instance context + def handle + target.respond_to?(:call) ? instance_exec(&target) : target + end + end +end + +# Use it like so. +SOME_BLOCK = -> { + "#{record.first_name} #{record.last_name}" +} + +Avo::ExecutionContext.new(target: &SOME_BLOCK, record: User.first).handle +``` + +This means you could throw any type of object at it and it it responds to a `call` method wil will be called with all those objects. + + + + + + + + + + + + + + + + diff --git a/.cursor/rules/ruby-general.mdc b/.cursor/rules/ruby-general.mdc new file mode 100644 index 000000000..30ba566c8 --- /dev/null +++ b/.cursor/rules/ruby-general.mdc @@ -0,0 +1,66 @@ +--- +description: +globs: +alwaysApply: true +--- + + You are an expert in Ruby on Rails, PostgreSQL, Hotwire (Turbo and Stimulus), and Tailwind CSS. + + Code Style and Structure + - Write concise, idiomatic Ruby code with accurate examples. + - Follow Rails conventions and best practices. + - Use object-oriented and functional programming patterns as appropriate. + - Prefer iteration and modularization over code duplication. + - Use descriptive variable and method names (e.g., user_signed_in?, calculate_total). + - Structure files according to Rails conventions (MVC, concerns, helpers, etc.). + + Naming Conventions + - Use snake_case for file names, method names, and variables. + - Use CamelCase for class and module names. + - Follow Rails naming conventions for models, controllers, and views. + + Ruby and Rails Usage + - Use Ruby 3.x features when appropriate (e.g., pattern matching, endless methods). + - Leverage Rails' built-in helpers and methods. + - Use ActiveRecord effectively for database operations. + + Syntax and Formatting + - Follow the Ruby Style Guide (https://rubystyle.guide/) + - Use Ruby's expressive syntax (e.g., unless, ||=, &.) + - Prefer single quotes for strings unless interpolation is needed. + + Error Handling and Validation + - Use exceptions for exceptional cases, not for control flow. + - Implement proper error logging and user-friendly messages. + - Use ActiveModel validations in models. + - Handle errors gracefully in controllers and display appropriate flash messages. + + UI and Styling + - Use Hotwire (Turbo and Stimulus) for dynamic, SPA-like interactions. + - Implement responsive design with Tailwind CSS. + - Use Rails view helpers and partials to keep views DRY. + + Performance Optimization + - Use database indexing effectively. + - Implement caching strategies (fragment caching, Russian Doll caching). + - Use eager loading to avoid N+1 queries. + - Optimize database queries using includes, joins, or select. + + Key Conventions + - Follow RESTful routing conventions. + - Use concerns for shared behavior across models or controllers. + - Implement service objects for complex business logic. + - Use background jobs (e.g., Sidekiq) for time-consuming tasks. + + Testing + - Write comprehensive tests using RSpec or Minitest. + - Follow TDD/BDD practices. + - Use factories (FactoryBot) for test data generation. + + Security + - Implement proper authentication and authorization (e.g., Devise, Pundit). + - Use strong parameters in controllers. + - Protect against common web vulnerabilities (XSS, CSRF, SQL injection). + + Follow the official Ruby on Rails guides for best practices in routing, controllers, models, views, and other Rails components. + \ No newline at end of file diff --git a/lib/generators/avo/rules_generator.rb b/lib/generators/avo/rules_generator.rb new file mode 100644 index 000000000..f7d09bc20 --- /dev/null +++ b/lib/generators/avo/rules_generator.rb @@ -0,0 +1,15 @@ +require_relative "base_generator" + +module Generators + module Avo + class RulesGenerator < BaseGenerator + source_root File.expand_path("templates", __dir__) + + namespace "avo:rules" + + def create_resource_file + template "rules/avo.tt", ".cursor/rules/avo.mdc" + end + end + end +end diff --git a/lib/generators/avo/templates/rules/avo.tt b/lib/generators/avo/templates/rules/avo.tt new file mode 100644 index 000000000..cbe008c0c --- /dev/null +++ b/lib/generators/avo/templates/rules/avo.tt @@ -0,0 +1,26 @@ +# Resources + +Each resource is defined in a file. + +## Fields + +Fields are declared using the `field` method inside a `fields` method in a resource like so: + +```ruby +class Avo::Resources::Team < Avo::BaseResource + def fields + field :id, as: :id + field :name, as: :text + field :description, as: :textarea + end +end +``` + +All fields have a name which is the first argument and the type of the field, as the `as:` argument. + +Most fields support optons that are universal for all fields. Some examples are: readonly, disabled, help, format_using, update using, hide_on, show_on, and others. +Some options are just for some fields. + +All field options: + +