Skip to content

enhancement: initial LLM rules #3762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .cursor/rules/avo.mdc
Original file line number Diff line number Diff line change
@@ -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:


123 changes: 123 additions & 0 deletions .cursor/rules/execution-context.mdc
Original file line number Diff line number Diff line change
@@ -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.

<Option name="`target`">

The block you'll pass to be evaluated. It may be anything but will only be evaluated if it responds to a `call` method.
</Option>

<Option name="`context`">

Aliased to [`Avo::Current.context`](mdc:avo-current#context).
</Option>

<Option name="`current_user`">

Aliased to [`Avo::Current.user`](mdc:avo-current#user).
</Option>

<Option name="`view_context`">

Aliased to [`Avo::Current.view_context`](mdc:avo-current#view_context).
</Option>

<Option name="`request`">

Aliased to [`Avo::Current.request`](mdc:avo-current#request).
</Option>

<Option name="`params`">

Aliased to [`Avo::Current.params`](mdc:avo-current#params).
</Option>

<Option name="Custom variables">

You can pass any variable to the `ExecutionContext` and it will be available in that block.
This is how we can expose `view`, `record`, and `resource` in the computed field example.

```ruby
Avo::ExecutionContext.new(target: &SOME_BLOCK, record: User.first, view: :index, resource: resource).handle
```
</Option>

<Option name="`helpers`">

Within the `ExecutionContext` you might want to use some of your already defined helpers. You can do that using the `helpers` object.

```ruby
# products_helper.rb
class ProductsHelper
# Strips the "CODE_" prefix from the name
def simple_name(name)
name.gsub "CODE_", ""
end
end

field :name, as: :text, format_using: -> { helpers.simple_name(value) }
```
</Option>
66 changes: 66 additions & 0 deletions .cursor/rules/ruby-general.mdc
Original file line number Diff line number Diff line change
@@ -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.

15 changes: 15 additions & 0 deletions lib/generators/avo/rules_generator.rb
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions lib/generators/avo/templates/rules/avo.tt
Original file line number Diff line number Diff line change
@@ -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:


Loading