regulate 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile.lock +6 -1
  3. data/LICENSE +1 -1
  4. data/README.md +78 -2
  5. data/app/controllers/regulate/admin/pages_controller.rb +23 -0
  6. data/app/views/regulate/admin/pages/_form.html.erb +11 -4
  7. data/app/views/regulate/admin/pages/index.html.erb +11 -2
  8. data/lib/generators/regulate/{install_generator.rb → mount_up_generator.rb} +2 -17
  9. data/lib/generators/regulate/{views_generator.rb → strap_generator.rb} +16 -7
  10. data/lib/generators/templates/regulate.rb +19 -0
  11. data/lib/generators/templates/regulate.yml +2 -2
  12. data/lib/generators/templates/regulate_admin.js +167 -0
  13. data/lib/regulate.rb +7 -0
  14. data/lib/regulate/engine.rb +2 -2
  15. data/lib/regulate/git.rb +0 -3
  16. data/lib/regulate/git/interface.rb +1 -1
  17. data/lib/regulate/git/model/base.rb +54 -29
  18. data/lib/regulate/version.rb +1 -1
  19. data/public/javascripts/regulate_admin.js +1 -1
  20. data/public/javascripts/regulate_index.js +32 -0
  21. data/regulate.gemspec +1 -0
  22. data/test/dummy/app/controllers/application_controller.rb +6 -0
  23. data/test/dummy/app/models/user.rb +20 -0
  24. data/test/dummy/config/initializers/regulate.rb +14 -0
  25. data/test/generators/mount_up_generator_test.rb +15 -0
  26. data/test/generators/strap_generator_test.rb +22 -0
  27. data/test/models/regulate_git_model_base_test.rb +176 -7
  28. data/test/{git_test.rb → regulate_git_interface_test.rb} +2 -2
  29. metadata +44 -28
  30. data/lib/generators/templates/regulate.css +0 -2
  31. data/lib/generators/templates/regulate.js +0 -44
  32. data/test/tmp/config/initializers/regulate.rb +0 -12
  33. data/test/tmp/config/regulate.yml +0 -10
  34. data/test/tmp/public/javascripts/regulate.js +0 -49
  35. data/test/tmp/public/stylesheets/regulate.css +0 -3
data/.gitignore CHANGED
@@ -11,4 +11,5 @@ doc
11
11
  docs
12
12
  .yardoc
13
13
  test/dummy/tmp/**/*
14
+ test/tmp/**/*
14
15
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- regulate (0.0.1)
4
+ regulate (0.1.0)
5
+ abstract_auth (~> 0.1.0)
5
6
  grit (~> 2.3.0)
6
7
  rails (~> 3.0.0)
7
8
 
@@ -9,6 +10,8 @@ GEM
9
10
  remote: http://rubygems.org/
10
11
  specs:
11
12
  abstract (1.0.0)
13
+ abstract_auth (0.1.0)
14
+ module_ext (~> 0.1.0)
12
15
  actionmailer (3.0.3)
13
16
  actionpack (= 3.0.3)
14
17
  mail (~> 2.2.9)
@@ -67,6 +70,7 @@ GEM
67
70
  mime-types (~> 1.16)
68
71
  treetop (~> 1.4.8)
69
72
  mime-types (1.16)
73
+ module_ext (0.1.0)
70
74
  nokogiri (1.4.4)
71
75
  polyglot (0.3.1)
72
76
  rack (1.2.1)
@@ -107,6 +111,7 @@ PLATFORMS
107
111
  ruby
108
112
 
109
113
  DEPENDENCIES
114
+ abstract_auth (~> 0.1.0)
110
115
  bluecloth (~> 2.0.9)
111
116
  bundler (~> 1.0.0)
112
117
  capybara (~> 0.4.0)
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Quick Left, Inc.
1
+ Copyright (c) 2011 Quick Left, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,3 +1,79 @@
1
- #Regulate
1
+ Regulate
2
+ ===========
3
+ Rails 3 engine that provides a Git backed CMS that allows for an admin to define editable regions in a page view.
4
+
5
+
6
+ ### Why
7
+ We often have applications that need fairly basic CMS solutions but the
8
+ client may not have someone on staff that knows HTML. We needed a CMS
9
+ that wasn't bloated, lived as an engine, allowed us to regulate what
10
+ parts of a page could be editable, and we wanted it to be Git backed so
11
+ we could have version control over the edits. We also designed regulate
12
+ to work with any authentication / authorization service.
13
+
14
+ ## How it works
15
+ Video coming soon!
16
+
17
+ ### Quick Start
18
+
19
+ Add regulate to your Rails 3 Gemfile
20
+
21
+ gem "regulate"
22
+
23
+ bundle install
24
+
25
+ Install the initializer
26
+
27
+ rails g regulate:mount_up
28
+
29
+ You MUST edit the initializer located in `config/initializers/regulate.rb`. Please read the comments located in the regulate initializer for details on configuring regulate to work with your authentication and authorization systems and for defining custom routes. NOTHING works until you do this!
30
+
31
+
32
+ ### Options
33
+
34
+ More than likely you will want to customize the views in regulate. To copy the view files, javascript in the admin and the git repo config yaml file simply run...
35
+
36
+ rails g regulate:strap
37
+
38
+ The `strap` generator will copy the following files into your Rails application like so.
39
+
40
+ create app/views/regulate
41
+ create app/views/regulate/admin/pages/_form.html.haml
42
+ create app/views/regulate/admin/pages/edit.html.haml
43
+ create app/views/regulate/admin/pages/index.html.haml
44
+ create app/views/regulate/admin/pages/new.html.haml
45
+ create app/views/regulate/pages/show.html.haml
46
+ create public/javascripts/regulate_admin.js
47
+ create config/regulate.yml
48
+
49
+
50
+
51
+ ### Issues
52
+
53
+ Please report issues to the [Regulate issue tracker](http://github.com/quickleft/regulate/issues/).
54
+
55
+
56
+ ### Patches/Pull Requests
57
+
58
+ * Fork it.
59
+ * Add tests for it.
60
+ * Make your changes.
61
+ * Commit.
62
+ * Send a pull request.
63
+
64
+
65
+ ### Thanks
66
+
67
+ Thanks to mojombo for writing grit and gollum. We use grit to talk to Git and reading the source of gollum was a major help. Thanks to brennandunn for inspiring
68
+ us with rack-cms. Thanks to josevalim for writing devise, it is an excellent example of a Rails 3 engine. Thanks to Nate Dogg and Warren G for insipiring the name.
69
+
70
+
71
+ ### Copyright
72
+
73
+ Copyright (c) 2011 Quick Left. We regulate any stealing of his property (actually we don't it's MIT). See LICENSE for details.
74
+
75
+ Regulators we regulate any stealing of his property and we damn good too But you can't be any geek off the street, gotta be handy with the steel if you know what I mean, earn your keep!
76
+
77
+
78
+ REGULATORS!!! MOUNT UP!
2
79
 
3
- The CMS we always wanted.
@@ -5,6 +5,12 @@ module Regulate
5
5
 
6
6
  # Standard CRUD Controller
7
7
  class PagesController < ActionController::Base
8
+ # Check that a user is authenticated
9
+ before_filter :is_authorized?
10
+ # Check that the user is an admin
11
+ before_filter :is_admin?, :only => [:new, :create, :destroy]
12
+ # Check that the user is at least an editor
13
+ before_filter :is_editor?, :not => [:new, :create, :destroy]
8
14
  # Load in our page object based on the ID
9
15
  before_filter :load_page, :only => [:edit,:update,:destroy]
10
16
 
@@ -23,6 +29,7 @@ module Regulate
23
29
 
24
30
  # PUT method to persist changes to a Page object
25
31
  def update
32
+ params[:page].delete(:view) if !@is_admin
26
33
  if @page.update_attributes(params[:page])
27
34
  flash[:notice] = "Successfully updated #{params[:page][:title]}"
28
35
  redirect_to regulate_admin_regulate_pages_path
@@ -56,6 +63,22 @@ module Regulate
56
63
 
57
64
  private
58
65
 
66
+ def is_authorized?
67
+ @authorized_user = AbstractAuth.invoke(:authorized_user)
68
+ @is_admin = AbstractAuth.invoke(:is_admin)
69
+ # Uncomment the following line to test out admin interface
70
+ #@is_admin = true
71
+ @is_editor = AbstractAuth.invoke(:is_editor)
72
+ end
73
+
74
+ def is_editor?
75
+ redirect_to root_path if !@is_editor
76
+ end
77
+
78
+ def is_admin?
79
+ redirect_to regulate_admin_regulate_pages_path if !@is_admin
80
+ end
81
+
59
82
  # Grab a page resource based on the ID passed to the URI
60
83
  def load_page
61
84
  @page = Regulate::Page.find(params[:id])
@@ -20,10 +20,17 @@ textarea {height:250px;}
20
20
  Title: <%= @page.title %>
21
21
  </div>
22
22
  <% end %>
23
- <div class="form_row">
24
- <%= f.label :view %>
25
- <%= f.text_area :view %>
26
- </div>
23
+ <% if @is_admin %>
24
+ <div class="form_row">
25
+ <%= f.label :view %>
26
+ <%= f.text_area :view %>
27
+ </div>
28
+ <% else %>
29
+ <div class="form_row">
30
+ View:<br /> <%= @page.view %>
31
+ <%= f.hidden_field :view %>
32
+ </div>
33
+ <% end %>
27
34
  <div id="edit_regions"></div>
28
35
  <div class="form_row">
29
36
  <%= f.submit "Save" %>
@@ -1,10 +1,19 @@
1
1
  <h2>Pages</h2>
2
- <%= link_to "New Page", new_regulate_admin_regulate_page_path %>
2
+ <% if @is_admin %>
3
+ <%= link_to "New Page", new_regulate_admin_regulate_page_path %>
4
+ <% end %>
3
5
  <ul>
4
6
  <% @pages.each do |page| %>
5
7
  <li>
6
8
  <%= page.title %>
7
- <%= link_to "Edit" , edit_regulate_admin_regulate_page_path(page.id) %>
9
+ <%= link_to "Edit" , edit_regulate_admin_regulate_page_path(page) %>
10
+ <% if @is_admin %>
11
+ <%= link_to "Delete" , regulate_admin_regulate_page_path(page), :confirm => 'Are you sure?', :method => :delete, :class => 'delete_link' %>
12
+ <% end %>
8
13
  </li>
9
14
  <% end %>
10
15
  </ul>
16
+ <% if @is_admin %>
17
+ <%= javascript_include_tag "jquery.min.js" %>
18
+ <%= javascript_include_tag "regulate_index.js" %>
19
+ <% end %>
@@ -4,27 +4,12 @@ module Regulate
4
4
  module Generators
5
5
 
6
6
  # Standard install that lets a user copy files over so they can override some functionality
7
- class InstallGenerator < Rails::Generators::Base
7
+ class MountUpGenerator < Rails::Generators::Base
8
8
 
9
9
  # Tell Rails where to find our templates
10
10
  source_root File.expand_path("../../templates", __FILE__)
11
11
 
12
- desc "Copies files to your application that regulate needs."
13
-
14
- # Copy over our CSS
15
- def copy_css
16
- template "regulate.css", "public/stylesheets/regulate.css"
17
- end
18
-
19
- # Copy over our JS
20
- def copy_js
21
- template "regulate.js", "public/javascripts/regulate.js"
22
- end
23
-
24
- # Copy over the repo config YAML file
25
- def copy_yml
26
- template "regulate.yml", "config/regulate.yml"
27
- end
12
+ desc "Copies the regulate initializer because you can't be any geek off the street."
28
13
 
29
14
  # Copy over the Regulate initializer
30
15
  def copy_initializer
@@ -3,12 +3,11 @@ module Regulate
3
3
  module Generators
4
4
 
5
5
  # Class lifted from Devise. Thanks josevalim!
6
- class ViewsGenerator < Rails::Generators::Base
6
+ class StrapGenerator < Rails::Generators::Base
7
7
 
8
8
  # Tell Rails where our view files are
9
- source_root File.expand_path("../../../../app/views", __FILE__)
10
- desc "Copies all Regulate views to your application in app/views/pages."
11
-
9
+ source_root File.expand_path("../../../..", __FILE__)
10
+ desc "Copies all Regulate views, js, and yaml files to your application in app/views/regulate/."
12
11
  class_option :template_engine, :type => :string, :aliases => "-t",
13
12
  :desc => "Template engine for the views. Available options are 'erb' and 'haml'."
14
13
 
@@ -20,10 +19,20 @@ module Regulate
20
19
  verify_haml_version
21
20
  create_and_copy_haml_views
22
21
  else
23
- directory "pages", "app/views/pages"
22
+ directory "app/views/regulate", "app/views/regulate"
24
23
  end
25
24
  end
26
25
 
26
+ # Copy over our JS
27
+ def copy_js
28
+ template "public/javascripts/regulate_admin.js", "public/javascripts/regulate_admin.js"
29
+ end
30
+
31
+ # Copy over the repo config YAML file
32
+ def copy_yml
33
+ template "config/regulate.yml", "config/regulate.yml"
34
+ end
35
+
27
36
  protected
28
37
 
29
38
  # Check whether the current environment has HAML
@@ -47,7 +56,7 @@ module Regulate
47
56
  # Copy over the HAML files
48
57
  def create_and_copy_haml_views
49
58
  require 'tmpdir'
50
- html_root = "#{self.class.source_root}/pages"
59
+ html_root = "#{self.class.source_root}/app/views/regulate"
51
60
 
52
61
  Dir.mktmpdir("regulate-haml.") do |haml_root|
53
62
  Dir["#{html_root}/**/*"].each do |path|
@@ -61,7 +70,7 @@ module Regulate
61
70
  end
62
71
  end
63
72
 
64
- directory haml_root, "app/views/pages"
73
+ directory haml_root, "app/views/regulate"
65
74
  end
66
75
  end
67
76
 
@@ -11,3 +11,22 @@ Regulate.setup do |config|
11
11
 
12
12
  end
13
13
 
14
+ # AbstractAuth Implementations
15
+ # You NEED to set these methods appropriately for Regulate to function
16
+ # This method should return a the currently authenticated resource
17
+ #AbstractAuth.implement :authorized_user do
18
+ #::ApplicationController.send('current_user')
19
+ #end
20
+ # This method should return whether the currently authenticated resource is an admin or not
21
+ # Admins are able to edit the view field on a CMS page and define the editable regions
22
+ # If you want any authenticated user to be able to have this ability, simply return true in your implementation
23
+ #AbstractAuth.implement :is_admin do
24
+ #::ApplicationController.send('current_user').is_admin?
25
+ #end
26
+ # This method should return whether the currently authenticated resource is an admin or not
27
+ # Editors only have the ability to change the content of editable regions on the page
28
+ # If you want any authenticated user to be able to have this ability, simply return true in your implementation
29
+ #AbstractAuth.implement :is_editor do
30
+ #::ApplicationController.send('current_user').is_editor?
31
+ #end
32
+
@@ -1,10 +1,10 @@
1
1
  development:
2
2
  repo: db/repos/development.git
3
3
  test: &test
4
- repo: db/repos/test.git
4
+ repo: db/repos/test.git
5
5
  production:
6
6
  repo: db/repos/production.git
7
7
  daily:
8
8
  repo: db/repos/daily.git
9
9
  cucumber:
10
- <<: *test
10
+ <<: *test
@@ -0,0 +1,167 @@
1
+ (function( $ ) {
2
+
3
+ var timer,
4
+ stored_keys = null,
5
+
6
+ Regulate = {
7
+
8
+ longPoll: function longPoll( force, callback ){
9
+ var i, text = $('#page_view').val(),
10
+
11
+ // This RegEx will catch anything inside the `{{ }}`
12
+ m = /\{\{(\w|\s)*\}\}/g,
13
+ // A more generic mustache `{ }` grab
14
+ clean_key = /[\{,\}]/g,
15
+
16
+ keys = [],
17
+ invalid_keys = [],
18
+ deleted_keys = [],
19
+
20
+ // To import data, we've attached an object to `window.existing_custom_fields`
21
+ existing_keys = existing_custom_fields || {},
22
+
23
+ // Define a blacklist of keys we know we'll never want
24
+ blacklist = ['{{view}}', '{{title}}'],
25
+
26
+ // This assigns our initial match of all keys in the textarea
27
+ initial_match = text.match( m ) || [],
28
+
29
+ // This allows to skip the early return
30
+ f = force || false;
31
+
32
+ // Prevent this core of this from running while someone's typing
33
+ if( $('#page_view').hasClass('active') && f === false) {
34
+ // Set the timer agains and return early
35
+ timer = setTimeout( longPoll , 1e3);
36
+ return false;
37
+ }
38
+
39
+ // Loop through the keys that our regex grabbed, and determine if they're ok to use
40
+ $.each( initial_match, function( i, v ) {
41
+ var invalid = false;
42
+
43
+ // Keys can't be in the blacklist, also don't double up the errors
44
+ if( ~blacklist.indexOf( v ) && !~invalid_keys.indexOf( v ) ) {
45
+ invalid = true;
46
+ invalid_keys.push({ 'key':v, 'msg': 'The key '+v+' is reserved.' });
47
+ }
48
+
49
+ // Keys can't contain spaces
50
+ if( /\s/.test(v) && !~invalid_keys.indexOf( v ) ) {
51
+ invalid = true;
52
+ invalid_keys.push({ 'key':v, 'msg': 'The key '+v+' contains a space. It can\'t do that.' });
53
+ }
54
+
55
+ // Keys can't start with a number
56
+ if( /\{\{\d/.test(v) && !~invalid_keys.indexOf( v ) ) {
57
+ invalid = true;
58
+ invalid_keys.push({ 'key':v, 'msg': 'The key '+v+' starts with a number. It can\'t do that.' });
59
+ }
60
+
61
+ // If we're still valid after this, add the key into the keys array
62
+ if( invalid === false ) {
63
+ keys.push( v );
64
+ }
65
+ });
66
+
67
+ // Loops through the stored keys and checks for any deleted keys
68
+ // stored_key[] - keys[] == any deleted keys
69
+ if( stored_keys !== null ) {
70
+ i = stored_keys.length;
71
+ while(i--){
72
+ if( ! ~keys.indexOf( stored_keys[i] ) ) {
73
+ deleted_keys.push( stored_keys[i] );
74
+ // Remove the label and text area within #edit_regions
75
+ $('#edit_regions .'+ stored_keys[i].replace( clean_key, "") ).remove();
76
+ }
77
+ }
78
+ }
79
+
80
+ // Loop through the valid keys and add labels and textareas for them
81
+ $.each( keys, function( i, v ) {
82
+ var new_key = v.replace( clean_key, ""),
83
+
84
+ textarea = $("<textarea>", {
85
+ 'name': 'page[edit_regions][' + new_key + ']',
86
+ 'class': new_key,
87
+ 'value': existing_keys[new_key] || 'Default Text for '+v
88
+ }),
89
+
90
+ label = $("<label>", {
91
+ 'for': 'page[edit_regions][' + new_key + ']',
92
+ 'text': new_key.replace('_', " "),
93
+ 'class': new_key
94
+ });
95
+
96
+ if( $('textarea').hasClass( new_key )) {
97
+ return;
98
+ }
99
+
100
+ // Add our textarea to the end of #edit_regions, and inset it's label before it
101
+ textarea.appendTo('#edit_regions').before(label);
102
+ });
103
+
104
+ // Make any old errors go away
105
+ $('#custom_field_errors').empty();
106
+
107
+ // Add error messages for an invalid keys
108
+ $.each( invalid_keys, function( i, v ) {
109
+ if( v.key !== "{{title}}" ) {
110
+ $('#custom_field_errors').append(v.msg+"</br>");
111
+ }
112
+ });
113
+
114
+ // Globally persist our keys for the next pass
115
+ stored_keys = keys;
116
+
117
+ // Set a 1 second timer
118
+ timer = setTimeout( longPoll , 1e3);
119
+
120
+ if( $.isFunction( callback ) ){
121
+ callback();
122
+ }
123
+ },
124
+
125
+ init: function(){
126
+ var buffer;
127
+
128
+ // Start the party with checking the form's inital value
129
+ Regulate.longPoll();
130
+
131
+ // Forces the longPoll to run one more time before you submit the form
132
+ $('form').bind('submit', function( e ){
133
+ e.preventDefault();
134
+
135
+ var f = e.target;
136
+
137
+ clearTimeout( timer );
138
+
139
+ Regulate.longPoll( null, function(){
140
+ $(f).unbind('submit').submit();
141
+ });
142
+
143
+ });
144
+
145
+ // Runs the longPoll if `}}` is entered into the form
146
+ $(window).bind('keyup', function(e) {
147
+ if( e.keyCode == 221 && buffer == 221 ) {
148
+ Regulate.longPoll( true );
149
+ }
150
+
151
+ buffer = e.keyCode;
152
+ });
153
+
154
+ // Prevents the longPoll from running if you're focused on the textarea
155
+ $('#page_view').bind('focus blur', function(e){
156
+ $(this).toggleClass('active');
157
+ });
158
+ }
159
+
160
+ };
161
+
162
+ $( Regulate.init );
163
+
164
+ window.Regulate = Regulate;
165
+
166
+ })(jQuery);
167
+