Class: RailsConsoleAi::Skill
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- RailsConsoleAi::Skill
- 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
- CONTENT_ATTRIBUTES =
Attributes that, if changed, invalidate the current approval and revert the skill back to “proposed”. Status / approver columns are excluded so that an explicit approve! call doesn’t reset its own approval.
%w[name description body tags bypass_guards_for_methods].freeze
Class Method Summary collapse
- .connection ⇒ Object
- .decode_json_array(raw) ⇒ Object
- .encode_json_array(value) ⇒ Object
-
.record_use!(id) ⇒ Object
Atomically bump use_count + last_used_at without firing callbacks / validations / updated_at.
Instance Method Summary collapse
-
#approve!(approved_by:) ⇒ Object
Marks the current head as approved.
- #approved? ⇒ Boolean
- #approved_at ⇒ Object
- #approved_by ⇒ Object
- #bypass_guards_for_methods ⇒ Object
- #bypass_guards_for_methods=(value) ⇒ Object
- #decode_json_array(raw) ⇒ Object
- #encode_json_array(value) ⇒ Object
- #has_attribute_status? ⇒ Boolean
- #last_used_at ⇒ Object
- #proposed? ⇒ Boolean
-
#status ⇒ Object
Defensive accessors — if ‘ai_db_migrate` hasn’t been run yet, the status / approval columns may be missing on an older table.
-
#tags ⇒ Object
Manual JSON accessors keep us off Rails-version-specific ‘serialize` syntax (positional coder in Rails 5–6, keyword coder in Rails 7+).
- #tags=(value) ⇒ Object
- #to_hash ⇒ Object
-
#update_with_version!(attrs, edited_by: nil, change_note: nil, preserve_approval: false) ⇒ Object
Assigns attrs, saves, and records one SkillVersion snapshot of the post-save state.
- #use_count ⇒ Object
Class Method Details
.connection ⇒ Object
29 30 31 32 33 34 35 36 37 |
# File 'app/models/rails_console_ai/skill.rb', line 29 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 |
.decode_json_array(raw) ⇒ Object
121 122 123 124 125 126 127 |
# File 'app/models/rails_console_ai/skill.rb', line 121 def self.decode_json_array(raw) return [] if raw.nil? || (raw.respond_to?(:empty?) && raw.empty?) return raw if raw.is_a?(Array) JSON.parse(raw) rescue JSON::ParserError [] end |
.encode_json_array(value) ⇒ Object
129 130 131 |
# File 'app/models/rails_console_ai/skill.rb', line 129 def self.encode_json_array(value) JSON.dump(Array(value)) 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. No-op (returns false) if the table doesn’t have the columns yet — that keeps older installs working until they run ai_db_migrate.
101 102 103 104 105 106 107 108 109 110 111 |
# File 'app/models/rails_console_ai/skill.rb', line 101 def self.record_use!(id) return false unless connection.column_exists?(table_name, :use_count) 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.}") false end |
Instance Method Details
#approve!(approved_by:) ⇒ Object
Marks the current head as approved. Logs a version row with the approver name so the audit trail captures the approval moment.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'app/models/rails_console_ai/skill.rb', line 176 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
73 |
# File 'app/models/rails_console_ai/skill.rb', line 73 def approved?; status.to_s == STATUS_APPROVED; end |
#approved_at ⇒ Object
68 69 70 |
# File 'app/models/rails_console_ai/skill.rb', line 68 def approved_at has_attribute?(:approved_at) ? read_attribute(:approved_at) : nil end |
#approved_by ⇒ Object
64 65 66 |
# File 'app/models/rails_console_ai/skill.rb', line 64 def approved_by has_attribute?(:approved_by) ? read_attribute(:approved_by) : nil end |
#bypass_guards_for_methods ⇒ Object
49 50 51 |
# File 'app/models/rails_console_ai/skill.rb', line 49 def bypass_guards_for_methods decode_json_array(read_attribute(:bypass_guards_for_methods)) end |
#bypass_guards_for_methods=(value) ⇒ Object
53 54 55 |
# File 'app/models/rails_console_ai/skill.rb', line 53 def bypass_guards_for_methods=(value) write_attribute(:bypass_guards_for_methods, encode_json_array(value)) end |
#decode_json_array(raw) ⇒ Object
133 134 135 |
# File 'app/models/rails_console_ai/skill.rb', line 133 def decode_json_array(raw) self.class.decode_json_array(raw) end |
#encode_json_array(value) ⇒ Object
137 138 139 |
# File 'app/models/rails_console_ai/skill.rb', line 137 def encode_json_array(value) self.class.encode_json_array(value) end |
#has_attribute_status? ⇒ Boolean
93 94 95 |
# File 'app/models/rails_console_ai/skill.rb', line 93 def has_attribute_status? has_attribute?(:status) end |
#last_used_at ⇒ Object
117 118 119 |
# File 'app/models/rails_console_ai/skill.rb', line 117 def last_used_at has_attribute?(:last_used_at) ? read_attribute(:last_used_at) : nil end |
#proposed? ⇒ Boolean
72 |
# File 'app/models/rails_console_ai/skill.rb', line 72 def proposed?; status.to_s == STATUS_PROPOSED; end |
#status ⇒ Object
Defensive accessors — if ‘ai_db_migrate` hasn’t been run yet, the status / approval columns may be missing on an older table. Return safe defaults instead of blowing up with NameError.
60 61 62 |
# File 'app/models/rails_console_ai/skill.rb', line 60 def status has_attribute_status? ? read_attribute(:status) : STATUS_PROPOSED end |
#tags ⇒ Object
Manual JSON accessors keep us off Rails-version-specific ‘serialize` syntax (positional coder in Rails 5–6, keyword coder in Rails 7+).
41 42 43 |
# File 'app/models/rails_console_ai/skill.rb', line 41 def decode_json_array(read_attribute(:tags)) end |
#tags=(value) ⇒ Object
45 46 47 |
# File 'app/models/rails_console_ai/skill.rb', line 45 def (value) write_attribute(:tags, encode_json_array(value)) end |
#to_hash ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'app/models/rails_console_ai/skill.rb', line 75 def to_hash { 'id' => id, 'name' => name, 'description' => description, 'body' => body, 'tags' => , 'bypass_guards_for_methods' => bypass_guards_for_methods, '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 of the post-save state. Every save produces exactly one version row, so the version log is a complete history including the current state (the most recent version mirrors ‘self`).
If ‘preserve_approval` is false (the default), any change to a content attribute reverts the skill back to “proposed” and clears the approver. Pass true from the approve! flow so approval doesn’t reset itself.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'app/models/rails_console_ai/skill.rb', line 148 def update_with_version!(attrs, edited_by: nil, change_note: nil, preserve_approval: false) transaction do assign_attributes(attrs) if !preserve_approval && approved? && content_dirty? self.status = STATUS_PROPOSED self.approved_by = nil self.approved_at = nil end save! RailsConsoleAi::SkillVersion.create!( skill_id: id, name: name, description: description, body: body, tags: , bypass_guards_for_methods: bypass_guards_for_methods, status: status, edited_by: edited_by, change_note: change_note ) end self end |
#use_count ⇒ Object
113 114 115 |
# File 'app/models/rails_console_ai/skill.rb', line 113 def use_count has_attribute?(:use_count) ? (read_attribute(:use_count) || 0) : 0 end |