Class: AnyCableRailsGenerators::SetupGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Defined in:
lib/generators/anycable/setup/setup_generator.rb

Overview

Entry point for interactive installation

Constant Summary collapse

DOCS_ROOT =
"https://docs.anycable.io"
DEVELOPMENT_METHODS =
%w[skip local docker].freeze
DEPLOYMENT_METHODS =
%w[skip thruster fly heroku anycable_plus].freeze
RPC_IMPL =
%w[none grpc http].freeze

Instance Method Summary collapse

Instance Method Details

#action_cable_engineObject



180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/generators/anycable/setup/setup_generator.rb', line 180

def action_cable_engine
  return unless application_rb
  return if application_rb.match?(/^require\s+['"](action_cable\/engine|rails\/all)['"]/)

  found = false
  gsub_file "config/application.rb", %r{^require ['"]rails['"].*$} do |match|
    found = true
    match << %(\nrequire "action_cable/engine")
  end

  return if found

  @todos << "⚠️  Ensure Action Cable is loaded. Add `require \"action_cable/engine\"` to your `config/application.rb` file"
end

#anycable_clientObject



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/generators/anycable/setup/setup_generator.rb', line 195

def anycable_client
  if hotwire? && install_js_packages
    gsub_file "app/javascript/application.js", /^import "@hotwired\/turbo-rails".*$/, "      import \"@hotwired/turbo\"\n      import { createCable } from \"@anycable/web\"\n      import { start } from \"@anycable/turbo-stream\"\n\n      // Use extended Action Cable protocol to support reliable streams and presence\n      // See https://github.com/anycable/anycable-client\n      const cable = createCable({ protocol: 'actioncable-v1-ext-json' })\n      // Prevent frequent resubscriptions during morphing or navigation\n      start(cable, { delayedUnsubscribe: true })\n    JS\n    return\n  end\n\n  @todos << \"\u26A0\uFE0F  Install AnyCable JS client to use advanced features (presence, reliable streams): \u{1F449} https://github.com/anycable/anycable-client\\n\"\nend\n"

#cable_url_infoObject



144
145
146
147
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
173
174
175
176
177
178
# File 'lib/generators/anycable/setup/setup_generator.rb', line 144

def cable_url_info
  meta_tag = norpc? ? "action_cable_with_jwt_meta_tag" : "action_cable_meta_tag"

  begin
    app_layout = nil
    inside("app/views/layouts") do
      next unless File.file?("application.html.erb")
      app_layout = File.read("application.html.erb")
    end
    return if app_layout&.include?(meta_tag)

    if norpc? && app_layout&.include?("action_cable_meta_tag")
      gsub_file "app/views/layouts/application.html.erb", %r{^\s+<%= action_cable_meta_tag %>.*$} do |match|
        match.sub("action_cable_meta_tag", "action_cable_with_jwt_meta_tag")
      end
      inform_jwt_identifiers("app/views/layouts/application.html.erb")
      return
    end

    found = false
    gsub_file "app/views/layouts/application.html.erb", %r{^\s+<%= csp_meta_tag %>.*$} do |match|
      found = true
      match << "\n    <%= #{meta_tag} %>"
    end
    if found
      inform_jwt_identifiers("app/views/layouts/application.html.erb") if norpc?
      return
    end
  rescue Errno::ENOENT
  end

  @todos << "⚠️  Ensure you have `action_cable_meta_tag`\n" \
    "      or `action_cable_with_jwt_meta_tag` included in your HTML layout:\n" \
    "      👉 https://docs.anycable.io/rails/getting_started"
end

#configsObject



121
122
123
124
125
126
127
128
129
# File 'lib/generators/anycable/setup/setup_generator.rb', line 121

def configs
  inside("config") do
    template "anycable.yml"
  end

  template "anycable.toml"

  update_cable_yml
end

#deployment_methodObject



226
227
228
229
230
# File 'lib/generators/anycable/setup/setup_generator.rb', line 226

def deployment_method
  @todos << "🚢 Learn how to run AnyCable in production: 👉 #{DOCS_ROOT}/deployment\n" \
    "      For the quick start, consider using AnyCable+ (https://plus.anycable.io)\n" \
    "      or AnyCable Thruster (https://github.com/anycable/thruster)"
end

#development_methodObject



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/generators/anycable/setup/setup_generator.rb', line 83

def development_method
  if DEVELOPMENT_METHODS.include?(options[:development])
    @development = options[:development]
  end

  # Fast-track for local development
  if file_exists?("bin/dev") && file_exists?("Procfile.dev")
    @development = "local"
  end

  unless @development
    say "      You can run AnyCable server locally (recommended for most cases) or as a Docker container (in case you develop in a containerized environment).\n\n      For a local installation, we provide a convenient binstub (`bin/anycable-go`) which automatically\n      installs AnyCable server for the current platform.\n    MSG\n    say \"\"\n\n    answer = DEVELOPMENT_METHODS.index(options[:development]) || 99\n\n    until DEVELOPMENT_METHODS[answer.to_i]\n      answer = ask <<~MSG\n        Which way to run AnyCable server locally would you prefer? (1) Binstub, (2) Docker, (0) Skip\n      MSG\n    end\n\n    @development = DEVELOPMENT_METHODS[answer.to_i]\n  end\n\n  case @development\n  when \"skip\"\n    @todos << \"Install AnyCable server for local development: \#{DOCS_ROOT}/anycable-go/getting_started\"\n  else\n    send \"install_for_\#{@development}\"\n  end\nend\n"

#finishObject



232
233
234
235
236
237
238
239
240
241
242
# File 'lib/generators/anycable/setup/setup_generator.rb', line 232

def finish
  say_status :info, "✅ AnyCable has been configured"

  if @todos.any?
    say ""
    say "📋 Please, check the following actions required to complete the setup:\n"
    @todos.each do |todo|
      say "- [ ] #{todo}"
    end
  end
end

#rpc_implementationObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/generators/anycable/setup/setup_generator.rb', line 37

def rpc_implementation
  if RPC_IMPL.include?(options[:rpc])
    @rpc_impl = options[:rpc]
    return
  end

  if hotwire? && !custom_channels?
    say "      \u26A1\uFE0F Hotwire application has been detected, installing AnyCable in a standalone mode.\n    MSG\n    @rpc_impl = \"none\"\n    return\n  end\n\n  if custom_channels?\n    answer = RPC_IMPL.index(options[:rpc]) || 99\n\n    unless RPC_IMPL[answer.to_i]\n      say <<~MSG\n        AnyCable connects to your Rails server to communicate with Action Cable channels either via HTTP or gRPC.\n\n        gRPC provides better performance and scalability but requires running\n        a separate component (a gRPC server).\n\n        HTTP is a good option for a quick start or in case your deployment platform doesn't\n        support running multiple web services (e.g., Heroku).\n\n        If you only use Action Cable for Turbo Streams, you don't need RPC at all.\n\n        Learn more from the docs \u{1F449} \#{DOCS_ROOT}/anycable-go/rpc\n      MSG\n      say \"\"\n    end\n\n    until RPC_IMPL[answer.to_i]\n      answer = ask \"Which RPC implementation would you like to use? (1) gRPC, (2) HTTP, (0) None\"\n    end\n\n    @rpc_impl = RPC_IMPL[answer.to_i]\n  end\n\n  # no Hotwire, no custom channels\n  say \"Looks like you don't have any real-time functionality yet. Let's start with a miminal AnyCable setup!\"\n  @rpc_impl = \"none\"\nend\n"

#rubocop_compatibilityObject



131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/generators/anycable/setup/setup_generator.rb', line 131

def rubocop_compatibility
  return unless rubocop?

  say_status :info, "🤖 Running static compatibility checks with RuboCop"
  res = run "bundle exec rubocop -r 'anycable/rails/compatibility/rubocop' --only AnyCable/InstanceVars,AnyCable/PeriodicalTimers,AnyCable/InstanceVars"

  unless res
    say_status :help, "⚠️  Please, take a look at the icompatibilities above and fix them"

    @todos << "Fix Action Cable compatibility issues (listed above): #{DOCS_ROOT}/rails/compatibility"
  end
end

#turbo_verifier_keyObject



214
215
216
217
218
219
220
221
222
223
224
# File 'lib/generators/anycable/setup/setup_generator.rb', line 214

def turbo_verifier_key
  return unless hotwire?
  return if application_rb.include?("config.turbo.signed_stream_verifier_key = AnyCable.config.secret")

  gsub_file "config/application.rb", %r{\s+end\nend} do |match|
    "\n\n" \
    "    # Use AnyCable secret to sign Turbo Streams\n" \
    "    # #{DOCS_ROOT}/guides/hotwire?id=rails-applications\n" \
    "    config.turbo.signed_stream_verifier_key = AnyCable.config.secret#{match}"
  end
end

#welcomeObject



30
31
32
33
34
35
# File 'lib/generators/anycable/setup/setup_generator.rb', line 30

def welcome
  say ""
  say "👋 Welcome to AnyCable interactive installer. We'll guide you through the process of installing AnyCable for your Rails application. Buckle up!"
  say ""
  @todos = []
end