effective_regions 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +580 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/images/effective/templates/image_and_title.png +0 -0
  6. data/app/assets/javascripts/effective/snippets/current_user_info.js.coffee +24 -0
  7. data/app/controllers/effective/regions_controller.rb +154 -0
  8. data/app/helpers/effective_regions_helper.rb +108 -0
  9. data/app/models/concerns/acts_as_regionable.rb +34 -0
  10. data/app/models/effective/access_denied.rb +17 -0
  11. data/app/models/effective/region.rb +44 -0
  12. data/app/models/effective/snippets/current_user_info.rb +12 -0
  13. data/app/models/effective/snippets/snippet.rb +69 -0
  14. data/app/models/effective/templates/image_and_title.rb +13 -0
  15. data/app/models/effective/templates/template.rb +26 -0
  16. data/app/views/effective/snippets/_current_user_info.html.haml +10 -0
  17. data/app/views/effective/templates/_image_and_title.html.haml +5 -0
  18. data/config/routes.rb +19 -0
  19. data/db/migrate/01_create_effective_regions.rb.erb +23 -0
  20. data/lib/effective_regions.rb +57 -0
  21. data/lib/effective_regions/engine.rb +27 -0
  22. data/lib/effective_regions/version.rb +3 -0
  23. data/lib/generators/effective_regions/install_generator.rb +32 -0
  24. data/lib/generators/templates/README +1 -0
  25. data/lib/generators/templates/effective_regions.rb +71 -0
  26. data/lib/tasks/effective_regions_tasks.rake +4 -0
  27. data/spec/dummy/README.rdoc +261 -0
  28. data/spec/dummy/Rakefile +7 -0
  29. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  30. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  31. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  32. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  33. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  34. data/spec/dummy/config.ru +4 -0
  35. data/spec/dummy/config/application.rb +59 -0
  36. data/spec/dummy/config/boot.rb +10 -0
  37. data/spec/dummy/config/database.yml +25 -0
  38. data/spec/dummy/config/environment.rb +5 -0
  39. data/spec/dummy/config/environments/development.rb +37 -0
  40. data/spec/dummy/config/environments/production.rb +67 -0
  41. data/spec/dummy/config/environments/test.rb +37 -0
  42. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  43. data/spec/dummy/config/initializers/inflections.rb +15 -0
  44. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  45. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  46. data/spec/dummy/config/initializers/session_store.rb +8 -0
  47. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/spec/dummy/config/locales/en.yml +5 -0
  49. data/spec/dummy/config/routes.rb +58 -0
  50. data/spec/dummy/db/development.sqlite3 +0 -0
  51. data/spec/dummy/db/schema.rb +16 -0
  52. data/spec/dummy/db/test.sqlite3 +0 -0
  53. data/spec/dummy/log/development.log +20 -0
  54. data/spec/dummy/log/test.log +2 -0
  55. data/spec/dummy/public/404.html +26 -0
  56. data/spec/dummy/public/422.html +26 -0
  57. data/spec/dummy/public/500.html +25 -0
  58. data/spec/dummy/public/favicon.ico +0 -0
  59. data/spec/dummy/script/rails +6 -0
  60. data/spec/effective_regions_spec.rb +7 -0
  61. data/spec/spec_helper.rb +34 -0
  62. data/spec/support/factories.rb +1 -0
  63. metadata +199 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3da3a25db0858892dedca323cbf867a7e9017961
4
+ data.tar.gz: c9ddf036366111755f3af12e9a748600256289b8
5
+ SHA512:
6
+ metadata.gz: a72c5adc99f5cdc4711514efdcb461356962f97d51ee25496575b81798eaf750a7e27bf21eee61317375992e243f84af3cb9d041c19059be8b19717481f777ff
7
+ data.tar.gz: d57234452b3c5881ed25ba0c96edb822ba709c2ae862d1133a960035ce8d09c9ef8db1e9fafcb65b87f724d945e7576ea73ca659ffe82aa1ed92788fe2154735
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,580 @@
1
+ # Effective Regions
2
+
3
+ Create editable content regions within your existing, ordinary ActionView::Base views, and update content with an actually-good full-screen WYSIWYG editor.
4
+
5
+ Define and Insert Snippets (mini model-view components) that intelligently render content based on the user selected attributes.
6
+
7
+ Specify and Insert pre-defined HTML-only templates for small pieces of common HTML.
8
+
9
+ Uses the actually-good fullscreen editor [effective_ckeditor](https://github.com/code-and-effect/effective_ckeditor) to achieve near perfect WYSIWYG editting of content regions.
10
+
11
+
12
+ ## Getting Started
13
+
14
+ Add to your Gemfile:
15
+
16
+ ```ruby
17
+ gem 'effective_regions'
18
+ ```
19
+
20
+ Run the bundle command to install it:
21
+
22
+ ```console
23
+ bundle install
24
+ ```
25
+
26
+ Then run the generator:
27
+
28
+ ```ruby
29
+ rails generate effective_regions:install
30
+ ```
31
+
32
+ The generator will install an initializer which describes all configuration options and creates a database migration.
33
+
34
+ If you want to tweak the database table name (to use something other than the default 'regions'), manually adjust both the configuration file and the migration now.
35
+
36
+ Then migrate the database:
37
+
38
+ ```ruby
39
+ rake db:migrate
40
+ ```
41
+
42
+ Add the following helper to your application layout in the `<head>..</head>` section. This will have the effect of loading the appropriate javascript & stylesheets only when in 'edit mode'.
43
+
44
+ ```ruby
45
+ effective_regions_include_tags
46
+ ```
47
+
48
+ Do not add anything to your asset pipeline javascripts or stylesheets.
49
+
50
+
51
+ ## Usage
52
+
53
+ ### Regions
54
+
55
+ Regions can be global, in which each is referenced by a unique name, or belong to a specific object.
56
+
57
+ If desired, permissions can be configured such that some users may edit global regions but not object regions or vice versa.
58
+
59
+ The regions can be created with or without default content. The default content is displayed only when no editable content has been entered.
60
+
61
+ The names for the regions are to be created on the fly, so you can just make up new names as you go along.
62
+
63
+ It's super easy to add an effective_region into any regular view, anywhere you want a dynamic content area.
64
+
65
+ The following is an example of a global region:
66
+
67
+ ```ruby
68
+ %h2 This is a header
69
+ %p= effective_region :footer_left
70
+ ```
71
+
72
+ and another example of the same region with some default content:
73
+
74
+ ```ruby
75
+ %h2 This is a header
76
+ %p
77
+ = effective_region :footer_left do
78
+ %p Default content
79
+ %p to display when footer_left is empty
80
+ ```
81
+
82
+ Anywhere in your application, in any layout or view, refering to `:footer_left` will render the same piece of content.
83
+
84
+ Effective Regions can also belong to a specific object:
85
+
86
+ ```ruby
87
+ %h2= effective_region(@event, :title)
88
+
89
+ %p
90
+ = effective_region @event, :summary do
91
+ %p= truncate(@event.excerpt)
92
+ %small
93
+ created on
94
+ = @event.created_at
95
+
96
+ %p= effective_region(@event, :body)
97
+ ```
98
+
99
+ Here each `@event` will have a unique `:title`, `:summary` and `:body` regions.
100
+
101
+
102
+ ### Restricting Editable Content
103
+
104
+ Using a regular `effective_region` tells the full-screen editor that any kind of HTML content and all available Snippets are allowed.
105
+
106
+ This is not always desirable - sometimes you want to lock down the content available to a specific region.
107
+
108
+ To allow text-only entry with no HTML or snippets, use `simple_effective_region`:
109
+
110
+ ```haml
111
+ %h2
112
+ = simple_effective_region @event, :title do
113
+ Default Title
114
+ ```
115
+
116
+ The above example ensures that the full-screen editor will only accept a simple title. No HTML is allowed. No Snippets are allowed. No newlines or ENTER keypresses are allowed.
117
+
118
+ This gives the user full control of the content, and allows the design and presentation to remain entirely in the hands of the developer.
119
+
120
+ Similarly, you may want to allow only Snippets to be inserted into a specific region:
121
+
122
+ ```haml
123
+ %div
124
+ = snippet_effective_region :sidebar_mentions
125
+ ```
126
+
127
+ only one type of snippet to be allowed:
128
+
129
+ ```haml
130
+ %div
131
+ = snippet_effective_region(:sidebar_mentions, :snippets => [:mention])
132
+ ```
133
+
134
+ or allow full content entry, but only a subset of the available Snippets:
135
+
136
+ ```haml
137
+ %div
138
+ = effective_region(:sidebar_mentions, :snippets => [:mention])
139
+ ```
140
+
141
+ ### Before Save Callback
142
+
143
+ Sometimes you may want to programmatically massage the content being assigned from the editor.
144
+
145
+ One use case for this would be to replace a tweet `@someone` mention with a full url to the appropriate twitter page.
146
+
147
+ Found in the `config/initializers/effective_regions.rb` file, the `config.before_save_method` hook exists for just such a purpose.
148
+
149
+ This method is called when a User clicks the 'Save' button in the full screen editor.
150
+
151
+ It will be called once for each region immediately before the regions are saved to the database.
152
+
153
+ This is not an ActiveRecord `before_save` callback and there is no way to cancel the save.
154
+
155
+ This method is run on the `controller.view_context`, so you have access to all your regular view helpers as well as the `request` object.
156
+
157
+ The second argument, `parent`, will be the `Effective::Region`'s parent `regionable` object, or the symbol `:global`.
158
+
159
+ If you are gsub'ing the `region.content` String value or altering the `region.snippets` Hash values, those changes will not be immediately visible on the front-end.
160
+
161
+ If you need the User to immediately see these changes, have your Proc or function return the symbol `:refresh`.
162
+
163
+ Returning the symbol `:refresh` will instruct javascript to perform a full page refresh after the Save is complete.
164
+
165
+ Warning: Don't change the `region.title` value or the `region.regionable` parent object, as this will just orphan the region.
166
+
167
+ Use via Proc:
168
+
169
+ ```ruby
170
+ config.before_save_method = Proc.new do |region, parent|
171
+ region.content = region.content.gsub('force', 'horse') if region.title == 'body'
172
+ :refresh
173
+ end
174
+ ```
175
+
176
+ or to use via custom method:
177
+
178
+ ```ruby
179
+ config.before_save_method = :my_region_before_save_method
180
+ ```
181
+
182
+ And then in your application_controller.rb:
183
+
184
+ ```ruby
185
+ def my_region_before_save_method(region, parent)
186
+ if region.title == 'body' && request.fullpath == posts_path
187
+ region.content = region.content.gsub('force', 'horse')
188
+ :refresh
189
+ end
190
+ end
191
+ ```
192
+
193
+ or to disable completely:
194
+
195
+ ```ruby
196
+ config.before_save_method = false
197
+ ```
198
+
199
+
200
+ ## Authorization
201
+
202
+ All authorization checks are handled via the config.authorization_method found in the effective_regions.rb initializer.
203
+
204
+ It is intended for flow through to CanCan, but that is not required.
205
+
206
+ The authorization method can be defined as:
207
+
208
+ ```ruby
209
+ EffectiveRegions.setup do |config|
210
+ config.authorization_method = Proc.new { |controller, action, resource| can?(action, resource) }
211
+ end
212
+ ```
213
+
214
+ or as a method:
215
+
216
+ ```ruby
217
+ EffectiveRegions.setup do |config|
218
+ config.authorization_method = :authorize_effective_regions
219
+ end
220
+ ```
221
+
222
+ and then in your application_controller.rb:
223
+
224
+ ```ruby
225
+ def authorize_effective_regions(action, resource)
226
+ can?(action, resource)
227
+ end
228
+ ```
229
+
230
+ There are 3 different levels of permissions to be considered:
231
+
232
+ 1 - Can I use the editor at all?
233
+
234
+ can :edit, Effective::Region
235
+
236
+ 2 - Can I update the Effective::Region global regions?
237
+
238
+ can :update, Effective::Region
239
+
240
+ 3 - Can I update the individual objects which define `acts_as_regionable`
241
+
242
+ can :update, ActsAsRegionableObject # This would be your Event, Post, or Page, or whatever.
243
+
244
+ If the method or proc returns false (user is not authorized) an `Effective::AccessDenied` exception will be raised
245
+
246
+ You can rescue from this exception by adding the following to your application_controller.rb
247
+
248
+ ```ruby
249
+ rescue_from Effective::AccessDenied do |exception|
250
+ respond_to do |format|
251
+ format.html { render 'static_pages/access_denied', :status => 403 }
252
+ format.any { render :text => 'Access Denied', :status => 403 }
253
+ end
254
+ end
255
+ ```
256
+
257
+ ## Snippets
258
+
259
+ Snippets are intelligent pieces of content that can be dropped into an effective_region through the full-screen editor's 'Insert Snippet' dropdown.
260
+
261
+ They are based on [CKEditor Widgets](http://docs.ckeditor.com/#!/guide/dev_widgets) but override some of the Widget internals to instead use the server to render content, allowing us to render Rails objects based on the user selected options.
262
+
263
+ To implement a Snippet, you must write 3 files: a model, a view, and a javascript options file.
264
+
265
+
266
+ ## Simple Snippet Example
267
+
268
+ We are going to create a Snippet called `current_user_info`.
269
+
270
+ When the snippet is inserted, the user may choose whether the `.email`, `.first_name` or `.last_name` methods will be called on the `current_user` object.
271
+
272
+ These examples use HAML, but ERB or SLIM will work the same way.
273
+
274
+ ### The Model
275
+
276
+ A model that extends from `Effective::Snippets::Snippet`
277
+
278
+ Any snippet models defined in app/models/effective/snippets/*.rb will be automatically detected and usable.
279
+
280
+ The models here are not ActiveRecord objects, and instead rely on [virtus](https://github.com/solnic/virtus) for the `attribute` functionality.
281
+
282
+ Any number of configurable options can be specified, but in this example we only have one.
283
+
284
+ This model file is defined in app/models/effective/snippets/current_user_info.rb
285
+
286
+ ```ruby
287
+ module Effective
288
+ module Snippets
289
+ class CurrentUserInfo < Snippet
290
+ attribute :display_method, String
291
+ end
292
+ end
293
+ end
294
+ ```
295
+
296
+ ### The View
297
+
298
+ The view must be defined as a partial and should be placed in app/views/effective/snippets/_current_user_info.html.haml
299
+
300
+ ```haml
301
+ - if current_user.blank?
302
+ = 'Not logged in'
303
+
304
+ - elsif current_user_info.display_method == 'email'
305
+ = current_user.email
306
+
307
+ - elsif current_user_info.display_method == 'first_name'
308
+ = current_user.first_name
309
+
310
+ - elsif current_user_info.display_method == 'last_name'
311
+ = current_user.last_name
312
+ ```
313
+
314
+ Or for the meta-programmers, instead of the above, we could:
315
+
316
+ ```haml
317
+ = (current_user.send(current_user_info.display_method) rescue 'Not logged in')
318
+ ```
319
+
320
+ In the above example, `current_user_info` is the snippet object, and `current_user` is the (probably Devise) User object.
321
+
322
+
323
+ ### The Javascript Options File
324
+
325
+ This file defines the dialog that CKEditor will present when inserting a new Snippet.
326
+
327
+ This must follow the CKEditor Widget Dialog Window Definition specification, which you can learn more about at:
328
+
329
+ http://docs.ckeditor.com/#!/guide/widget_sdk_tutorial_2
330
+
331
+ http://docs.ckeditor.com/#!/api/CKEDITOR.dialog.definition
332
+
333
+ The javascript file must be placed in app/assets/javascripts/effective/snippets/current_user_info.js.coffee
334
+
335
+ ```Coffeescript
336
+ CKEDITOR.dialog.add 'current_user_info', (editor) -> # Must match the class name of the snippet
337
+ title: 'Current User Info',
338
+ minWidth: 200,
339
+ minHeight: 100,
340
+ contents: [
341
+ {
342
+ id: 'current_user_info', # Just an html id, doesn't really matter what is here
343
+ elements: [
344
+ { # elements Array should contain one Hash for each Snippet attribute
345
+ id: 'display_method',
346
+ type: 'select',
347
+ label: 'Current User Info',
348
+ items: [
349
+ ['E-mail', 'email'],
350
+ ['First Name', 'first_name'],
351
+ ['Last Name', 'last_name']
352
+ ],
353
+ setup: (widget) -> this.setValue(widget.data.display_method),
354
+ commit: (widget) -> widget.setData('display_method', this.getValue())
355
+ }
356
+ ]
357
+ }
358
+ ]
359
+ ```
360
+
361
+ Please note, this file should not be included into the asset pipeline. It's a standalone javascript file that is read (just once, and then cached) by CKEditor when the Insert Snippet is triggered.
362
+
363
+ You may be thinking that this file won't be available due to asset digesting, but there is a custom `assets:precompile` enhancement task in the [effective_ckeditor](https://github.com/code-and-effect/effective_ckeditor) gem (a dependency of this gem) that ensures these snippet options files are available at the non-digested file path. This just works and is not something you need to worry about.
364
+
365
+ ### Summary
366
+
367
+ We have created a simple Snippet to display the current_user's email, first_name or last_name.
368
+
369
+ When any logged in user visits this page, their specific instance of current_user will be called, and they will see their own email, first or last name.
370
+
371
+ Once the Snippet is inserted, the user editting the page can double-click the Snippet and set the display_method to something else.
372
+
373
+
374
+ ## Advanced Snippet Example
375
+
376
+ The above, simple, example works great because `current_user` is something always available to the application.
377
+
378
+ In this next example we are going to create a Snippet to insert a summary and link to a `Post` which is created using the standard Rails CRUD workflow.
379
+
380
+ We must use an AJAX request to query all current Posts, rather than just the ones available at compile/deploy time.
381
+
382
+ ### The Model
383
+
384
+ This snippet model is defined in app/models/effective/snippets/post.rb
385
+
386
+ ```ruby
387
+ module Effective
388
+ module Snippets
389
+ class Post < Snippet
390
+ attribute :post_id, Integer
391
+
392
+ def post_object
393
+ # We're using ::Post to refer to the app/models/post.rb rather than the Effective::Snippets::Post
394
+ @post ||= ::Post.find_by_id(post_id)
395
+ end
396
+
397
+ end
398
+ end
399
+ end
400
+ ```
401
+
402
+ ### The View
403
+
404
+ This view partial is defined in app/views/effective/snippets/_post.html.haml
405
+
406
+ Some advanced snippet partials work best with CKEditor when you can start them with a parent div. This one isn't advanced enough to actually matter.
407
+
408
+ ```haml
409
+ .post
410
+ %h3= post.post_object.title
411
+ %small
412
+ This is post number
413
+ = post.post_object.id
414
+ created on
415
+ = post.created_at
416
+
417
+ %p= post.post_object.summary
418
+ ```
419
+
420
+ ### The Javascript Options File
421
+
422
+ The javascript file should be placed in app/assets/javascripts/effective/snippets/post.js.coffee
423
+
424
+ ```Coffeescript
425
+ getPosts = ->
426
+ posts = []
427
+
428
+ $.ajax
429
+ url: '/effective/snippets/posts'
430
+ type: 'GET'
431
+ dataType: 'json'
432
+ async: false
433
+ complete: (data) -> posts = data.responseJSON
434
+
435
+ posts
436
+
437
+ CKEDITOR.dialog.add 'post', (editor) ->
438
+ title: 'Post'
439
+ minWidth: 200,
440
+ minHeight: 100,
441
+ contents: [
442
+ {
443
+ id: 'post',
444
+ elements: [
445
+ {
446
+ id: 'post_id',
447
+ type: 'select',
448
+ label: 'Post',
449
+ items: getPosts(), # This only runs once, when the Dialog is created.
450
+ setup: (widget) -> this.setValue(widget.data.post_id)
451
+ commit: (widget) -> widget.setData('post_id', this.getValue())
452
+ }
453
+ ]
454
+ }
455
+ ]
456
+ ```
457
+
458
+ So when the Snippet dialog for an 'Insert Snippet' -> Post is opened, an AJAX request to the server is made, and the list of Posts is read.
459
+
460
+ ### The Controller
461
+
462
+ This controller is in no way part of the effective_regions/effective_ckeditor magic. It's just a one-off controller action.
463
+
464
+ For consistency with the other file paths (which do matter), I have namespaced the action under effective/snippets/, but this could be any valid rails route.
465
+
466
+ This controller is defined in app/controllers/effective/snippets/posts_controller.rb
467
+
468
+ ```ruby
469
+ module Effective
470
+ module Snippets
471
+ class PostsController < ApplicationController
472
+ respond_to :json
473
+
474
+ def index
475
+ authorize! :index, Post # CanCan authorization here
476
+
477
+ @posts = Post.order(:title).map { |post| [post.title, post.id] }.to_json
478
+
479
+ respond_with @posts
480
+ end
481
+ end
482
+ end
483
+ end
484
+ ```
485
+
486
+ and then in your routes.rb:
487
+
488
+ ```ruby
489
+ get '/effective/snippets/posts', :to => 'effective/snippets/posts#index'
490
+ ```
491
+
492
+ ### Default Content
493
+
494
+ We can pre-populate an effective_region's default content with some posts. These posts will be displayed until a user edits that region and selects some specific posts.
495
+
496
+ ```haml
497
+ %h2 Posts
498
+
499
+ = snippet_effective_region :sidebar_posts, :snippets => [:post] do
500
+ - Post.order(:created_at).first(5).each do |post|
501
+ = render_snippet Effective::Snippets::Post.new(:post_id => post.id)
502
+ ```
503
+
504
+
505
+ ### Summary
506
+
507
+ This Snippet makes an AJAX request to the server and receives a JSON response containing all the Posts. The Posts are displayed in a select drop-down, and when one is chosen, inserted into the given region.
508
+
509
+
510
+ ## Templates
511
+
512
+ Templates are small pieces of reusable HTML that can be inserted into an `effective_region` with just one or two clicks.
513
+
514
+ Unlike snippets, there are no configurable options or anything. They're just pieces of raw HTML that can be dropped in and then immediately editted.
515
+
516
+ While handy, they were implemented as a bit of an after-thought, and will probably be refactored in future versions of effective_regions.
517
+
518
+ They take the form of two files, a model and a view.
519
+
520
+ ### The Model
521
+
522
+ A model extends from `Effective::Templates::Template`
523
+
524
+ Any template models defined in app/models/effective/templates/*.rb will be automatically detected and usable.
525
+
526
+ The model here is very minimalistic. It's basically just to inform the full-screen editor that a View with the same name exists.
527
+
528
+ This model is defined at app/models/effective/templates/two_column.rb
529
+
530
+ ```ruby
531
+ module Effective
532
+ module Templates
533
+ class TwoColumn < Template
534
+ def description
535
+ 'Two Column Area'
536
+ end
537
+ end
538
+ end
539
+ end
540
+ ```
541
+
542
+ ### The View
543
+
544
+ The view is defined at app/models/effective/templates/_two_column.html.haml
545
+
546
+ ```haml
547
+ .row
548
+ .col-sm-6
549
+ %p Left Column
550
+ .col-sm-6
551
+ %p Right column
552
+ ```
553
+
554
+ ## License
555
+
556
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
557
+
558
+ Code and Effect is the product arm of [AgileStyle](http://www.agilestyle.com/), an Edmonton-based shop that specializes in building custom web applications with Ruby on Rails.
559
+
560
+
561
+ ## Testing
562
+
563
+ The test suite for this gem is unfortunately not yet complete.
564
+
565
+ Run tests by:
566
+
567
+ ```ruby
568
+ rake spec
569
+ ```
570
+
571
+
572
+ ## Contributing
573
+
574
+ 1. Fork it
575
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
576
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
577
+ 4. Push to the branch (`git push origin my-new-feature`)
578
+ 5. Bonus points for test coverage
579
+ 6. Create new Pull Request
580
+