effective_regions 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+