declare_schema 0.1.1 → 0.3.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +37 -0
- data/CHANGELOG.md +36 -4
- data/Gemfile +0 -2
- data/Gemfile.lock +1 -4
- data/Rakefile +13 -20
- data/gemfiles/rails_4.gemfile +4 -7
- data/gemfiles/rails_5.gemfile +4 -7
- data/gemfiles/rails_6.gemfile +4 -7
- data/lib/declare_schema/model.rb +21 -23
- data/lib/declare_schema/model/field_spec.rb +1 -12
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migration_generator.rb +20 -13
- data/lib/generators/declare_schema/migration/migrator.rb +57 -33
- data/lib/generators/declare_schema/migration/templates/migration.rb.erb +1 -1
- data/lib/generators/declare_schema/support/eval_template.rb +12 -3
- data/lib/generators/declare_schema/support/model.rb +77 -2
- data/spec/lib/declare_schema/api_spec.rb +125 -0
- data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +8 -4
- data/spec/lib/declare_schema/generator_spec.rb +57 -0
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +51 -0
- data/spec/lib/declare_schema/migration_generator_spec.rb +735 -0
- data/spec/lib/declare_schema/prepare_testapp.rb +31 -0
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +42 -0
- data/spec/spec_helper.rb +26 -0
- metadata +9 -11
- data/.jenkins/Jenkinsfile +0 -72
- data/.jenkins/ruby_build_pod.yml +0 -19
- data/lib/generators/declare_schema/model/templates/model_injection.rb.erb +0 -25
- data/test/api.rdoctest +0 -136
- data/test/generators.rdoctest +0 -60
- data/test/interactive_primary_key.rdoctest +0 -56
- data/test/migration_generator.rdoctest +0 -846
- data/test/migration_generator_comments.rdoctestDISABLED +0 -74
- data/test/prepare_testapp.rb +0 -15
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
TESTAPP_PATH = ENV['TESTAPP_PATH'] || File.join(Dir.tmpdir, 'declare_schema_testapp') unless defined?(TESTAPP_PATH)
|
7
|
+
FileUtils.chdir(TESTAPP_PATH)
|
8
|
+
|
9
|
+
system "rm -rf app/models/ad* app/models/alpha*"
|
10
|
+
system "rm -rf test/models/ad* test/models/alpha*"
|
11
|
+
system "rm -rf test/fixtures/ad* test/fixtures/alpha*"
|
12
|
+
system "rm -rf db/migrate/*"
|
13
|
+
system "mkdir -p #{TESTAPP_PATH}/app/assets/config"
|
14
|
+
system "echo '' >> #{TESTAPP_PATH}/app/assets/config/manifest.js"
|
15
|
+
|
16
|
+
require "#{TESTAPP_PATH}/config/environment"
|
17
|
+
|
18
|
+
require 'rails/generators'
|
19
|
+
Rails::Generators.configure!(Rails.application.config.generators)
|
20
|
+
|
21
|
+
ActiveRecord::Base.connection.schema_cache.clear!
|
22
|
+
|
23
|
+
(ActiveRecord::Base.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).each do |table|
|
24
|
+
ActiveRecord::Base.connection.execute("DROP TABLE #{ActiveRecord::Base.connection.quote_table_name(table)}")
|
25
|
+
end
|
26
|
+
|
27
|
+
ActiveRecord::Base.send(:descendants).each do |model|
|
28
|
+
unless model.name['Active'] || model.name['Application']
|
29
|
+
nuke_model_class(model)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails'
|
4
|
+
require 'rails/generators'
|
5
|
+
|
6
|
+
module Generators
|
7
|
+
module DeclareSchema
|
8
|
+
module Migration
|
9
|
+
RSpec.describe Migrator do
|
10
|
+
before do
|
11
|
+
ActiveRecord::Base.connection.tables
|
12
|
+
end
|
13
|
+
|
14
|
+
subject { described_class.new }
|
15
|
+
|
16
|
+
describe 'format_options' do
|
17
|
+
let(:mysql_longtext_limit) { 0xffff_ffff }
|
18
|
+
|
19
|
+
context 'MySQL' do
|
20
|
+
before do
|
21
|
+
expect(::DeclareSchema::Model::FieldSpec).to receive(:mysql_text_limits?).and_return(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns text limits' do
|
25
|
+
expect(subject.format_options({ limit: mysql_longtext_limit }, :text)).to eq(["limit: #{mysql_longtext_limit}"])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'non-MySQL' do
|
30
|
+
before do
|
31
|
+
expect(::DeclareSchema::Model::FieldSpec).to receive(:mysql_text_limits?).and_return(false)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns text limits' do
|
35
|
+
expect(subject.format_options({ limit: mysql_longtext_limit }, :text)).to eq([])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -22,6 +22,32 @@ RSpec.configure do |config|
|
|
22
22
|
|
23
23
|
RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = 2_000
|
24
24
|
|
25
|
+
def active_record_base_class
|
26
|
+
if Rails::VERSION::MAJOR == 4
|
27
|
+
'ActiveRecord::Base'
|
28
|
+
else
|
29
|
+
'ApplicationRecord'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def migrate(renames = {})
|
34
|
+
up, down = Generators::DeclareSchema::Migration::Migrator.run(renames)
|
35
|
+
ActiveRecord::Migration.class_eval(up)
|
36
|
+
ActiveRecord::Base.send(:descendants).each { |model| model.reset_column_information }
|
37
|
+
[up, down]
|
38
|
+
end
|
39
|
+
|
40
|
+
def nuke_model_class(klass)
|
41
|
+
ActiveSupport::DescendantsTracker.instance_eval do
|
42
|
+
direct_descendants = class_variable_get('@@direct_descendants')
|
43
|
+
direct_descendants[ActiveRecord::Base] = direct_descendants[ActiveRecord::Base].to_a.reject { |descendant| descendant == klass }
|
44
|
+
if defined?(ApplicationRecord)
|
45
|
+
direct_descendants[ApplicationRecord] = direct_descendants[ApplicationRecord].to_a.reject { |descendant| descendant == klass }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
Object.instance_eval { remove_const(klass.name.to_sym) rescue nil }
|
49
|
+
end
|
50
|
+
|
25
51
|
def with_modified_env(options, &block)
|
26
52
|
ClimateControl.modify(options, &block)
|
27
53
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: declare_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Invoca Development adapted from hobo_fields by Tom Locke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -35,11 +35,10 @@ files:
|
|
35
35
|
- ".dependabot/config.yml"
|
36
36
|
- ".github/workflows/gem_release.yml"
|
37
37
|
- ".gitignore"
|
38
|
-
- ".jenkins/Jenkinsfile"
|
39
|
-
- ".jenkins/ruby_build_pod.yml"
|
40
38
|
- ".rspec"
|
41
39
|
- ".rubocop.yml"
|
42
40
|
- ".ruby-version"
|
41
|
+
- ".travis.yml"
|
43
42
|
- Appraisals
|
44
43
|
- CHANGELOG.md
|
45
44
|
- Gemfile
|
@@ -69,18 +68,17 @@ files:
|
|
69
68
|
- lib/generators/declare_schema/migration/templates/migration.rb.erb
|
70
69
|
- lib/generators/declare_schema/model/USAGE
|
71
70
|
- lib/generators/declare_schema/model/model_generator.rb
|
72
|
-
- lib/generators/declare_schema/model/templates/model_injection.rb.erb
|
73
71
|
- lib/generators/declare_schema/support/eval_template.rb
|
74
72
|
- lib/generators/declare_schema/support/model.rb
|
75
73
|
- lib/generators/declare_schema/support/thor_shell.rb
|
74
|
+
- spec/lib/declare_schema/api_spec.rb
|
76
75
|
- spec/lib/declare_schema/field_declaration_dsl_spec.rb
|
76
|
+
- spec/lib/declare_schema/generator_spec.rb
|
77
|
+
- spec/lib/declare_schema/interactive_primary_key_spec.rb
|
78
|
+
- spec/lib/declare_schema/migration_generator_spec.rb
|
79
|
+
- spec/lib/declare_schema/prepare_testapp.rb
|
80
|
+
- spec/lib/generators/declare_schema/migration/migrator_spec.rb
|
77
81
|
- spec/spec_helper.rb
|
78
|
-
- test/api.rdoctest
|
79
|
-
- test/generators.rdoctest
|
80
|
-
- test/interactive_primary_key.rdoctest
|
81
|
-
- test/migration_generator.rdoctest
|
82
|
-
- test/migration_generator_comments.rdoctestDISABLED
|
83
|
-
- test/prepare_testapp.rb
|
84
82
|
- test_responses.txt
|
85
83
|
homepage: https://github.com/Invoca/declare_schema
|
86
84
|
licenses: []
|
data/.jenkins/Jenkinsfile
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
#!/usr/bin/groovy
|
2
|
-
@Library('jenkins-pipeline@v0.5.1')
|
3
|
-
import com.invoca.ci.*;
|
4
|
-
|
5
|
-
pipeline {
|
6
|
-
agent {
|
7
|
-
kubernetes {
|
8
|
-
defaultContainer 'ruby'
|
9
|
-
yamlFile '.jenkins/ruby_build_pod.yml'
|
10
|
-
}
|
11
|
-
}
|
12
|
-
|
13
|
-
environment {
|
14
|
-
GITHUB_TOKEN = credentials('github_token')
|
15
|
-
BUNDLE_GEM__FURY__IO = credentials('gemfury_deploy_token')
|
16
|
-
}
|
17
|
-
|
18
|
-
stages {
|
19
|
-
stage('Setup') {
|
20
|
-
steps {
|
21
|
-
updateGitHubStatus('clean-build', 'pending', "Running unit tests")
|
22
|
-
sh 'bundle install'
|
23
|
-
sh 'bundle exec appraisal install'
|
24
|
-
}
|
25
|
-
}
|
26
|
-
|
27
|
-
stage("Current Unit Tests") {
|
28
|
-
steps {
|
29
|
-
sh 'bundle exec rake test:prepare_testapp[force]'
|
30
|
-
sh 'bundle exec rake test:all < test_responses.txt'
|
31
|
-
}
|
32
|
-
}
|
33
|
-
|
34
|
-
stage("Rails 4 Appraisal") {
|
35
|
-
steps {
|
36
|
-
sh 'bundle exec appraisal rails-4 rake test:prepare_testapp[force]'
|
37
|
-
sh 'bundle exec appraisal rails-4 rake test:all < test_responses.txt'
|
38
|
-
}
|
39
|
-
}
|
40
|
-
|
41
|
-
stage("Rails 5 Appraisal") {
|
42
|
-
steps {
|
43
|
-
sh 'bundle exec appraisal rails-5 rake test:prepare_testapp[force]'
|
44
|
-
sh 'bundle exec appraisal rails-5 rake test:all < test_responses.txt'
|
45
|
-
}
|
46
|
-
}
|
47
|
-
|
48
|
-
stage("Rails 6 Appraisal") {
|
49
|
-
steps {
|
50
|
-
sh 'bundle exec appraisal rails-6 rake test:prepare_testapp[force]'
|
51
|
-
sh 'bundle exec appraisal rails-6 rake test:all < test_responses.txt'
|
52
|
-
}
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
post {
|
57
|
-
success { updateGitHubStatus('clean-build', 'success', "Unit tests passed") }
|
58
|
-
failure { updateGitHubStatus('clean-build', 'failure', "Unit tests failed") }
|
59
|
-
}
|
60
|
-
}
|
61
|
-
|
62
|
-
void updateGitHubStatus(String context, String status, String description) {
|
63
|
-
gitHubStatus([
|
64
|
-
repoSlug: 'Invoca/declare_schema',
|
65
|
-
sha: env.GIT_COMMIT,
|
66
|
-
description: description,
|
67
|
-
context: context,
|
68
|
-
targetURL: env.RUN_DISPLAY_URL,
|
69
|
-
token: env.GITHUB_TOKEN,
|
70
|
-
status: status
|
71
|
-
])
|
72
|
-
}
|
data/.jenkins/ruby_build_pod.yml
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
|
2
|
-
---
|
3
|
-
apiVersion: v1
|
4
|
-
kind: Pod
|
5
|
-
metadata:
|
6
|
-
labels:
|
7
|
-
jenkins/declare_schema: 'true'
|
8
|
-
namespace: jenkins
|
9
|
-
name: declare_schema
|
10
|
-
spec:
|
11
|
-
containers:
|
12
|
-
- name: ruby
|
13
|
-
image: ruby:2.6.5
|
14
|
-
tty: true
|
15
|
-
resources:
|
16
|
-
requests:
|
17
|
-
memory: "100Mi"
|
18
|
-
command:
|
19
|
-
- cat
|
@@ -1,25 +0,0 @@
|
|
1
|
-
|
2
|
-
fields do
|
3
|
-
<% for attribute in field_attributes -%>
|
4
|
-
<%= "%-#{max_attribute_length}s" % attribute.name %> :<%= attribute.type %><%=
|
5
|
-
case attribute.type.to_s
|
6
|
-
when 'string'
|
7
|
-
', limit: 255'
|
8
|
-
else
|
9
|
-
''
|
10
|
-
end
|
11
|
-
%>
|
12
|
-
<% end -%>
|
13
|
-
<% if options[:timestamps] -%>
|
14
|
-
timestamps
|
15
|
-
<% end -%>
|
16
|
-
end
|
17
|
-
|
18
|
-
<% for bt in bts -%>
|
19
|
-
belongs_to :<%= bt %>
|
20
|
-
<% end -%>
|
21
|
-
<%= "\n" unless bts.empty? -%>
|
22
|
-
<% for hm in hms -%>
|
23
|
-
has_many :<%= hm %>, dependent: :destroy
|
24
|
-
<% end -%>
|
25
|
-
<%= "\n" unless hms.empty? -%>
|
data/test/api.rdoctest
DELETED
@@ -1,136 +0,0 @@
|
|
1
|
-
# DeclareSchema API
|
2
|
-
|
3
|
-
In order for the API examples to run we need to load the rails generators of our testapp:
|
4
|
-
{.hidden}
|
5
|
-
|
6
|
-
doctest: prepare testapp environment
|
7
|
-
doctest_require: 'prepare_testapp'
|
8
|
-
{.hidden}
|
9
|
-
|
10
|
-
## Example Models
|
11
|
-
|
12
|
-
Let's define some example models that we can use to demonstrate the API. With DeclareSchema we can use the 'declare_schema:model' generator like so:
|
13
|
-
|
14
|
-
$ rails generate declare_schema:model advert title:string body:text
|
15
|
-
|
16
|
-
This will generate the test, fixture and a model file like this:
|
17
|
-
|
18
|
-
>> Rails::Generators.invoke 'declare_schema:model', %w(advert title:string body:text)
|
19
|
-
{.hidden}
|
20
|
-
|
21
|
-
class Advert < ActiveRecord::Base
|
22
|
-
fields do
|
23
|
-
title :string
|
24
|
-
body :text, limit: 0xffff, null: true
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
The migration generator uses this information to create a migration. The following creates and runs the migration so we're ready to go.
|
29
|
-
|
30
|
-
$ rails generate declare_schema:migration -n -m
|
31
|
-
|
32
|
-
We're now ready to start demonstrating the API
|
33
|
-
|
34
|
-
>> require_relative "#{Rails.root}/app/models/advert.rb" if Rails::VERSION::MAJOR > 5
|
35
|
-
>> Rails::Generators.invoke 'declare_schema:migration', %w(-n -m)
|
36
|
-
>> Rails::Generators.invoke 'declare_schema:migration', %w(-n -m)
|
37
|
-
{.hidden}
|
38
|
-
|
39
|
-
## The Basics
|
40
|
-
|
41
|
-
The main feature of DeclareSchema, aside from the migration generator, is the ability to declare rich types for your fields. For example, you can declare that a field is an email address, and the field will be automatically validated for correct email address syntax.
|
42
|
-
|
43
|
-
### Field Types
|
44
|
-
|
45
|
-
Field values are returned as the type you specify.
|
46
|
-
|
47
|
-
>> a = Advert.new :body => "This is the body", id: 1, title: "title"
|
48
|
-
>> a.body.class
|
49
|
-
=> String
|
50
|
-
|
51
|
-
This also works after a round-trip to the database
|
52
|
-
|
53
|
-
>> a.save
|
54
|
-
>> b = Advert.find(a.id)
|
55
|
-
>> b.body.class
|
56
|
-
=> String
|
57
|
-
|
58
|
-
## Names vs. Classes
|
59
|
-
|
60
|
-
The full set of available symbolic names is
|
61
|
-
|
62
|
-
* `:integer`
|
63
|
-
* `:float`
|
64
|
-
* `:decimal`
|
65
|
-
* `:string`
|
66
|
-
* `:text`
|
67
|
-
* `:boolean`
|
68
|
-
* `:date`
|
69
|
-
* `:datetime`
|
70
|
-
* `:html`
|
71
|
-
* `:textile`
|
72
|
-
* `:markdown`
|
73
|
-
* `:password`
|
74
|
-
|
75
|
-
You can add your own types too. More on that later.
|
76
|
-
|
77
|
-
|
78
|
-
## Model extensions
|
79
|
-
|
80
|
-
DeclareSchema adds a few features to your models.
|
81
|
-
|
82
|
-
### `Model.attr_type`
|
83
|
-
|
84
|
-
Returns the type (i.e. class) declared for a given field or attribute
|
85
|
-
|
86
|
-
>> Advert.connection.schema_cache.clear!
|
87
|
-
>> Advert.reset_column_information
|
88
|
-
>> Advert.attr_type :title
|
89
|
-
=> String
|
90
|
-
>> Advert.attr_type :body
|
91
|
-
=> String
|
92
|
-
|
93
|
-
## Field validations
|
94
|
-
|
95
|
-
DeclareSchema gives you some shorthands for declaring some common validations right in the field declaration
|
96
|
-
|
97
|
-
### Required fields
|
98
|
-
|
99
|
-
The `:required` argument to a field gives a `validates_presence_of`:
|
100
|
-
|
101
|
-
>>
|
102
|
-
class Advert
|
103
|
-
fields do
|
104
|
-
title :string, :required, limit: 255
|
105
|
-
end
|
106
|
-
end
|
107
|
-
>> a = Advert.new
|
108
|
-
>> a.valid?
|
109
|
-
=> false
|
110
|
-
>> a.errors.full_messages
|
111
|
-
=> ["Title can't be blank"]
|
112
|
-
>> a.id = 2
|
113
|
-
>> a.body = "hello"
|
114
|
-
>> a.title = "Jimbo"
|
115
|
-
>> a.save
|
116
|
-
=> true
|
117
|
-
|
118
|
-
|
119
|
-
### Unique fields
|
120
|
-
|
121
|
-
The `:unique` argument in a field declaration gives `validates_uniqueness_of`:
|
122
|
-
|
123
|
-
>>
|
124
|
-
class Advert
|
125
|
-
fields do
|
126
|
-
title :string, :unique, limit: 255
|
127
|
-
end
|
128
|
-
end
|
129
|
-
>> a = Advert.new :title => "Jimbo", id: 3, body: "hello"
|
130
|
-
>> a.valid?
|
131
|
-
=> false
|
132
|
-
>> a.errors.full_messages
|
133
|
-
=> ["Title has already been taken"]
|
134
|
-
>> a.title = "Sambo"
|
135
|
-
>> a.save
|
136
|
-
=> true
|
data/test/generators.rdoctest
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
doctest: prepare testapp environment
|
2
|
-
doctest_require: 'prepare_testapp'
|
3
|
-
|
4
|
-
doctest: generate declare_schema:model
|
5
|
-
>> Rails::Generators.invoke 'declare_schema:model', %w(alpha/beta one:string two:integer)
|
6
|
-
|
7
|
-
|
8
|
-
doctest: model file exists
|
9
|
-
>> File.exist? 'app/models/alpha/beta.rb'
|
10
|
-
=> true
|
11
|
-
|
12
|
-
doctest: model content matches
|
13
|
-
>> File.read 'app/models/alpha/beta.rb'
|
14
|
-
=> "class Alpha::Beta < #{Rails::VERSION::MAJOR > 4 ? 'ApplicationRecord' : 'ActiveRecord::Base'}\n\n fields do\n one :string, limit: 255\n two :integer\n end\n\nend\n"
|
15
|
-
|
16
|
-
doctest: module file exists
|
17
|
-
>> File.exist? 'app/models/alpha.rb'
|
18
|
-
=> true
|
19
|
-
|
20
|
-
doctest: module content matches
|
21
|
-
>> File.read 'app/models/alpha.rb'
|
22
|
-
=> "module Alpha\n def self.table_name_prefix\n 'alpha_'\n end\nend\n"
|
23
|
-
|
24
|
-
|
25
|
-
doctest: test file exists
|
26
|
-
>> File.exist? 'test/models/alpha/beta_test.rb'
|
27
|
-
=> true
|
28
|
-
|
29
|
-
doctest: test content matches
|
30
|
-
>> File.read 'test/models/alpha/beta_test.rb'
|
31
|
-
=>
|
32
|
-
require 'test_helper'
|
33
|
-
|
34
|
-
class Alpha::BetaTest < ActiveSupport::TestCase
|
35
|
-
# test "the truth" do
|
36
|
-
# assert true
|
37
|
-
# end
|
38
|
-
end
|
39
|
-
|
40
|
-
doctest: fixture file exists
|
41
|
-
>> File.exist? 'test/fixtures/alpha/beta.yml'
|
42
|
-
=> true
|
43
|
-
|
44
|
-
|
45
|
-
doctest: generate declare_schema:migration
|
46
|
-
>> require_relative "#{Rails.root}/app/models/alpha.rb" if Rails::VERSION::MAJOR > 5
|
47
|
-
>> require_relative "#{Rails.root}/app/models/alpha/beta.rb" if Rails::VERSION::MAJOR > 5
|
48
|
-
>> Rails::Generators.invoke 'declare_schema:migration', %w(-n -m)
|
49
|
-
|
50
|
-
doctest: schema.rb file exists
|
51
|
-
>> File.exist? 'db/schema.rb'
|
52
|
-
=> true
|
53
|
-
|
54
|
-
doctest: db file exists
|
55
|
-
>> File.exist? 'db/development.sqlite3'
|
56
|
-
=> true
|
57
|
-
|
58
|
-
doctest: Alpha::Beta class exists
|
59
|
-
>> Alpha::Beta
|
60
|
-
# will error if class doesn't exist
|