katapult 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Guardfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/Rakefile +14 -0
- data/bin/katapult +44 -0
- data/features/binary.feature +48 -0
- data/features/configuration.feature +24 -0
- data/features/katapult.feature +201 -0
- data/features/model.feature +203 -0
- data/features/navigation.feature +80 -0
- data/features/step_definitions/db_steps.rb +8 -0
- data/features/step_definitions/file_steps.rb +14 -0
- data/features/step_definitions/katapult_steps.rb +14 -0
- data/features/step_definitions/rails_steps.rb +44 -0
- data/features/step_definitions/test_steps.rb +7 -0
- data/features/support/env.rb +16 -0
- data/features/wui.feature +319 -0
- data/katapult.gemspec +35 -0
- data/katapult.png +0 -0
- data/lib/generators/katapult/basics/basics_generator.rb +95 -0
- data/lib/generators/katapult/basics/templates/Gemfile +76 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application.css.sass +6 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_all.css.sass +4 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_items.css.sass +11 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_layout.css.sass +26 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_navigation.css.sass +11 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_tools.css.sass +12 -0
- data/lib/generators/katapult/basics/templates/config/database.sample.yml +16 -0
- data/lib/generators/katapult/basics/templates/config/database.yml +13 -0
- data/lib/generators/katapult/basics/templates/config/spring.rb +3 -0
- data/lib/generators/katapult/basics/templates/features/support/env-custom.rb +3 -0
- data/lib/generators/katapult/basics/templates/features/support/paths.rb +47 -0
- data/lib/generators/katapult/cucumber_features/cucumber_features_generator.rb +23 -0
- data/lib/generators/katapult/cucumber_features/templates/feature.feature +59 -0
- data/lib/generators/katapult/haml/haml_generator.rb +90 -0
- data/lib/generators/katapult/haml/templates/_form.html.haml +38 -0
- data/lib/generators/katapult/haml/templates/app/views/layouts/application.html.haml +25 -0
- data/lib/generators/katapult/haml/templates/custom_action.html.haml +5 -0
- data/lib/generators/katapult/haml/templates/edit.html.haml +4 -0
- data/lib/generators/katapult/haml/templates/index.html.haml +29 -0
- data/lib/generators/katapult/haml/templates/new.html.haml +4 -0
- data/lib/generators/katapult/haml/templates/show.html.haml +41 -0
- data/lib/generators/katapult/install/install_generator.rb +14 -0
- data/lib/generators/katapult/install/templates/lib/katapult/application_model.rb +18 -0
- data/lib/generators/katapult/model/model_generator.rb +59 -0
- data/lib/generators/katapult/model/templates/app/models/shared/does_flag.rb +32 -0
- data/lib/generators/katapult/model/templates/model.rb +21 -0
- data/lib/generators/katapult/model_specs/model_specs_generator.rb +51 -0
- data/lib/generators/katapult/model_specs/templates/model_spec.rb +34 -0
- data/lib/generators/katapult/navigation/navigation_generator.rb +25 -0
- data/lib/generators/katapult/navigation/templates/app/models/navigation.rb +12 -0
- data/lib/generators/katapult/transform/transform_generator.rb +47 -0
- data/lib/generators/katapult/w_u_i/templates/_route.rb +13 -0
- data/lib/generators/katapult/w_u_i/templates/controller.rb +106 -0
- data/lib/generators/katapult/w_u_i/w_u_i_generator.rb +57 -0
- data/lib/katapult.rb +5 -0
- data/lib/katapult/action.rb +44 -0
- data/lib/katapult/application_model.rb +45 -0
- data/lib/katapult/attribute.rb +83 -0
- data/lib/katapult/element.rb +72 -0
- data/lib/katapult/generator.rb +28 -0
- data/lib/katapult/model.rb +33 -0
- data/lib/katapult/navigation.rb +22 -0
- data/lib/katapult/parser.rb +39 -0
- data/lib/katapult/util.rb +16 -0
- data/lib/katapult/version.rb +3 -0
- data/lib/katapult/wui.rb +77 -0
- data/script/console +16 -0
- data/spec/action_spec.rb +44 -0
- data/spec/attribute_spec.rb +48 -0
- data/spec/model_spec.rb +18 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/util_spec.rb +23 -0
- data/spec/wui_spec.rb +49 -0
- metadata +253 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe <%= model.name :class %> do
|
4
|
+
|
5
|
+
describe '#to_s' do
|
6
|
+
<%- if model.label_attr -%>
|
7
|
+
it 'returns the #<%= model.label_attr.name %> attribute' do
|
8
|
+
subject.<%= model.label_attr.name %> = <%= model.label_attr.test_value.inspect %>
|
9
|
+
expect(subject.to_s).to eql(<%= model.label_attr.test_value.to_s.inspect %>)
|
10
|
+
end
|
11
|
+
<%- else -%>
|
12
|
+
it 'returns its class name with its id' do
|
13
|
+
subject.id = 17
|
14
|
+
expect(subject.to_s).to eql("<%= model.name %>#17")
|
15
|
+
end
|
16
|
+
<%- end -%>
|
17
|
+
end
|
18
|
+
<% specable_attrs.each do |attr| -%>
|
19
|
+
|
20
|
+
describe '#<%= attr.name %>' do
|
21
|
+
<%- if attr.assignable_values.present? -%>
|
22
|
+
it { is_expected.to allow_value(<%= assignable_value_for(attr).inspect %>).for(:<%= attr.name %>) }
|
23
|
+
it { is_expected.to_not allow_value(<%= unassignable_value_for(attr).inspect %>).for(:<%= attr.name %>) }
|
24
|
+
<%- end -%>
|
25
|
+
<%- unless attr.default.nil? -%>
|
26
|
+
|
27
|
+
it 'has a default' do
|
28
|
+
expect( subject.<%= attr.name %> ).to eql(<%= attr.default.inspect %>)
|
29
|
+
end
|
30
|
+
<%- end -%>
|
31
|
+
end
|
32
|
+
<% end -%>
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Generate navigation.
|
2
|
+
|
3
|
+
require 'katapult/generator'
|
4
|
+
|
5
|
+
module Katapult
|
6
|
+
module Generators
|
7
|
+
class NavigationGenerator < Katapult::Generator
|
8
|
+
|
9
|
+
desc 'Generate the navigation'
|
10
|
+
source_root File.expand_path('../templates', __FILE__)
|
11
|
+
|
12
|
+
|
13
|
+
def create_navigation
|
14
|
+
template 'app/models/navigation.rb'
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def navigation
|
20
|
+
@element
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Navigation
|
2
|
+
include Navy::Description
|
3
|
+
|
4
|
+
navigation :<%= name.downcase %> do
|
5
|
+
<% navigation.wuis.each do |wui| -%>
|
6
|
+
<%- if wui.find_action(:index) -%>
|
7
|
+
section <%= navigation.section_name(wui) %>, <%= wui.model_name(:human_plural).titlecase.inspect %>, <%= wui.path(:index) %>
|
8
|
+
<%- end -%>
|
9
|
+
<% end -%>
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Generate files and directories from an application model file. Afterwards do
|
2
|
+
# any setup left necessary (e.g. updating the database).
|
3
|
+
|
4
|
+
# The application model transformation is split into two parts:
|
5
|
+
# 1) parse the model into an object-based representation
|
6
|
+
# 2) render the parsed model into code
|
7
|
+
|
8
|
+
require 'katapult/parser'
|
9
|
+
|
10
|
+
module Katapult
|
11
|
+
class TransformGenerator < Rails::Generators::Base
|
12
|
+
desc 'Transform the katapult application model'
|
13
|
+
|
14
|
+
argument :path, required: true, type: :string,
|
15
|
+
description: 'The path to the application model file'
|
16
|
+
|
17
|
+
|
18
|
+
def transform_application_model
|
19
|
+
say_status :parse, path
|
20
|
+
@app_model = Katapult::Parser.new.parse(path)
|
21
|
+
|
22
|
+
say_status :render, "into #{application_name}"
|
23
|
+
@app_model.render
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_root_route
|
27
|
+
root_wui = @app_model.wuis.find do |wui|
|
28
|
+
wui.find_action :index
|
29
|
+
end
|
30
|
+
|
31
|
+
route "root '#{ root_wui.model_name(:variables) }#index'" if root_wui
|
32
|
+
end
|
33
|
+
|
34
|
+
def migrate
|
35
|
+
run 'bin/rake db:drop:all &> /dev/null'
|
36
|
+
run 'bin/rake db:create db:migrate RAILS_ENV=development'
|
37
|
+
run 'bin/rake db:create db:migrate RAILS_ENV=test'
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def application_name
|
43
|
+
File.basename(Dir.pwd)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
% rails_actions = wui.actions - wui.custom_actions
|
2
|
+
resources <%= model_name(:symbols) %>, only: <%= rails_actions.map(&:name).map(&:to_sym) %> do
|
3
|
+
member do
|
4
|
+
% wui.custom_actions.select(&:member?).each do |action|
|
5
|
+
<%= action.method %> '<%= action.name %>'
|
6
|
+
% end
|
7
|
+
end
|
8
|
+
collection do
|
9
|
+
% wui.custom_actions.select(&:collection?).each do |action|
|
10
|
+
<%= action.method %> '<%= action.name %>'
|
11
|
+
% end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class <%= model_name(:classes) %>Controller < ApplicationController
|
2
|
+
<%- if navigation -%>
|
3
|
+
|
4
|
+
before_filter :set_section
|
5
|
+
<%- end -%>
|
6
|
+
<% if wui.find_action :index -%>
|
7
|
+
|
8
|
+
def index
|
9
|
+
<%= method_name(:load_collection) %>
|
10
|
+
end
|
11
|
+
<% end -%>
|
12
|
+
<% if wui.find_action :show -%>
|
13
|
+
|
14
|
+
def show
|
15
|
+
<%= method_name(:load_object) %>
|
16
|
+
end
|
17
|
+
<% end -%>
|
18
|
+
<% if wui.find_action :new -%>
|
19
|
+
|
20
|
+
def new
|
21
|
+
<%= method_name(:build) %>
|
22
|
+
end
|
23
|
+
<% end -%>
|
24
|
+
<% if wui.find_action :create -%>
|
25
|
+
|
26
|
+
def create
|
27
|
+
<%= method_name(:build) %>
|
28
|
+
<%= method_name(:save) %> or render :new
|
29
|
+
end
|
30
|
+
<% end -%>
|
31
|
+
<% if wui.find_action :edit -%>
|
32
|
+
|
33
|
+
def edit
|
34
|
+
<%= method_name(:load_object) %>
|
35
|
+
<%= method_name(:build) %>
|
36
|
+
end
|
37
|
+
<% end -%>
|
38
|
+
<% if wui.find_action :update -%>
|
39
|
+
|
40
|
+
def update
|
41
|
+
<%= method_name(:load_object) %>
|
42
|
+
<%= method_name(:build) %>
|
43
|
+
<%= method_name(:save) %> or render :edit
|
44
|
+
end
|
45
|
+
<% end -%>
|
46
|
+
<% if wui.find_action :destroy -%>
|
47
|
+
|
48
|
+
def destroy
|
49
|
+
<%= method_name(:load_object) %>
|
50
|
+
<%= model_name(:ivar) %>.destroy
|
51
|
+
redirect_to <%= wui.path(:index) %>
|
52
|
+
end
|
53
|
+
<% end -%>
|
54
|
+
<% wui.custom_actions.each do |action| -%>
|
55
|
+
|
56
|
+
def <%= action.name %>
|
57
|
+
<% if action.member? -%>
|
58
|
+
<%= method_name(:load_object) %>
|
59
|
+
<%- unless action.get? -%>
|
60
|
+
redirect_to <%= model_name(:ivar) %>
|
61
|
+
<%- end -%>
|
62
|
+
<% elsif action.collection? -%>
|
63
|
+
<%= method_name(:load_collection) %>
|
64
|
+
<%- unless action.get? -%>
|
65
|
+
redirect_to <%= wui.path(:index) %>
|
66
|
+
<%- end -%>
|
67
|
+
<% end -%>
|
68
|
+
end
|
69
|
+
<% end -%>
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def <%= method_name(:load_collection) %>
|
74
|
+
<%= model_name(:ivars) %> ||= <%= method_name(:scope) %>.to_a
|
75
|
+
end
|
76
|
+
|
77
|
+
def <%= method_name(:load_object) %>
|
78
|
+
<%= model_name(:ivar) %> ||= <%= method_name(:scope) %>.find(params[:id])
|
79
|
+
end
|
80
|
+
|
81
|
+
def <%= method_name(:build) %>
|
82
|
+
<%= model_name(:ivar) %> ||= <%= method_name(:scope) %>.build
|
83
|
+
<%= model_name(:ivar) %>.attributes = <%= method_name(:params) %>
|
84
|
+
end
|
85
|
+
|
86
|
+
def <%= method_name(:save) %>
|
87
|
+
if <%= model_name(:ivar) %>.save
|
88
|
+
redirect_to <%= model_name(:ivar) %>
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def <%= method_name(:params) %>
|
93
|
+
<%= method_name(:params) %> = params[:<%= model_name(:variable) %>]
|
94
|
+
<%= method_name(:params) %> ? <%= method_name(:params) %>.permit(<%= wui.model.attrs.map(&:name).map(&:to_sym) %>) : {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def <%= method_name(:scope) %>
|
98
|
+
<%= model_name(:class_name) %>.scoped
|
99
|
+
end
|
100
|
+
|
101
|
+
<%- if navigation -%>
|
102
|
+
def set_section
|
103
|
+
in_sections <%= navigation.section_name(wui) %>
|
104
|
+
end
|
105
|
+
<%- end -%>
|
106
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Generate a controller, routes and view files for a WUI.
|
2
|
+
|
3
|
+
require 'katapult/generator'
|
4
|
+
require 'generators/katapult/haml/haml_generator'
|
5
|
+
|
6
|
+
module Katapult
|
7
|
+
module Generators
|
8
|
+
class WUIGenerator < Katapult::Generator
|
9
|
+
|
10
|
+
desc 'Generate a Web User Interface'
|
11
|
+
|
12
|
+
check_class_collision suffix: 'Controller'
|
13
|
+
source_root File.expand_path('../templates', __FILE__)
|
14
|
+
|
15
|
+
|
16
|
+
def create_controller_file
|
17
|
+
template 'controller.rb', File.join('app', 'controllers', "#{ model_name(:variables) }_controller.rb")
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_route
|
21
|
+
route render_partial('_route.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate_views
|
25
|
+
Generators::HamlGenerator.new(wui).invoke_all
|
26
|
+
end
|
27
|
+
|
28
|
+
no_tasks do
|
29
|
+
def method_name(name)
|
30
|
+
case name
|
31
|
+
when :load_collection then "load_#{model_name :variables}"
|
32
|
+
when :load_object then "load_#{model_name :variable}"
|
33
|
+
when :build then "build_#{model_name :variable}"
|
34
|
+
when :save then "save_#{model_name :variable}"
|
35
|
+
when :params then "#{model_name :variable}_params"
|
36
|
+
when :scope then "#{model_name :variable}_scope"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def model_name(kind = nil)
|
41
|
+
wui.model_name(kind)
|
42
|
+
end
|
43
|
+
|
44
|
+
def navigation
|
45
|
+
wui.application_model.navigation
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def wui
|
52
|
+
@element
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/katapult.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Models a controller action. To be used within the block of a WUI.
|
2
|
+
|
3
|
+
require 'katapult/element'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
require 'active_support/core_ext/string/inquiry'
|
6
|
+
|
7
|
+
module Katapult
|
8
|
+
class Action < Element
|
9
|
+
|
10
|
+
options :method, :scope
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
super
|
14
|
+
|
15
|
+
self.scope ||= (name == 'index') ? :collection : :member
|
16
|
+
set_method
|
17
|
+
end
|
18
|
+
|
19
|
+
delegate :post?, :get?, :put?, to: :method_inquiry
|
20
|
+
delegate :member?, :collection?, to: :scope_inquiry
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def method_inquiry
|
25
|
+
@method.to_s.inquiry
|
26
|
+
end
|
27
|
+
|
28
|
+
def scope_inquiry
|
29
|
+
@scope.to_s.inquiry
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_method
|
33
|
+
self.method ||= case name
|
34
|
+
when 'create', 'update'
|
35
|
+
:post
|
36
|
+
when 'destroy'
|
37
|
+
:delete
|
38
|
+
else # index, show, new, edit + custom actions
|
39
|
+
:get
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Internal representation of an application model file.
|
2
|
+
|
3
|
+
module Katapult
|
4
|
+
class ApplicationModel
|
5
|
+
|
6
|
+
attr_reader :models, :wuis, :navigation
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@models = []
|
10
|
+
@wuis = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_model(model)
|
14
|
+
model.set_application_model(self)
|
15
|
+
@models << model
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_model(name)
|
19
|
+
models.find { |m| m.name == name }
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_wui(wui)
|
23
|
+
wui.set_application_model(self)
|
24
|
+
@wuis << wui
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_wui(name)
|
28
|
+
wuis.find { |w| w.name == name }
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_navigation(navigation)
|
32
|
+
navigation.set_application_model(self)
|
33
|
+
@navigation = navigation
|
34
|
+
end
|
35
|
+
|
36
|
+
# ---
|
37
|
+
|
38
|
+
def render
|
39
|
+
models.each &:render
|
40
|
+
wuis.each &:render
|
41
|
+
navigation.render if navigation
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Models a model attribute. To be used within the block of a model element.
|
2
|
+
|
3
|
+
require 'katapult/element'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
require 'active_support/core_ext/string/inquiry'
|
6
|
+
require 'zlib'
|
7
|
+
|
8
|
+
module Katapult
|
9
|
+
class Attribute < Element
|
10
|
+
|
11
|
+
options :type, :default, :assignable_values, :allow_blank
|
12
|
+
|
13
|
+
UnknownTypeError = Class.new(StandardError)
|
14
|
+
MissingOptionError = Class.new(StandardError)
|
15
|
+
TYPES = %i(string email url integer money text markdown flag datetime)
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
super
|
19
|
+
|
20
|
+
self.type ||= :email if name.to_s =~ /email/
|
21
|
+
self.type ||= :string
|
22
|
+
|
23
|
+
validate
|
24
|
+
end
|
25
|
+
|
26
|
+
delegate :flag?, to: :type_inquiry
|
27
|
+
|
28
|
+
def has_defaults?
|
29
|
+
default and not [flag?, assignable_values].any?
|
30
|
+
end
|
31
|
+
|
32
|
+
def for_migration
|
33
|
+
db_type = case type
|
34
|
+
when :email, :url
|
35
|
+
'string'
|
36
|
+
when :flag
|
37
|
+
'boolean'
|
38
|
+
when :money
|
39
|
+
'decimal{10,2}' # {precision,scale} = total digits, decimal places
|
40
|
+
else
|
41
|
+
type
|
42
|
+
end
|
43
|
+
|
44
|
+
"#{name}:#{db_type}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_value
|
48
|
+
if assignable_values
|
49
|
+
assignable_values.first
|
50
|
+
|
51
|
+
else
|
52
|
+
case type
|
53
|
+
when :string then "#{name}-string"
|
54
|
+
when :email then "#{name}@example.com"
|
55
|
+
when :url then "#{name}.example.com"
|
56
|
+
when :text then "#{name}-text"
|
57
|
+
|
58
|
+
# Deterministically generate a value from the attribute's name
|
59
|
+
when :integer then Zlib.crc32(name).modulo(1000)
|
60
|
+
when :money then Zlib.crc32(name).modulo(1000) / 100.0
|
61
|
+
when :datetime then Time.at(Zlib.crc32(name))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def type_inquiry
|
69
|
+
@type.to_s.inquiry
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate
|
73
|
+
TYPES.include?(type) or raise UnknownTypeError,
|
74
|
+
"Attribute type :#{type} is not supported. Use one of #{TYPES.inspect}."
|
75
|
+
|
76
|
+
if flag? and default.nil?
|
77
|
+
raise MissingOptionError,
|
78
|
+
"The :flag attribute '#{name}' requires a default (true or false)."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|