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,23 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ # Our tasks
9
+ load 'lib/tasks/effective_regions_tasks.rake'
10
+
11
+ # Testing tasks
12
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
13
+ load 'rails/tasks/engine.rake'
14
+
15
+ Bundler::GemHelper.install_tasks
16
+
17
+ require 'rspec/core'
18
+ require 'rspec/core/rake_task'
19
+
20
+ desc "Run all specs in spec directory (excluding plugin specs)"
21
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
22
+
23
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ CKEDITOR.dialog.add 'current_user_info', (editor) ->
2
+ title: 'Current User info',
3
+ minWidth: 200,
4
+ minHeight: 100,
5
+ contents: [
6
+ {
7
+ id: 'current_user_info',
8
+ elements: [
9
+ {
10
+ id: 'method',
11
+ type: 'select',
12
+ label: 'Current User Info',
13
+ items: [
14
+ ['E-mail', 'email'],
15
+ ['Full Name', 'full_name'],
16
+ ['First Name', 'first_name'],
17
+ ['Last Name', 'last_name']
18
+ ],
19
+ setup: (widget) -> this.setValue(widget.data.method),
20
+ commit: (widget) -> widget.setData('method', this.getValue())
21
+ }
22
+ ]
23
+ }
24
+ ]
@@ -0,0 +1,154 @@
1
+ module Effective
2
+ class RegionsController < ApplicationController
3
+ respond_to :html, :json
4
+ layout false
5
+
6
+ before_filter :authenticate_user! if defined?(Devise)
7
+ skip_log_page_views :quiet => true, :only => [:snippet, :snippets, :templates] if defined?(EffectiveLogging)
8
+
9
+ skip_before_filter :verify_authenticity_token, :only => [:update]
10
+
11
+ def edit
12
+ EffectiveRegions.authorized?(self, :edit, Effective::Region.new())
13
+
14
+ cookies['effective_regions_editting'] = {:value => request.referrer, :path => '/'}
15
+
16
+ # TODO: turn this into a cookie or something better.
17
+ redirect_to request.url.gsub('/edit', '') + '?edit=true'
18
+ end
19
+
20
+ def update
21
+ javascript_should_refresh_page = ''
22
+
23
+ Effective::Region.transaction do
24
+ (request.fullpath.slice!(0..4) rescue nil) if request.fullpath.to_s.starts_with?('/edit') # This is so the before_save_method can reference the real current page
25
+
26
+ region_params.each do |key, vals| # article_section_2_title => {:content => '<p></p>'}
27
+ to_save = nil # Which object, the regionable, or the region (if global) to save
28
+
29
+ regionable, title = find_regionable(key)
30
+
31
+ if regionable
32
+ EffectiveRegions.authorized?(self, :update, regionable) # can I update the regionable object?
33
+
34
+ region = regionable.regions.find { |region| region.title == title }
35
+ region ||= regionable.regions.build(:title => title)
36
+
37
+ to_save = regionable
38
+ else
39
+ region = Effective::Region.global.where(:title => title).first_or_initialize
40
+ EffectiveRegions.authorized?(self, :update, region) # can I update the global region?
41
+
42
+ to_save = region
43
+ end
44
+
45
+ region.content = cleanup(vals[:content])
46
+
47
+ region.snippets = HashWithIndifferentAccess.new()
48
+ (vals[:snippets] || []).each { |snippet, vals| region.snippets[snippet] = vals }
49
+
50
+ # Last chance for a developer to make some changes here
51
+ if (run_before_save_method(region, regionable) rescue nil) == :refresh
52
+ javascript_should_refresh_page = 'refresh'
53
+ end
54
+
55
+ to_save.save!
56
+ end
57
+
58
+ render :text => javascript_should_refresh_page, :status => 200
59
+ return
60
+ end
61
+
62
+ render :text => 'error', :status => :unprocessable_entity
63
+ end
64
+
65
+ def snippets
66
+ EffectiveRegions.authorized?(self, :edit, Effective::Region.new())
67
+
68
+ retval = {}
69
+ EffectiveRegions.snippets.each do |snippet|
70
+ retval[snippet.class_name] = {
71
+ :dialog_url => snippet.snippet_dialog_url,
72
+ :label => snippet.snippet_label,
73
+ :description => snippet.snippet_description,
74
+ :inline => snippet.snippet_inline,
75
+ :editables => snippet.snippet_editables,
76
+ :tag => snippet.snippet_tag.to_s
77
+ #:template => ActionView::Base.new(ActionController::Base.view_paths, {}, ActionController::Base.new).render(:partial => snippet.to_partial_path, :object => snippet, :locals => {:snippet => snippet})
78
+ }
79
+ end
80
+
81
+ render :json => retval
82
+ end
83
+
84
+ def snippet # This is a GET. CKEDITOR passes us data, we need to render the non-editable content
85
+ klass = "Effective::Snippets::#{region_params[:name].try(:classify)}".safe_constantize
86
+
87
+ if klass.present?
88
+ @snippet = klass.new(region_params[:data])
89
+ render :partial => @snippet.to_partial_path, :object => @snippet, :locals => {:snippet => @snippet, :snippet_preview => true}
90
+ else
91
+ render :text => "Missing class Effective::Snippets::#{region_params[:name].try(:classify)}"
92
+ end
93
+ end
94
+
95
+ def templates
96
+ EffectiveRegions.authorized?(self, :edit, Effective::Region.new())
97
+
98
+ retval = EffectiveRegions.templates.map do |template|
99
+ {
100
+ :title => template.title,
101
+ :description => template.description,
102
+ :image => template.image || "#{template.class_name}.png",
103
+ :html => render_to_string(:partial => template.to_partial_path, :object => template, :locals => {:template => template})
104
+ }
105
+ end
106
+
107
+ render :json => retval
108
+ end
109
+
110
+ protected
111
+
112
+ def find_regionable(key)
113
+ regionable = nil
114
+ title = nil
115
+
116
+ if(class_name, id, title = key.scan(/(\w+)_(\d+)_(\w+)/).flatten).present?
117
+ regionable = (class_name.classify.safe_constantize).find(id) rescue nil
118
+ end
119
+
120
+ return regionable, (title || key)
121
+ end
122
+
123
+ def cleanup(str)
124
+ (str || '').tap do |str|
125
+ str.chomp!('<p>&nbsp;</p>') # Remove any trailing empty <p>'s
126
+ str.gsub!("\n", '')
127
+ str.strip!
128
+ end
129
+ end
130
+
131
+ def region_params
132
+ begin
133
+ params.require(:effective_regions).permit!
134
+ rescue => e
135
+ params[:effective_regions]
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def run_before_save_method(region, regionable)
142
+ return nil if region == nil
143
+
144
+ if EffectiveRegions.before_save_method.respond_to?(:call)
145
+ view_context.instance_exec(region, (regionable || :global), &EffectiveRegions.before_save_method)
146
+ elsif EffectiveRegions.before_save_method.kind_of?(Symbol)
147
+ self.instance_exec(self, region, (regionable || :global), &EffectiveRegions.before_save_method)
148
+ end
149
+
150
+ end
151
+
152
+ end
153
+ end
154
+
@@ -0,0 +1,108 @@
1
+ module EffectiveRegionsHelper
2
+ def effective_region(*args)
3
+ options = args.extract_options!
4
+ block_given? ? ckeditor_region(args, options) { yield } : ckeditor_region(args, options)
5
+ end
6
+
7
+ def simple_effective_region(*args)
8
+ (options = args.extract_options!).merge!(:type => :simple)
9
+ block_given? ? ckeditor_region(args, options) { yield } : ckeditor_region(args, options)
10
+ end
11
+
12
+ def snippet_effective_region(*args)
13
+ (options = args.extract_options!).merge!(:type => :snippets)
14
+ block_given? ? ckeditor_region(args, options) { yield } : ckeditor_region(args, options)
15
+ end
16
+
17
+ def wrapped_snippet_effective_region(*args)
18
+ (options = args.extract_options!).merge!(:type => :wrapped_snippets)
19
+ block_given? ? ckeditor_region(args, options) { yield } : ckeditor_region(args, options)
20
+ end
21
+
22
+ # Loads the Ckeditor Javascript & Stylesheets only when in edit mode
23
+ def effective_regions_include_tags
24
+ if effectively_editting?
25
+ javascript_include_tag('effective_ckeditor') + stylesheet_link_tag('effective_ckeditor')
26
+ end
27
+ end
28
+
29
+ def effectively_editting?
30
+ @effectively_editting ||= request.fullpath.include?('?edit=true')
31
+ end
32
+
33
+ private
34
+
35
+ def ckeditor_region(args, options = {}, &block)
36
+ obj = args.first
37
+ title = args.last.to_s.parameterize
38
+ editable_tag = options.delete(:editable_tag) || :div
39
+
40
+ # Set up the editable div options we need to send to ckeditor
41
+ if effectively_editting?
42
+ opts = {
43
+ :contenteditable => true,
44
+ 'data-effective-ckeditor' => (options.delete(:type) || :full).to_s,
45
+ 'data-allowed-snippets' => [options.delete(:snippets)].flatten.compact.to_json,
46
+ :style => ['-webkit-user-modify: read-write;', options.delete(:style), ('display: inline;' if options.delete(:inline))].compact.join(' '),
47
+ :class => ['effective-region', options.delete(:class)].compact.join(' ')
48
+ }.merge(options)
49
+ end
50
+
51
+ if obj.kind_of?(ActiveRecord::Base)
52
+ raise StandardError.new('Object passed to effective_region helper must declare act_as_regionable') unless obj.respond_to?(:acts_as_regionable)
53
+
54
+ region = obj.regions.find { |region| region.title == title }
55
+
56
+ if effectively_editting?
57
+ can_edit = (EffectiveRegions.authorized?(controller, :update, obj) rescue false)
58
+ opts[:id] = [model_name_from_record_or_class(obj).param_key(), obj.id, title].join('_')
59
+ end
60
+ else # This is a global region
61
+ regions = (@effective_regions_global ||= Effective::Region.global.to_a)
62
+ region = regions.find { |region| region.title == title } || Effective::Region.new(:title => title)
63
+
64
+ if effectively_editting?
65
+ can_edit = (EffectiveRegions.authorized?(controller, :update, region) rescue false)
66
+ opts[:id] = title.to_s.parameterize
67
+ end
68
+ end
69
+
70
+ if effectively_editting? && can_edit # If we need the editable div
71
+ content_tag(editable_tag, opts) do
72
+ region.try(:content).present? ? render_region(region, true) : ((capture(&block).strip.html_safe) if block_given?)
73
+ end
74
+ else
75
+ region.try(:content).present? ? render_region(region, false) : ((capture(&block).strip.html_safe) if block_given?)
76
+ end
77
+ end
78
+
79
+ def render_region(region, can_edit = true)
80
+ return '' unless region
81
+
82
+ region.content.tap do |html|
83
+ html.scan(/\[(snippet_\d+)\]/).flatten.uniq.each do |id| # find snippet_1 and replace with snippet content
84
+ snippet = region.snippet_objects.find { |snippet| snippet.id == id }
85
+ html.gsub!("[#{id}]", render_snippet(snippet, can_edit)) if snippet
86
+ end
87
+ end.html_safe
88
+ end
89
+
90
+ def render_snippet(snippet, can_edit = true)
91
+ return '' unless snippet
92
+
93
+ if Rails.env.production?
94
+ content = render(:partial => snippet.to_partial_path, :object => snippet, :locals => {:snippet => snippet}) rescue ''
95
+ else
96
+ content = render(:partial => snippet.to_partial_path, :object => snippet, :locals => {:snippet => snippet})
97
+ end
98
+
99
+ if effectively_editting? && can_edit
100
+ content_tag(snippet.snippet_tag, content, :data => {'effective-snippet' => snippet.class_name, 'snippet-data' => snippet.data().to_json})
101
+ else
102
+ content
103
+ end.html_safe
104
+ end
105
+
106
+
107
+
108
+ end
@@ -0,0 +1,34 @@
1
+ module ActsAsRegionable
2
+ extend ActiveSupport::Concern
3
+
4
+ module ActiveRecord
5
+ def acts_as_regionable(*options)
6
+ @acts_as_regionable_opts = options || []
7
+ include ::ActsAsRegionable
8
+ end
9
+ end
10
+
11
+ included do
12
+ has_many :regions, :as => :regionable, :class_name => 'Effective::Region', :dependent => :delete_all, :autosave => true
13
+ end
14
+
15
+ module ClassMethods
16
+ end
17
+
18
+ def acts_as_regionable
19
+ true
20
+ end
21
+
22
+ def snippet_objects(klass = nil)
23
+ objs = regions.map { |region| region.snippet_objects }.flatten
24
+
25
+ if klass
26
+ objs = objs.select { |obj| obj.class == klass }
27
+ else
28
+ objs
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,17 @@
1
+ unless defined?(Effective::AccessDenied)
2
+ module Effective
3
+ class AccessDenied < StandardError
4
+ attr_reader :action, :subject
5
+
6
+ def initialize(message = nil, action = nil, subject = nil)
7
+ @message = message
8
+ @action = action
9
+ @subject = subject
10
+ end
11
+
12
+ def to_s
13
+ @message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ module Effective
2
+ class Region < ActiveRecord::Base
3
+ self.table_name = EffectiveRegions.regions_table_name.to_s
4
+
5
+ belongs_to :regionable, :polymorphic => true
6
+
7
+ structure do
8
+ title :string, :validates => [:presence]
9
+ content :text
10
+ snippets :text
11
+
12
+ timestamps
13
+ end
14
+
15
+ serialize :snippets, HashWithIndifferentAccess
16
+
17
+ scope :global, -> { where("#{EffectiveRegions.regions_table_name}.regionable_type IS NULL").where("#{EffectiveRegions.regions_table_name}.regionable_id IS NULL") }
18
+
19
+ def snippets
20
+ self[:snippets] || HashWithIndifferentAccess.new()
21
+ end
22
+
23
+ # Hash of the Snippets objectified
24
+ #
25
+ # Returns a Hash of {'snippet_1' => CurrentUserInfo.new(snippets[:key]['options'])}
26
+ def snippet_objects
27
+ @snippet_objects ||= snippets.map do |key, snippet| # Key here is 'snippet_1'
28
+ if snippet['class_name']
29
+ klass = "Effective::Snippets::#{snippet['class_name'].classify}".safe_constantize
30
+ klass.new(snippet.merge!(:region => self, :id => key)) if klass
31
+ end
32
+ end.compact
33
+ end
34
+
35
+ def global?
36
+ regionable_id == nil && regionable_type == nil
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+
43
+
44
+
@@ -0,0 +1,12 @@
1
+ module Effective
2
+ module Snippets
3
+ class CurrentUserInfo < Snippet
4
+ attribute :method, String
5
+
6
+ def snippet_tag
7
+ :span
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,69 @@
1
+ require 'virtus'
2
+
3
+ module Effective
4
+ module Snippets
5
+ class Snippet
6
+ include Virtus.model
7
+
8
+ attribute :id, String # This will be snippet_12345
9
+ attribute :region, Effective::Region # The region Object
10
+
11
+ # SO I have to add some restrictions on how snippets are built:
12
+
13
+ # Each Snippet has to be a block (or inline) element with nested children.
14
+ # It has to start with a root object
15
+
16
+ # That root object has to do {snippet_data(snippet)}
17
+
18
+
19
+ def initialize(atts = {})
20
+ (atts || {}).each { |k, v| self.send("#{k}=", v) if respond_to?("#{k}=") }
21
+ end
22
+
23
+ def id
24
+ super.presence || "snippet_#{object_id}"
25
+ end
26
+
27
+ def data
28
+ self.attributes.reject { |k, v| [:region, :id].include?(k) }
29
+ end
30
+
31
+ def to_partial_path
32
+ "effective/snippets/#{class_name}"
33
+ end
34
+
35
+ def class_name
36
+ @class_name ||= self.class.name.demodulize.underscore.to_sym
37
+ end
38
+
39
+ ### The following methods are used for the CKEditor widget creation.
40
+ def snippet_label
41
+ class_name.to_s.humanize
42
+ end
43
+
44
+ def snippet_description
45
+ "Insert #{snippet_label}"
46
+ end
47
+
48
+ def snippet_dialog_url
49
+ "/assets/effective/snippets/#{class_name}.js"
50
+ end
51
+
52
+ # This is the tag that the ckeditor snippet will be created as
53
+ # It supports divs and spans, but that's it
54
+ # No ULs, or LIs
55
+ def snippet_tag
56
+ :div
57
+ end
58
+
59
+ def snippet_inline
60
+ [:span].include?(snippet_tag)
61
+ end
62
+
63
+ def snippet_editables
64
+ false
65
+ end
66
+
67
+ end
68
+ end
69
+ end