autoforme 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +3 -0
- data/MIT-LICENSE +18 -0
- data/README.rdoc +226 -0
- data/Rakefile +79 -0
- data/autoforme.js +77 -0
- data/lib/autoforme/action.rb +629 -0
- data/lib/autoforme/framework.rb +145 -0
- data/lib/autoforme/frameworks/rails.rb +80 -0
- data/lib/autoforme/frameworks/sinatra.rb +59 -0
- data/lib/autoforme/model.rb +377 -0
- data/lib/autoforme/models/sequel.rb +344 -0
- data/lib/autoforme/opts_attributes.rb +29 -0
- data/lib/autoforme/request.rb +53 -0
- data/lib/autoforme/table.rb +70 -0
- data/lib/autoforme/version.rb +9 -0
- data/lib/autoforme.rb +57 -0
- data/spec/associations_spec.rb +505 -0
- data/spec/basic_spec.rb +661 -0
- data/spec/mtm_spec.rb +333 -0
- data/spec/rails_spec_helper.rb +75 -0
- data/spec/sequel_spec_helper.rb +44 -0
- data/spec/sinatra_spec_helper.rb +53 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/unit_spec.rb +449 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6375a5b69141ffd6b2d499165407588b1e11154d
|
4
|
+
data.tar.gz: 24578636cda7918d7deb38dc24ccd44d4d2c5887
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83f29c0cf2764928f08d23c7b4e54b636a50670a1d75dee7698616e4f8828206b3c3adee93c75937cbd6d0d81b1aff4cfc2395e934429a5eb12c8b9b1a05a9bc
|
7
|
+
data.tar.gz: 869465f07383c25f289f39c143dacf978502a9af1f0c8d6c0cdc5f0b271ec5aa4cc2b8190680c0fcbd7c43968437075036221d1e84a9fb51511dc9979821d226
|
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2013 Jeremy Evans
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
= AutoForme
|
2
|
+
|
3
|
+
AutoForme is an administrative web front end to an ORM that uses
|
4
|
+
Forme [1] for building the HTML forms. It is designed to
|
5
|
+
integrate easily into web frameworks, and currently supports
|
6
|
+
both Sinatra and Rails.
|
7
|
+
|
8
|
+
AutoForme's UI and capabilities are modeled on
|
9
|
+
scaffolding_extensions [2], though AutoForme is considerably more
|
10
|
+
flexible in terms of configuration.
|
11
|
+
|
12
|
+
1. https://github.com/jeremyevans/forme
|
13
|
+
2. https://github.com/jeremyevans/scaffolding_extensions
|
14
|
+
|
15
|
+
= Demo Site
|
16
|
+
|
17
|
+
A demo site is available at http://autoforme-demo.jeremyevans.net
|
18
|
+
|
19
|
+
= Source Code
|
20
|
+
|
21
|
+
Source code is available on GitHub at https://github.com/jeremyevans/autoforme
|
22
|
+
|
23
|
+
= Features
|
24
|
+
|
25
|
+
* Create, update, edit, and view model objects
|
26
|
+
* Browse and search model objects
|
27
|
+
* Edit many-to-many relationships for model objects
|
28
|
+
* Easily access associated objects
|
29
|
+
* Support autocompletion for all objects
|
30
|
+
* Allow customization for all likely configuration points, using
|
31
|
+
any parameters available in the request
|
32
|
+
|
33
|
+
= Basic Configuration
|
34
|
+
|
35
|
+
AutoForme is configured using a fairly simple DSL. Here is an example:
|
36
|
+
|
37
|
+
class App < Sinatra::Base
|
38
|
+
AutoForme.for(:sinatra, self) do
|
39
|
+
model_type :sequel
|
40
|
+
order [:name]
|
41
|
+
|
42
|
+
model Artist do
|
43
|
+
columns [:name]
|
44
|
+
end
|
45
|
+
model Album do
|
46
|
+
columns [:artist, :name]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Let's break down how this works. You setup AutoForme using <tt>AutoForme.for</tt>,
|
52
|
+
which takes 2 arguments, the controller type symbol (currently either :sinatra or :rails),
|
53
|
+
and the controller class (either a Sinatra::Base or ActionController::Base subclass). You
|
54
|
+
pass <tt>AutoForme.for</tt> a block, which is instance evaled at the framework level. This
|
55
|
+
level sets the defaults.
|
56
|
+
|
57
|
+
Inside the framework block, you first call model_type with a symbol representing the ORM you are
|
58
|
+
using. Currently, only Sequel is supported, so this should be :sequel.
|
59
|
+
|
60
|
+
The order call in the framework block sets the default order for all models.
|
61
|
+
|
62
|
+
The model calls in the framework block take a ORM model class. As only Sequel is currently
|
63
|
+
supported, this should be a Sequel::Model subclass. The block passed to the model method is
|
64
|
+
instance evaled at the model level, and sets the configuration for that model. In this example,
|
65
|
+
the Artist model will only show the name column, and the Album model will only show the artist
|
66
|
+
association and the name column.
|
67
|
+
|
68
|
+
In your application, you can then to go '/Artist/browse' or '/Album/browse' to get to the web
|
69
|
+
UI exposed to AutoForme.
|
70
|
+
|
71
|
+
= Design
|
72
|
+
|
73
|
+
== Principles
|
74
|
+
|
75
|
+
* Use Forme to generate the forms
|
76
|
+
* Do not modify/extend model or controller classes
|
77
|
+
* Assume that the web framework provides the layout
|
78
|
+
* Do not use templates, render form objects to strings
|
79
|
+
* Use a block-based DSL in the controller for configuration
|
80
|
+
* Allow customization on a per-request basis for everything
|
81
|
+
|
82
|
+
== Basic Implementation
|
83
|
+
|
84
|
+
The web framework controllers call <tt>AutoForme.for</tt> to create
|
85
|
+
AutoForme::Framework instances, which contain and set default values
|
86
|
+
for AutoForme::Model instances.
|
87
|
+
|
88
|
+
When a request comes in from the web framework, the AutoForme::Framework
|
89
|
+
instance wraps request-level data in a AutoForme::Request. Then it
|
90
|
+
creates an AutoForme::Action to handle this request. The
|
91
|
+
AutoForme::Action either returns a string that the web framework then
|
92
|
+
renders, or it redirects to another page.
|
93
|
+
|
94
|
+
= Advanced Configuration
|
95
|
+
|
96
|
+
AutoForme doesn't have all that many features compared to other admin
|
97
|
+
frameworks, but the features it does have are extremely flexible.
|
98
|
+
|
99
|
+
Most of the configuration you'll do in AutoForme is at the model
|
100
|
+
level (in the context of an AutoForme::Model instance), so we'll start
|
101
|
+
looking at the customization options there. The most common options
|
102
|
+
are probably:
|
103
|
+
|
104
|
+
columns :: This is an array of column/association name symbols to use
|
105
|
+
for the model.
|
106
|
+
column_options :: This is a hash of column options for the model,
|
107
|
+
keyed by column symbol, with values that are hashes
|
108
|
+
of column options.
|
109
|
+
order :: This is an expression or an array of expressions by which
|
110
|
+
to order returned rows.
|
111
|
+
|
112
|
+
Note that for each of the customization options, you can do per-request
|
113
|
+
customization by using a proc which is called with the type symbol and
|
114
|
+
request (AutoForme::Request instance), which should return an appropriate
|
115
|
+
object.
|
116
|
+
|
117
|
+
columns :: Proc called with type symbol and request, should return array
|
118
|
+
of column/association symbols
|
119
|
+
column_options :: Proc called with column/association symbol, type symbol
|
120
|
+
and request, should return hash of column options.
|
121
|
+
order :: Proc called with type symbol and request, should return expression
|
122
|
+
or array of expressions by which to order returned rows.
|
123
|
+
|
124
|
+
Below is brief description of other available options. Note that just like the above
|
125
|
+
options you can use Procs with most of these options to do customization on a
|
126
|
+
per-request basis.
|
127
|
+
|
128
|
+
association_links :: Array of association symbols for associations to display on the show/edit pages,
|
129
|
+
can also be set to :all for all associations or :all_except_mtm for all associations
|
130
|
+
except many to many associations.
|
131
|
+
autocomplete_options :: Enable autocompletion for this model, with the given
|
132
|
+
options. The following keys are respected:
|
133
|
+
:callback :: Proc called with dataset and options hash containing :type, :request, and :query
|
134
|
+
:display :: A SQL expression to search on and display in the result
|
135
|
+
:limit :: The number of results to return
|
136
|
+
:filter :: Similar to callback, but overriding the default filter
|
137
|
+
(a case insensitive substring search on display)
|
138
|
+
eager :: Array of associations to eagerly load in separate queries
|
139
|
+
eager_graph :: Array of associations to eager load in the same query
|
140
|
+
(necessary if order or filter refers to them)
|
141
|
+
filter :: A Proc called with a dataset, type symbol, and request that
|
142
|
+
can be used to filter the available rows. Can be used to
|
143
|
+
implement access control.
|
144
|
+
redirect :: A Proc called with an object, type symbol, and request that can be used to override
|
145
|
+
the default redirecting after form submittal.
|
146
|
+
inline_mtm_associations :: Array of many to many association symbols to allow editing on the edit page
|
147
|
+
lazy_load_association_links :: Whether to show the association links directly on the show/edit pages,
|
148
|
+
or to load them via ajax on request
|
149
|
+
mtm_associations :: Array of many to many association symbols to support editing on a separate page
|
150
|
+
per_page :: Number of records to show per page on the browse and search pages
|
151
|
+
session_value :: Sets up a filter and before_create hook that makes it so access is limited
|
152
|
+
to objects where the object's column value is the same as the session value
|
153
|
+
with the same name.
|
154
|
+
supported_actions :: Array of action symbols to support for the model, should be a subset of
|
155
|
+
[:browse, :new, :show, :edit, :delete, :search, :mtm_edit]
|
156
|
+
|
157
|
+
These options are related to displayed output:
|
158
|
+
|
159
|
+
form_attributes :: Hash of attributes to use for any form tags
|
160
|
+
form_options :: Hash of Forme::Form options to pass for any forms created
|
161
|
+
class_display_name :: The string to use when referring to the model class
|
162
|
+
display_name :: The string to use when referring to a model instance. Can either be a symbol
|
163
|
+
representing an instance method call, or a Proc called with the model object,
|
164
|
+
the model object and type symbol, or the model object, type symbol, and request,
|
165
|
+
depending on the arity of the Proc.
|
166
|
+
link_name :: The string to use in links for the class
|
167
|
+
page_footer :: Override the default footer used for pages
|
168
|
+
page_header :: Override the default header used for pages
|
169
|
+
table_class :: The html class string to use for the browse and search tables
|
170
|
+
|
171
|
+
These hook options should be callable objects that are called with the model object and the request.
|
172
|
+
|
173
|
+
after_create :: Called after creating the object
|
174
|
+
after_destroy :: Called after destroy the object
|
175
|
+
after_update :: Called after updating the object
|
176
|
+
before_create :: Called before creating the object
|
177
|
+
before_destroy :: Called before destroy the object
|
178
|
+
before_edit :: Called before displaying object on edit page
|
179
|
+
before_new :: Called before displaying object on new page
|
180
|
+
before_update :: Called before updating the object
|
181
|
+
|
182
|
+
There's also an addition before_action hook that is called with the type symbol of the request, and
|
183
|
+
the request before every page.
|
184
|
+
|
185
|
+
In addition to being specified at the model level, almost all of these options can be specified at the
|
186
|
+
framework level, where they operate as default values for models that don't specify the options. Just
|
187
|
+
like the model level, the framework level also allows customization on a per request basis, though
|
188
|
+
framework-level Procs generally take the model class as an initial argument (in addition to the type
|
189
|
+
symbol and request).
|
190
|
+
|
191
|
+
Additionally, AutoForme.for accepts a :prefix option that controls where the forms are mounted:
|
192
|
+
|
193
|
+
AutoForm.for(:sinatra, self, :prefix=>'/path/to') do
|
194
|
+
model Artist
|
195
|
+
end
|
196
|
+
|
197
|
+
Means you can go to <tt>/path/to/Artist/browse</tt> to browse the artists.
|
198
|
+
|
199
|
+
= Javascript
|
200
|
+
|
201
|
+
By default, AutoForme requires no javascript. The only optional
|
202
|
+
part of AutoForme that requires javascript is the autocompleting.
|
203
|
+
AutoForme also has javascript support for the progressive enhancement of the following:
|
204
|
+
|
205
|
+
* Loading association links via ajax if lazy_load_association_links is true.
|
206
|
+
* Adding and removing many-to-many associated objects via ajax for inline_mtm_associations
|
207
|
+
|
208
|
+
AutoForme's javascript support is contained in the autoforme.js file in the root
|
209
|
+
of the distribution. AutoForme's autocompleting support also requires
|
210
|
+
https://github.com/dyve/jquery-autocomplete
|
211
|
+
|
212
|
+
= TODO
|
213
|
+
|
214
|
+
* capybara-webkit tests for ajax behavior
|
215
|
+
* read_only fields for edit page
|
216
|
+
* one_to_many/many_to_many associations in columns
|
217
|
+
* configurable searching
|
218
|
+
* nested form objects
|
219
|
+
|
220
|
+
= License
|
221
|
+
|
222
|
+
MIT
|
223
|
+
|
224
|
+
= Author
|
225
|
+
|
226
|
+
Jeremy Evans <code@jeremyevans.net>
|
data/Rakefile
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/clean"
|
3
|
+
|
4
|
+
CLEAN.include ["autoforme-*.gem", "rdoc", "coverage"]
|
5
|
+
|
6
|
+
desc "Build autoforme gem"
|
7
|
+
task :package=>[:clean] do |p|
|
8
|
+
sh %{#{FileUtils::RUBY} -S gem build autoforme.gemspec}
|
9
|
+
end
|
10
|
+
|
11
|
+
### Specs
|
12
|
+
|
13
|
+
begin
|
14
|
+
require "rspec/core/rake_task"
|
15
|
+
|
16
|
+
spec = lambda do |name, files, d|
|
17
|
+
lib_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'lib')
|
18
|
+
ENV['RUBYLIB'] ? (ENV['RUBYLIB'] += ":#{lib_dir}") : (ENV['RUBYLIB'] = lib_dir)
|
19
|
+
desc d
|
20
|
+
RSpec::Core::RakeTask.new(name) do |t|
|
21
|
+
t.pattern= files
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
spec_with_cov = lambda do |name, files, d|
|
26
|
+
spec.call(name, files, d)
|
27
|
+
desc "#{d} with coverage"
|
28
|
+
task "#{name}_cov" do
|
29
|
+
ENV['COVERAGE'] = '1'
|
30
|
+
Rake::Task[name].invoke
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
task :default => [:spec]
|
35
|
+
spec_with_cov.call("spec", Dir["spec/*_spec.rb"], "Run specs with sinatra/sequel")
|
36
|
+
|
37
|
+
desc "Run specs with rails/sequel"
|
38
|
+
task :rails_spec do
|
39
|
+
begin
|
40
|
+
ENV['FRAMEWORK'] = 'rails'
|
41
|
+
Rake::Task[:spec].invoke
|
42
|
+
ensure
|
43
|
+
ENV.delete('FRAMEWORK')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
rescue LoadError
|
48
|
+
task :default do
|
49
|
+
puts "Must install rspec >=2.0 to run the default task (which runs specs)"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
### RDoc
|
54
|
+
|
55
|
+
RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'AutoForme: Web Adminstrative Console for Sinatra/Rails and Sequel']
|
56
|
+
|
57
|
+
begin
|
58
|
+
gem 'rdoc', '= 3.12.2'
|
59
|
+
gem 'hanna-nouveau'
|
60
|
+
RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
|
61
|
+
rescue Gem::LoadError
|
62
|
+
end
|
63
|
+
|
64
|
+
rdoc_task_class = begin
|
65
|
+
require "rdoc/task"
|
66
|
+
RDoc::Task
|
67
|
+
rescue LoadError
|
68
|
+
require "rake/rdoctask"
|
69
|
+
Rake::RDocTask
|
70
|
+
end
|
71
|
+
|
72
|
+
RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
|
73
|
+
|
74
|
+
rdoc_task_class.new do |rdoc|
|
75
|
+
rdoc.rdoc_dir = "rdoc"
|
76
|
+
rdoc.options += RDOC_OPTS
|
77
|
+
rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb"
|
78
|
+
end
|
79
|
+
|
data/autoforme.js
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
$(function() {
|
2
|
+
var autoforme = $('#autoforme_content');
|
3
|
+
var base_url = autoforme.data('url');
|
4
|
+
|
5
|
+
function autoforme_fix_autocomplete(e) {
|
6
|
+
$(e).find('.autoforme_autocomplete').each(function(){
|
7
|
+
$(this).val($(this).val().split(' - ', 2)[0]);
|
8
|
+
});
|
9
|
+
}
|
10
|
+
|
11
|
+
function autoforme_setup_autocomplete() {
|
12
|
+
autoforme.find('.autoforme_autocomplete').each(function(){
|
13
|
+
var e = $(this);
|
14
|
+
var column = e.data('column');
|
15
|
+
var exclude = e.data('exclude');
|
16
|
+
var url = base_url + '/autocomplete';
|
17
|
+
if (column) {
|
18
|
+
url += '/' + column;
|
19
|
+
}
|
20
|
+
url += '?type=' + e.data('type');
|
21
|
+
if (exclude) {
|
22
|
+
url += '&exclude=' + exclude;
|
23
|
+
}
|
24
|
+
e.autocomplete(url);
|
25
|
+
});
|
26
|
+
}
|
27
|
+
|
28
|
+
autoforme_setup_autocomplete();
|
29
|
+
|
30
|
+
autoforme.on('submit', 'form', function(e){
|
31
|
+
autoforme_fix_autocomplete(this);
|
32
|
+
});
|
33
|
+
|
34
|
+
|
35
|
+
$('#lazy_load_association_links').click(function(e){
|
36
|
+
var t = $(this);
|
37
|
+
t.load(base_url + "association_links/" + t.data('object') + "?type=" + t.data('type'), autoforme_setup_autocomplete);
|
38
|
+
t.unbind('click');
|
39
|
+
e.preventDefault();
|
40
|
+
});
|
41
|
+
|
42
|
+
autoforme.on('submit', '.mtm_add_associations', function(e){
|
43
|
+
var form = $(this);
|
44
|
+
if (form.find('.autoforme_autocomplete').length == 0) {
|
45
|
+
var select = form.find('select')[0];
|
46
|
+
$.post(this.action, form.serialize(), function(data, textStatus){
|
47
|
+
$(select).find('option:selected').remove();
|
48
|
+
select.selectedIndex = 0;
|
49
|
+
$(form.data('remove')).append(data);
|
50
|
+
});
|
51
|
+
} else {
|
52
|
+
autoforme_fix_autocomplete(form);
|
53
|
+
$.post(this.action, form.serialize(), function(data, textStatus){
|
54
|
+
var t = form.find('.autoforme_autocomplete');
|
55
|
+
t.val('');
|
56
|
+
t.data('autocompleter').cacheFlush();
|
57
|
+
$(form.data('remove')).append(data);
|
58
|
+
});
|
59
|
+
}
|
60
|
+
e.preventDefault();
|
61
|
+
});
|
62
|
+
|
63
|
+
autoforme.on('submit', '.inline_mtm_remove_associations form', function(e){
|
64
|
+
var form = $(this);
|
65
|
+
var parent = form.parent();
|
66
|
+
$.post(this.action, form.serialize(), function(data, textStatus){
|
67
|
+
var t = $(form.data('add'));
|
68
|
+
if (t[0].type == "text") {
|
69
|
+
t.data('autocompleter').cacheFlush();
|
70
|
+
} else {
|
71
|
+
t.append(data);
|
72
|
+
}
|
73
|
+
parent.remove();
|
74
|
+
});
|
75
|
+
e.preventDefault();
|
76
|
+
});
|
77
|
+
});
|