radiant-polls-extension 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGELOG +27 -0
  2. data/HELP.textile +71 -0
  3. data/LICENSE +21 -0
  4. data/README.textile +112 -0
  5. data/Rakefile +129 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/admin/polls_controller.rb +25 -0
  8. data/app/controllers/poll_response_controller.rb +35 -0
  9. data/app/models/option.rb +22 -0
  10. data/app/models/poll.rb +67 -0
  11. data/app/views/admin/polls/_form.html.haml +67 -0
  12. data/app/views/admin/polls/_option.html.haml +9 -0
  13. data/app/views/admin/polls/edit.html.haml +5 -0
  14. data/app/views/admin/polls/index.html.haml +72 -0
  15. data/app/views/admin/polls/new.html.haml +5 -0
  16. data/app/views/admin/polls/remove.html.haml +17 -0
  17. data/config/locales/en.yml +25 -0
  18. data/config/locales/en_available_tags.yml +187 -0
  19. data/config/routes.rb +6 -0
  20. data/db/migrate/001_create_polls.rb +12 -0
  21. data/db/migrate/002_create_options.rb +14 -0
  22. data/db/migrate/003_add_start_date_to_polls.rb +9 -0
  23. data/lib/poll_process.rb +23 -0
  24. data/lib/poll_tags.rb +408 -0
  25. data/lib/tasks/polls_extension_tasks.rake +56 -0
  26. data/polls_extension.rb +51 -0
  27. data/public/images/admin/new-poll.png +0 -0
  28. data/public/images/admin/poll.png +0 -0
  29. data/public/images/admin/recycle.png +0 -0
  30. data/public/images/admin/reset.png +0 -0
  31. data/public/javascripts/admin/date_selector.js +177 -0
  32. data/public/javascripts/admin/polls.js +47 -0
  33. data/public/javascripts/poll_check.js +52 -0
  34. data/public/stylesheets/admin/polls.css +93 -0
  35. data/public/stylesheets/polls.css +9 -0
  36. data/radiant-polls-extension.gemspec +83 -0
  37. data/spec/controllers/admin/polls_controller_spec.rb +16 -0
  38. data/spec/controllers/poll_response_controller_spec.rb +149 -0
  39. data/spec/integration/page_caching_spec.rb +76 -0
  40. data/spec/lib/poll_tags_spec.rb +415 -0
  41. data/spec/models/poll_spec.rb +50 -0
  42. data/spec/spec.opts +6 -0
  43. data/spec/spec_helper.rb +42 -0
  44. metadata +141 -0
@@ -0,0 +1,27 @@
1
+ === 1.0.0
2
+
3
+ * Radiant 1.1.x compatible
4
+ * use cookies instead of sessions to monitor poll responses
5
+ * does not automactically expire cache on poll pages
6
+ * switched views from erb to haml
7
+ * now using pagination built into Radiant
8
+ * added poll:id radius tag
9
+ * use radiant-cache_by_page-extension, in order to set shorter cache times on pages with polls
10
+
11
+ === 0.3.2
12
+
13
+ * added <r:poll:options:each:if_first/> and <r:poll:options:each:if_last/>
14
+ * added <r:poll:options:each:unless_first /> and <r:poll:options:each:unless_last/>
15
+
16
+ === 0.3.1
17
+
18
+ * 0.8.1 compatibility (davec)
19
+ * Admin UI enhancements (davec)
20
+ * will_paginate poll pagination (davec)
21
+ * poll sorting options (davec)
22
+ * better handling of displaying multiple polls (davec)
23
+ * unit and functional tests (davec)
24
+
25
+ === 0.2.1
26
+
27
+ * polls in radiant
@@ -0,0 +1,71 @@
1
+ h1. Polls
2
+
3
+ The *Poll* extension, as its name implies, enables Web Polls for Radiant CMS.
4
+
5
+ h2. Usage
6
+
7
+ * Modify your Radiant Layouts to include:
8
+ ** inside the head tag: <code><script src="/javascripts/poll_check.js" type="text/javascript" charset="utf-8"></script></code>
9
+ ** modify the body tag thus: <code><body onLoad="checkForPoll();"></code>
10
+ * Create a polls snippet, something like this:
11
+
12
+ <pre>
13
+ <r:poll>
14
+ <div id="poll">
15
+ <div class="title">Poll of the Week</div>
16
+ <div id="poll_<r:id/>" class="content">
17
+ <h2><r:title/></h2>
18
+ <div id="poll_<r:id/>_unsubmitted" style="display:none">
19
+ <r:form>
20
+ <ul>
21
+ <r:options:each><li><r:input /> <r:title /></li></r:options:each>
22
+ </ul>
23
+ <br />
24
+ <input type="submit" name="poll[submit]" value="Submit" onclick="return checkRadioSelection('poll_form','response_id');" />
25
+ </r:form>
26
+ </div>
27
+
28
+ <div id="poll_<r:id/>_submitted" style="display:none">
29
+ <table>
30
+ <tr>
31
+ <th align="left">Answers</th>
32
+ <th align="left">Results</th>
33
+ </tr>
34
+ <r:options:each>
35
+ <tr>
36
+ <td><r:title /></td>
37
+ <td><r:number_responses /> (<r:percent_responses />%)</td>
38
+ </tr>
39
+ </r:options:each>
40
+ </table>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </r:poll>
45
+ </pre>
46
+
47
+ If polls are defined with a start date, then the current poll (defined as the
48
+ poll that has the latest start date that is no later than the current date) can
49
+ be accessed without the use of the title attribute (i.e., <r:poll>...</r:poll>).
50
+
51
+ Poll "archives" can also be listed, using pagination via the will_paginate plugin.
52
+
53
+ <r:polls per_page="10" by="start_date" order="desc">
54
+ <r:each:poll>
55
+ <p><r:title /></p>
56
+ <table>
57
+ <tr>
58
+ <th>Answer</th>
59
+ <th>Result</th>
60
+ </tr>
61
+ <r:options:each>
62
+ <tr>
63
+ <td><r:title /></td>
64
+ <td><r:number_responses /> (<r:percent_responses />%)</td>
65
+ </tr>
66
+ </r:options:each>
67
+ </table>
68
+ </r:each:poll>
69
+ <r:pages />
70
+ </r:polls>
71
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ == MIT License
2
+
3
+ Copyright (c) 2009, Chase A. James <nx@nu-ex.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,112 @@
1
+ h1. Polls Extension
2
+
3
+ This is a significant fork of the original at https://github.com/nuex/radiant-polls-extension ("Web polls for Radiant CMS.")
4
+
5
+ h2. Differences in this Fork
6
+
7
+ * Uses cookies instead of sessions to trace poll responses.
8
+ ** This limits the occurrence of multiple responses from the same respondent, thus skewing poll results
9
+ ** It also means that we will not automatically expire caches on pages with polls, thus improving overall performance. Instead the server will send both the poll and response data, and client side javascript will read cookies to determine which to display
10
+ * Uses poll_check.js to read cookies, determining if this user has submitted response to this poll yet
11
+ * Switched views from erb to haml
12
+ * Uses radiant-cache_by_page-extension, in order to set shorter cache times on pages with polls
13
+
14
+ h2. Installation
15
+
16
+ Using Bundler:
17
+
18
+ * add this to your Gemfile
19
+ <code>
20
+ gem "radiant-polls-extension", :git => "https://github.com/avonderluft/radiant-polls-extension.git"</code>
21
+ * %rake radiant:extensions:polls:migrate%
22
+ * %rake radiant:extensions:polls:update%
23
+ * %bundle%
24
+
25
+ h2. Usage
26
+
27
+ * Modify your Radiant Layouts to include:
28
+ ** inside the head tag: <code><script src="/javascripts/poll_check.js" type="text/javascript" charset="utf-8"></script></code>
29
+ ** modify the body tag thus: <code><body onLoad="checkForPoll();"></code>
30
+ * Create a polls snippet, something like this:
31
+
32
+ <pre>
33
+ <r:poll>
34
+ <div id="poll">
35
+ <div class="title">Poll of the Week</div>
36
+ <div id="poll_<r:id/>" class="content">
37
+ <h2><r:title/></h2>
38
+ <div id="poll_<r:id/>_unsubmitted" style="display:none">
39
+ <r:form>
40
+ <ul>
41
+ <r:options:each><li><r:input /> <r:title /></li></r:options:each>
42
+ </ul>
43
+ <br />
44
+ <input type="submit" name="poll[submit]" value="Submit" onclick="return checkRadioSelection('poll_form','response_id');" />
45
+ </r:form>
46
+ </div>
47
+
48
+ <div id="poll_<r:id/>_submitted" style="display:none">
49
+ <table>
50
+ <tr>
51
+ <th align="left">Answers</th>
52
+ <th align="left">Results</th>
53
+ </tr>
54
+ <r:options:each>
55
+ <tr>
56
+ <td><r:title /></td>
57
+ <td><r:number_responses /> (<r:percent_responses />%)</td>
58
+ </tr>
59
+ </r:options:each>
60
+ </table>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </r:poll>
65
+ </pre>
66
+
67
+ If polls are defined with a start date, then the current poll (defined as the
68
+ poll that has the latest start date that is no later than the current date) can
69
+ be accessed without the use of the title attribute (i.e., <r:poll>...</r:poll>).
70
+
71
+ Poll "archives" can also be listed, using pagination via the will_paginate plugin.
72
+
73
+ <r:polls per_page="10" by="start_date" order="desc">
74
+ <r:each:poll>
75
+ <p><r:title /></p>
76
+ <table>
77
+ <tr>
78
+ <th>Answer</th>
79
+ <th>Result</th>
80
+ </tr>
81
+ <r:options:each>
82
+ <tr>
83
+ <td><r:title /></td>
84
+ <td><r:number_responses /> (<r:percent_responses />%)</td>
85
+ </tr>
86
+ </r:options:each>
87
+ </table>
88
+ </r:each:poll>
89
+ <r:pages />
90
+ </r:polls>
91
+
92
+ h2. To do
93
+
94
+ * the polls (archives) are not working in some instances
95
+
96
+ h2. Note and Caveats
97
+
98
+ If you are using the Oracle Enhanced Adapter, you may want to add this to your environment.rb,
99
+ in the after_initialize block:
100
+
101
+ # By default, OracleEnhancedAdapter typecasts all columns of type DATE to Time or DateTime.
102
+ # This will force DATE values with hour, min and secs equal to 0 to be typecasted to Date.
103
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
104
+ # The above can obviously cause problems, therefore this is to be preferred,
105
+ # since it will typecast to DATE, only columns with “date” in their name (e.g. “creation_date“)
106
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
107
+
108
+ h2. Acknowledgements
109
+
110
+ * Thanks to David Coto (http://github.com/davec) for the many enhancements and specs.
111
+ * The date picker uses Dan Webb's LowPro library and its associated date_selector
112
+ behavior.
@@ -0,0 +1,129 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "radiant-polls-extension"
5
+ gem.summary = %Q{Polls Extension for Radiant CMS}
6
+ gem.description = %Q{Enables polls on pages. Uses cookies instead of sessions}
7
+ gem.email = "avonderluft@avlux.net"
8
+ gem.homepage = "https://github.com/avonderluft/radiant-polls-extension"
9
+ gem.authors = ['Chase James','David Cato','Andrew vonderLuft']
10
+ gem.add_dependency 'radiant', ">=1.1.3"
11
+ gem.add_dependency 'radiant-cache_by_page-extension', ">=1.0.2"
12
+ end
13
+ rescue LoadError
14
+ puts "Jeweler (or a dependency) not available. This is only required if you plan to package polls extension as a gem."
15
+ end
16
+
17
+ # I think this is the one that should be moved to the extension Rakefile templat
18
+
19
+ # In rails 1.2, plugins aren't available in the path until they're loaded.
20
+ # Check to see if the rspec plugin is installed first and require
21
+ # it if it is. If not, use the gem version.
22
+
23
+ # Determine where the RSpec plugin is by loading the boot
24
+ unless defined? RADIANT_ROOT
25
+ ENV["RAILS_ENV"] = "test"
26
+ case
27
+ when ENV["RADIANT_ENV_FILE"]
28
+ require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
29
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
30
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
31
+ else
32
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
33
+ end
34
+ end
35
+
36
+ require 'rake'
37
+ require 'rake/testtask'
38
+ require 'rdoc/task'
39
+
40
+ rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
41
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
42
+ require 'spec/rake/spectask'
43
+ # require 'spec/translator'
44
+
45
+
46
+ # Cleanup the RADIANT_ROOT constant so specs will load the environment
47
+ Object.send(:remove_const, :RADIANT_ROOT)
48
+
49
+ extension_root = File.expand_path(File.dirname(__FILE__))
50
+
51
+ task :default => :spec
52
+ task :stats => "spec:statsetup"
53
+
54
+ desc "Run all specs in spec directory"
55
+ Spec::Rake::SpecTask.new(:spec) do |t|
56
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
57
+ t.spec_files = FileList['spec/**/*_spec.rb']
58
+ end
59
+
60
+ namespace :spec do
61
+ desc "Run all specs in spec directory with RCov"
62
+ Spec::Rake::SpecTask.new(:rcov) do |t|
63
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
64
+ t.spec_files = FileList['spec/**/*_spec.rb']
65
+ t.rcov = true
66
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
67
+ end
68
+
69
+ desc "Print Specdoc for all specs"
70
+ Spec::Rake::SpecTask.new(:doc) do |t|
71
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
72
+ t.spec_files = FileList['spec/**/*_spec.rb']
73
+ end
74
+
75
+ [:models, :controllers, :views, :helpers, :integration, :lib].each do |sub|
76
+ desc "Run the specs under spec/#{sub}"
77
+ Spec::Rake::SpecTask.new(sub) do |t|
78
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
79
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
80
+ end
81
+ end
82
+
83
+ # Setup specs for stats
84
+ task :statsetup do
85
+ require 'code_statistics'
86
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
87
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
88
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
89
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
90
+ ::CodeStatistics::TEST_TYPES << "Model specs"
91
+ ::CodeStatistics::TEST_TYPES << "View specs"
92
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
93
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
94
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
95
+ end
96
+
97
+ namespace :db do
98
+ namespace :fixtures do
99
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
100
+ task :load => :environment do
101
+ require 'active_record/fixtures'
102
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
103
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
104
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ desc 'Generate documentation for the polls extension.'
112
+ Rake::RDocTask.new(:rdoc) do |rdoc|
113
+ rdoc.rdoc_dir = 'rdoc'
114
+ rdoc.title = 'PollsExtension'
115
+ rdoc.options << '--line-numbers' << '--inline-source'
116
+ rdoc.rdoc_files.include('README')
117
+ rdoc.rdoc_files.include('lib/**/*.rb')
118
+ end
119
+
120
+ # For extensions that are in transition
121
+ desc 'Test the polls extension.'
122
+ Rake::TestTask.new(:test) do |t|
123
+ t.libs << 'lib'
124
+ t.pattern = 'test/**/*_test.rb'
125
+ t.verbose = true
126
+ end
127
+
128
+ # Load any custom rakefiles for extension
129
+ Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,25 @@
1
+ class Admin::PollsController < Admin::ResourceController
2
+
3
+ model_class Poll
4
+ paginate_models
5
+ # only_allow_access_to :index, :show, :new, :create, :edit, :update, :remove, :destroy,
6
+ # :when => [:designer, :admin],
7
+ # :denied_url => { :controller => 'admin/pages', :action => 'index' },
8
+ # :denied_message => 'You must have designer privileges to perform this action.'
9
+
10
+ def clear_responses
11
+ if @poll = Poll.find(params[:id])
12
+ @poll.clear_responses
13
+ flash[:notice] = t('polls_controller.responses_cleared', :poll => @poll.title)
14
+ end
15
+ redirect_to :action => :index
16
+ end
17
+
18
+ protected
19
+
20
+ def load_models
21
+ # Order polls by descending date, then alphabetically by title
22
+ self.models = model_class.all(:order => Poll.send(:sanitize_sql_array, [ "COALESCE(start_date, ?) DESC, title", Date.new(1900, 1, 1) ])).paginate(pagination_parameters)
23
+ end
24
+
25
+ end
@@ -0,0 +1,35 @@
1
+ class PollResponseController < ApplicationController
2
+ no_login_required
3
+ skip_before_filter :verify_authenticity_token
4
+
5
+ def create
6
+ @page = Page.find(params[:page_id])
7
+ @page.request, @page.response = request, response
8
+
9
+ poll = Poll.find(params[:poll_id])
10
+ current_poll = Poll.find_current
11
+
12
+ # Use cookies instead of sessions, to limit multiple responses on same poll
13
+ # assign the submitted polls from cookies to the current page so we can
14
+ # pass it off to our radius tags
15
+ @page.submitted_polls = @page.poll_cookies(cookies)
16
+
17
+ if !@page.submitted_polls.include?(poll.id) && (!current_poll || current_poll.id == poll.id)
18
+
19
+ begin
20
+ poll_cookie_duration = eval(config['polls.cookie_duration']).from_now || 1.month.from_now
21
+ rescue Exception => exc
22
+ poll_cookie_duration = 1.month.from_now
23
+ end
24
+
25
+ cookies["poll_#{poll.id.to_s}"] = { :value => "#{@page.id}", :expires => poll_cookie_duration }
26
+ @page.submitted_polls << poll.id
27
+
28
+ poll_response = Option.find(params[:response_id])
29
+ poll.submit_response(poll_response)
30
+ end
31
+
32
+ redirect_to @page.url
33
+ end
34
+
35
+ end
@@ -0,0 +1,22 @@
1
+ class Option < ActiveRecord::Base
2
+ belongs_to :poll
3
+ validates_presence_of :title
4
+ before_create :set_defaults
5
+
6
+ # return the percentage of responses this
7
+ # option has
8
+ def response_percentage
9
+ return 0 unless self.poll.response_count >= 1
10
+ sprintf("%0.1f", (self.response_count / self.poll.response_count.to_f) * 100.0)
11
+ end
12
+
13
+ def should_destroy?
14
+ should_destroy.to_i == 1
15
+ end
16
+
17
+ private
18
+
19
+ def set_defaults
20
+ self.response_count = 0
21
+ end
22
+ end