autoforme 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ === 0.5.0 (2013-12-13)
2
+
3
+ * Initial Public Release
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
+ });