Class: RailsConsoleAi::Skill

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/rails_console_ai/skill.rb

Constant Summary collapse

STATUS_PROPOSED =
'proposed'.freeze
STATUS_APPROVED =
'approved'.freeze
STATUSES =
[STATUS_PROPOSED, STATUS_APPROVED].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.connectionObject



28
29
30
31
32
33
34
35
36
# File 'app/models/rails_console_ai/skill.rb', line 28

def self.connection
  klass = RailsConsoleAi.configuration.connection_class
  if klass
    klass = Object.const_get(klass) if klass.is_a?(String)
    klass.connection
  else
    super
  end
end

.record_use!(id) ⇒ Object

Atomically bump use_count + last_used_at without firing callbacks / validations / updated_at. Safe to call from concurrent AI tool calls.



59
60
61
62
63
64
65
66
67
68
# File 'app/models/rails_console_ai/skill.rb', line 59

def self.record_use!(id)
  where(id: id).update_all([
    'use_count = COALESCE(use_count, 0) + 1, last_used_at = ?',
    Time.now.utc
  ])
  true
rescue ::ActiveRecord::ActiveRecordError => e
  RailsConsoleAi.logger.warn("RailsConsoleAi::Skill.record_use!(#{id.inspect}) failed: #{e.message}")
  false
end

Instance Method Details

#approve!(approved_by:) ⇒ Object

Raises:

  • (ArgumentError)


115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'app/models/rails_console_ai/skill.rb', line 115

def approve!(approved_by:)
  raise ArgumentError, 'approved_by is required' if approved_by.to_s.strip.empty?

  update_with_version!(
    {
      status:      STATUS_APPROVED,
      approved_by: approved_by,
      approved_at: Time.now.utc
    },
    edited_by: approved_by,
    change_note: "Approved by #{approved_by}",
    preserve_approval: true
  )
end

#approved?Boolean

Returns:

  • (Boolean)


55
# File 'app/models/rails_console_ai/skill.rb', line 55

def approved?; status.to_s == STATUS_APPROVED; end

#bodyObject



50
# File 'app/models/rails_console_ai/skill.rb', line 50

def body;        parsed['body']; end

#bypass_guards_for_methodsObject



52
# File 'app/models/rails_console_ai/skill.rb', line 52

def bypass_guards_for_methods; Array(parsed['bypass_guards_for_methods']); end

#content=(value) ⇒ Object



44
45
46
47
# File 'app/models/rails_console_ai/skill.rb', line 44

def content=(value)
  @parsed = nil
  super
end

#descriptionObject



49
# File 'app/models/rails_console_ai/skill.rb', line 49

def description; parsed['description']; end

#parsedObject

Parsed view of the raw markdown content. Memoized per-instance and cleared on assignment. Returns {} for invalid content so callers don’t need to nil-guard.



40
41
42
# File 'app/models/rails_console_ai/skill.rb', line 40

def parsed
  @parsed ||= (RailsConsoleAi::SkillLoader.parse(content.to_s) || {})
end

#proposed?Boolean

Returns:

  • (Boolean)


54
# File 'app/models/rails_console_ai/skill.rb', line 54

def proposed?; status.to_s == STATUS_PROPOSED; end

#tagsObject



51
# File 'app/models/rails_console_ai/skill.rb', line 51

def tags;        Array(parsed['tags']); end

#to_hashObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/models/rails_console_ai/skill.rb', line 70

def to_hash
  {
    'id'                        => id,
    'name'                      => name,
    'description'               => description,
    'body'                      => body,
    'tags'                      => tags,
    'bypass_guards_for_methods' => bypass_guards_for_methods,
    'content'                   => content,
    'status'                    => status,
    'approved_by'               => approved_by,
    'approved_at'               => approved_at,
    'use_count'                 => use_count,
    'last_used_at'              => last_used_at,
    'source'                    => :db,
    'updated_at'                => updated_at
  }
end

#update_with_version!(attrs, edited_by: nil, change_note: nil, preserve_approval: false) ⇒ Object

Assigns attrs, saves, and records one SkillVersion snapshot. Any change to ‘content` reverts approval back to “proposed” unless `preserve_approval: true` is passed (approve! does this).



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'app/models/rails_console_ai/skill.rb', line 92

def update_with_version!(attrs, edited_by: nil, change_note: nil, preserve_approval: false)
  transaction do
    assign_attributes(attrs)

    if !preserve_approval && approved? && changes.key?('content')
      self.status      = STATUS_PROPOSED
      self.approved_by = nil
      self.approved_at = nil
    end

    save!
    RailsConsoleAi::SkillVersion.create!(
      skill_id:    id,
      name:        name,
      content:     content,
      status:      status,
      edited_by:   edited_by,
      change_note: change_note
    )
  end
  self
end