Module: RubyLLM::Generators::GeneratorHelpers

Included in:
ChatUIGenerator, InstallGenerator, ToolGenerator, UpgradeGenerator
Defined in:
lib/generators/ruby_llm/generator_helpers.rb

Overview

Shared helpers for RubyLLM generators

Constant Summary collapse

APPLICATION_MODEL_TYPES =
%w[chat message].freeze
DEFAULT_MODEL_NAMES =
{
  chat: 'Chat',
  message: 'Message'
}.freeze

Instance Method Summary collapse

Instance Method Details

#acts_as_chat_declarationObject



86
87
88
89
90
91
92
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 86

def acts_as_chat_declaration
  params = []

  add_association_params(params, :messages, message_table_name, message_model_name,
                         owner_table: chat_table_name, owner_model_name: chat_model_name, plural: true)
  "acts_as_chat#{" #{params.join(', ')}" if params.any?}"
end

#acts_as_message_declarationObject



94
95
96
97
98
99
100
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 94

def acts_as_message_declaration
  params = []

  add_association_params(params, :chat, chat_table_name, chat_model_name,
                         owner_table: message_table_name, owner_model_name: message_model_name)
  "acts_as_message#{" #{params.join(', ')}" if params.any?}"
end

#chat_controller_class_nameObject



70
71
72
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 70

def chat_controller_class_name
  controller_class_name_for(chat_model_name)
end

#chat_job_class_nameObject



82
83
84
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 82

def chat_job_class_name
  "#{chat_variable_name.camelize}ResponseJob"
end

#create_migration_class_name(table_name) ⇒ Object



147
148
149
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 147

def create_migration_class_name(table_name)
  "create_#{table_name}".camelize
end

#create_namespace_modulesObject



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 102

def create_namespace_modules
  namespaces = []

  [chat_model_name, message_model_name].each do |model_name|
    if model_name.include?('::')
      namespace = model_name.split('::').first
      namespaces << namespace unless namespaces.include?(namespace)
    end
  end

  namespaces.each do |namespace|
    module_path = "app/models/#{namespace.underscore}.rb"
    next if File.exist?(Rails.root.join(module_path))

    create_file module_path do
      <<~RUBY
        module #{namespace}
          def self.table_name_prefix
            "#{namespace.underscore}_"
          end
        end
      RUBY
    end
  end
end

#gem_in_bundle?(gem_name) ⇒ Boolean

Returns:

  • (Boolean)


196
197
198
199
200
201
202
203
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 196

def gem_in_bundle?(gem_name)
  gemfile_path = Rails.root.join('Gemfile')
  lockfile_path = Rails.root.join('Gemfile.lock')

  [gemfile_path, lockfile_path].any? do |path|
    path.exist? && path.read.include?(gem_name)
  end
end

#message_controller_class_nameObject



74
75
76
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 74

def message_controller_class_name
  controller_class_name_for(message_model_name)
end

#migration_versionObject



128
129
130
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 128

def migration_version
  "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end

#model_controller_class_nameObject



78
79
80
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 78

def model_controller_class_name
  controller_class_name_for(model_model_name)
end

#model_model_nameObject

Models remain a UI resource even though RubyLLM owns their persistence. Keep that resource beside a namespaced chat without implying that the application has a corresponding ActiveRecord model.



54
55
56
57
58
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 54

def model_model_name
  return 'Model' unless chat_model_name.include?('::')

  "#{chat_model_name.deconstantize}::Model"
end

#model_resource_nameObject

Routes, view paths and DOM ids for the models UI. Deliberately not a table name: models live in ruby_llm_models whatever the app calls this resource, and they are reached through the :model association.



63
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 63

def model_resource_name = table_name_for(model_model_name)

#model_variable_nameObject



64
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 64

def model_variable_name = variable_name_for(model_model_name)

#mysql?Boolean

Returns:

  • (Boolean)


157
158
159
160
161
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 157

def mysql?
  ::ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
rescue StandardError
  false
end

#parse_model_mappings(allowed_types: APPLICATION_MODEL_TYPES, defaults: DEFAULT_MODEL_NAMES) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 13

def parse_model_mappings(allowed_types: APPLICATION_MODEL_TYPES, defaults: DEFAULT_MODEL_NAMES)
  @model_names = defaults.dup
  @explicit_model_mappings = []

  model_mappings.each do |mapping|
    key, value = mapping.split(':', 2)
    validate_model_mapping!(mapping, key, value, allowed_types)
    raise Thor::Error, "Duplicate model mapping: #{key}" if @explicit_model_mappings.include?(key.to_sym)

    @explicit_model_mappings << key.to_sym
    @model_names[key.to_sym] = value.classify
  end

  @model_names
end

#postgresql?Boolean

Returns:

  • (Boolean)


151
152
153
154
155
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 151

def postgresql?
  ::ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
rescue StandardError
  false
end

#reference_typeObject



140
141
142
143
144
145
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 140

def reference_type
  application = Rails.application if Rails.respond_to?(:application)
  return :bigint unless application

  application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint
end

#table_exists?(table_name) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
166
167
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 163

def table_exists?(table_name)
  ::ActiveRecord::Base.connection.table_exists?(table_name)
rescue StandardError
  false
end

#tailwind_available?Boolean

Returns:

  • (Boolean)


189
190
191
192
193
194
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 189

def tailwind_available?
  Rails.root.join('app/assets/tailwind/application.css').exist? ||
    Rails.root.join('config/tailwind.config.js').exist? ||
    gem_in_bundle?('tailwindcss-rails') ||
    gem_in_bundle?('cssbundling-rails')
end

#tool_call_variable_nameObject



68
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 68

def tool_call_variable_name = 'tool_call'

#ui_template(template_path) ⇒ Object



180
181
182
183
184
185
186
187
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 180

def ui_template(template_path)
  return template_path unless ui_variant == :tailwind

  # Keep Tailwind templates as a separate set so we can mirror Rails/Tailwind
  # scaffold conventions without complicating scaffold templates.
  tailwind_template = "tailwind/#{template_path}"
  File.exist?(File.join(self.class.source_root, "#{tailwind_template}.tt")) ? tailwind_template : template_path
end

#ui_variantObject



169
170
171
172
173
174
175
176
177
178
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 169

def ui_variant
  @ui_variant ||= case options[:ui]
                  when 'tailwind'
                    :tailwind
                  when 'auto'
                    tailwind_available? ? :tailwind : :scaffold
                  else
                    :scaffold
                  end
end

#unprocessable_content_statusObject

Rack 3.1 renamed the 422 symbol and Rails mirrors the rename from 7.2 on.



133
134
135
136
137
138
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 133

def unprocessable_content_status
  constants = ::ActionDispatch::Constants
  return :unprocessable_entity unless constants.const_defined?(:UNPROCESSABLE_CONTENT)

  constants::UNPROCESSABLE_CONTENT
end

#usage_operations_sqlObject



66
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 66

def usage_operations_sql = sql_string_list(::RubyLLM::Accounting::Usage::Entry::OPERATIONS)

#usage_statuses_sqlObject



67
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 67

def usage_statuses_sql = sql_string_list(::RubyLLM::Accounting::Usage::Entry::STATUSES)

#validate_model_mapping!(mapping, key, value, allowed_types) ⇒ Object

Raises:

  • (Thor::Error)


29
30
31
32
33
34
# File 'lib/generators/ruby_llm/generator_helpers.rb', line 29

def validate_model_mapping!(mapping, key, value, allowed_types)
  return if allowed_types.include?(key) && value.present?

  expected = allowed_types.map { |type| "#{type}:ModelName" }.join(', ')
  raise Thor::Error, "Invalid model mapping #{mapping.inspect}. Expected one of: #{expected}"
end