Skip to content

Commit

Permalink
Channel generator (#95)
Browse files Browse the repository at this point in the history
* Generate channel and prepare JS file

* Inject stream identifier

* Generate stimulus controller from template

* Only generate stimulus controller if actually using stimulus

* Prepare a test helper

* Clarify usage

* Standardize

* Correct stream_for 🤦

* update cable_ready channel generator

The most important changes:

add class options

add tests

add default values for prompts

change generator name from `cable_ready_channel` to `cable_ready:channel`

* remove byebug require

* change channel generator to make more use of --stream-for and --stream-from

* add GitHub action to actually run the tests

Co-authored-by: Marco Roth <[email protected]>
  • Loading branch information
julianrubisch and marcoroth authored Feb 3, 2021
1 parent cbf3682 commit b2c7f63
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 3 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Tests

on:
pull_request:
branches:
- '*'
push:
branches:
- master

jobs:
ruby_test:
name: Ruby Test Action
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: [2.6.6, 2.7.2, '3.0']
steps:
- uses: actions/checkout@master
- name: Set up Ruby ${{ matrix.ruby-version }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
- uses: actions/cache@v1
with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Bundle install
run: |
gem install bundler
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Run ruby tests
run: bundle exec rake test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
/spec/reports/
/tmp/
node_modules/
*~
.byebug_history
6 changes: 5 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,14 @@ GEM
method_source (0.9.2)
mimemagic (0.3.5)
mini_mime (1.0.2)
mini_portile2 (2.5.0)
minitest (5.14.3)
mocha (1.12.0)
multi_json (1.15.0)
multipart-post (2.1.1)
nio4r (2.5.4)
nokogiri (1.11.1-x86_64-darwin)
nokogiri (1.11.1)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
octokit (4.20.0)
faraday (>= 0.9)
Expand Down Expand Up @@ -206,6 +209,7 @@ DEPENDENCIES
cable_ready!
github_changelog_generator
magic_frozen_string_literal
mocha
pry
pry-nav
rake
Expand Down
10 changes: 8 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

require "bundler/gem_tasks"
require "github_changelog_generator/task"
require "rake/testtask"
require "pry"

task default: [:test]

task :test do
puts "Please write some tests..."
Rake::TestTask.new(:test) do |t|
t.libs << "lib"
t.libs << "test"
t.pattern = "test/**/*_test.rb"
t.verbose = true
t.warning = false
end

GitHubChangelogGenerator::RakeTask.new :changelog do |config|
Expand Down
1 change: 1 addition & 0 deletions cable_ready.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "pry-nav"
gem.add_development_dependency "rake"
gem.add_development_dependency "standardrb"
gem.add_development_dependency "mocha"
end
10 changes: 10 additions & 0 deletions lib/generators/cable_ready/USAGE
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Description:
Create an ActionCable channel ready for usage with CableReady

Example:
bin/rails generate cable_ready:channel Thing

This will create:
app/channels/thing_channel.rb
app/javascript/channels/thing_channel.js
app/javascript/controllers/thing_controller.js (Optional)
71 changes: 71 additions & 0 deletions lib/generators/cable_ready/channel_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

class CableReady::ChannelGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)

class_option :stream_from, type: :string
class_option :stream_for, type: :string
class_option :stimulus, type: :boolean

def check_options
raise "Can't specify --stream-from and --stream-for at the same time" if options.key?(:stream_from) && options.key?(:stream_for)
end

def create_channel
generate "channel", file_name
end

def enhance_channels
if using_broadcast_to?
gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_for #{resource}.find(params[:id])\n"
template "app/javascript/controllers/%file_name%_controller.js" if using_stimulus?
else
prepend_to_file "app/javascript/channels/#{file_name}_channel.js", "import CableReady from 'cable_ready'\n"
inject_into_file "app/javascript/channels/#{file_name}_channel.js", after: "// Called when there's incoming data on the websocket for this channel\n" do
<<-JS
if (data.cableReady) CableReady.perform(data.operations)
JS
end

gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_from \"#{identifier}\"\n"
end
end

private

def option_given?
options.key?(:stream_from) || options.key?(:stream_for)
end

def using_broadcast_to?
@using_broadcast_to ||= option_given? ? options.key?(:stream_for) : yes?("Are you streaming to a resource using broadcast_to? (y/N)")
end

def using_stimulus?
@using_stimulus ||= options.fetch(:stimulus) {
yes?("Are you going to use a Stimulus controller to subscribe to this channel? (y/N)")
}
end

def resource
return @resource if @resource

stream_for = options.fetch(:stream_for) {
ask("Which resource are you streaming for?", default: class_name)
}

stream_for = file_name if stream_for == "stream_for"
@resource = stream_for.camelize
end

def identifier
return @identifier if @identifier

stream_from = options.fetch(:stream_from) {
ask("What is the stream identifier that goes into stream_from?", default: file_name)
}

stream_from = file_name if stream_from == "stream_from"
@identifier = stream_from.underscore
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Controller } from 'stimulus'
import CableReady from 'cable_ready'

export default class extends Controller {
static values = { id: Number }

connect() {
this.channel = this.application.consumer.subscriptions.create({
channel: '<%= class_name %>Channel',
id: this.idValue
}, {
received (data) {
if (data.cableReady) CableReady.perform(data.operations)
}
})
}

disconnect() {
this.channel.unsubscribe()
}
}
157 changes: 157 additions & 0 deletions test/lib/generators/cable_ready/channel_generator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# frozen_string_literal: true

require "test_helper"
require "generators/cable_ready/channel_generator"

class CableReady::ChannelGeneratorTest < Rails::Generators::TestCase
include ::GeneratorTestHelpers

tests CableReady::ChannelGenerator
destination File.expand_path("../../../../tmp/dummy", __dir__)

prepare_destination
create_sample_app

MiniTest.after_run do
remove_sample_app
end

test "should generate channel with the same resource name and stimulus controller" do
run_generator ["user", "--stream-for=user", "--stimulus"]

assert_file "app/channels/user_channel.rb" do |content|
assert_match(/class\ UserChannel\ </, content)
assert_match(/stream_for\ User\.find\(params\[:id\]\)/, content)
end

assert_file "app/javascript/channels/user_channel.js", /"UserChannel"/
assert_file "app/javascript/controllers/user_controller.js", /'UserChannel'/
end

test "should generate channel with different resource name" do
run_generator ["my_name", "--stream-for=under_scored_resource_name", "--no-stimulus"]

assert_file "app/channels/my_name_channel.rb" do |content|
assert_match(/class\ MyNameChannel\ </, content)
assert_match(/stream_for\ UnderScoredResourceName\.find\(params\[:id\]\)/, content)
end

assert_file "app/javascript/channels/my_name_channel.js", /"MyNameChannel"/
assert_no_file "app/javascript/controllers/my_name_controller.js"
end

test "should not generate stimulus controller if not requested" do
run_generator ["comment", "--stream-for=comment", "--no-stimulus"]

assert_file "app/channels/comment_channel.rb"
assert_file "app/javascript/channels/comment_channel.js"
assert_no_file "app/javascript/controllers/comment_controller.js"
end

test "should run the generator when streaming from identifier" do
run_generator ["page", "--stream-from=page"]

assert_file "app/channels/page_channel.rb" do |content|
assert_match(/PageChannel/, content)
assert_match(/stream_from\ "page"/, content)
end

assert_file "app/javascript/channels/page_channel.js" do |content|
assert_match(/"PageChannel"/, content)
assert_match(/import\ CableReady/, content)
assert_match(/if\ \(data\.cableReady\)\ CableReady\.perform\(data\.operations\)/, content)
end
end

test "should run the generator when streaming without resource and different identifier" do
run_generator ["my_page", "--stream-from=ThisIsMyPage"]

assert_file "app/channels/my_page_channel.rb" do |content|
assert_match(/MyPageChannel/, content)
assert_match(/stream_from\ "this_is_my_page"/, content)
end

assert_file "app/javascript/channels/my_page_channel.js" do |content|
assert_match(/"MyPageChannel"/, content)
assert_match(/import\ CableReady/, content)
assert_match(/if\ \(data\.cableReady\)\ CableReady\.perform\(data\.operations\)/, content)
end
end

test "should run the generator and use the NAME for --stream-from if nothing passed" do
run_generator ["house", "--stream-from"]

assert_file "app/channels/house_channel.rb" do |content|
assert_match(/HouseChannel/, content)
assert_match(/stream_from\ "house"/, content)
end

assert_file "app/javascript/channels/house_channel.js" do |content|
assert_match(/"HouseChannel"/, content)
assert_match(/import\ CableReady/, content)
assert_match(/if\ \(data\.cableReady\)\ CableReady\.perform\(data\.operations\)/, content)
end
end

test "should run the generator and use the NAME for --stream-for if nothing passed" do
run_generator ["option", "--stream-for", "--stimulus"]

assert_file "app/channels/option_channel.rb" do |content|
assert_match(/class\ OptionChannel\ </, content)
assert_match(/stream_for\ Option\.find\(params\[:id\]\)/, content)
end

assert_file "app/javascript/channels/option_channel.js", /"OptionChannel"/
assert_file "app/javascript/controllers/option_controller.js", /'OptionChannel'/
end

test "should run not generate anything if passed stream_from and stream_for" do
assert_raises "Can't specify --stream-from and --stream-for at the same time" do
run_generator ["error", "--stream-from=1", "--stream-for=2"]
end
end

# some tests without generator options to simulate the inputs passed via cli

test "should generate channel with the same resource name and stimulus controller (without options)" do
CableReady::ChannelGenerator.any_instance.stubs(:using_broadcast_to?).returns(true)
CableReady::ChannelGenerator.any_instance.stubs(:resource).returns("Post")
CableReady::ChannelGenerator.any_instance.stubs(:using_stimulus?).returns(true)

run_generator ["post"]

assert_file "app/channels/post_channel.rb" do |content|
assert_match(/class\ PostChannel\ </, content)
assert_match(/stream_for\ Post\.find\(params\[:id\]\)/, content)
end

assert_file "app/javascript/channels/post_channel.js", /PostChannel/
assert_file "app/javascript/controllers/post_controller.js", /'PostChannel'/
end

test "should not generate stimulus controller if not requested (without options)" do
CableReady::ChannelGenerator.any_instance.stubs(:using_broadcast_to?).returns(true)
CableReady::ChannelGenerator.any_instance.stubs(:resource).returns("Admin")
CableReady::ChannelGenerator.any_instance.stubs(:using_stimulus?).returns(false)

run_generator ["admin"]

assert_file "app/channels/admin_channel.rb"
assert_file "app/javascript/channels/admin_channel.js"
assert_no_file "app/javascript/controllers/admin_controller.js"
end

test "should run the generator when streaming from identifier (without options)" do
CableReady::ChannelGenerator.any_instance.stubs(:using_broadcast_to?).returns(false)
CableReady::ChannelGenerator.any_instance.stubs(:identifier).returns("index_identifier")

run_generator ["index"]

assert_file "app/channels/index_channel.rb" do |content|
assert_match(/IndexChannel/, content)
assert_match(/stream_from\ "index_identifier"/, content)
end

assert_file "app/javascript/channels/index_channel.js", /"IndexChannel"/
end
end
28 changes: 28 additions & 0 deletions test/support/generator_test_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module GeneratorTestHelpers
def self.included(base)
base.extend ClassMethods
end

module ClassMethods
def sample_app_path
File.expand_path("../../tmp/dummy", __dir__)
end

def prepare_destination
FileUtils.rm_rf(sample_app_path) if Dir.exist?(sample_app_path)
FileUtils.mkdir_p(sample_app_path)
end

def create_sample_app
FileUtils.cd(sample_app_path) do
system "rails new . --minimal --skip-active-record --skip-test-unit --skip-spring --skip-bundle --quiet --force"
end
end

def remove_sample_app
FileUtils.rm_rf(destination_root)
end
end
end
Loading

0 comments on commit b2c7f63

Please sign in to comment.