Merge pull request #4861 from JannikStreek/feature/3676-conduct_polls
Feature/3676 Conduct Polls
This commit is contained in:
commit
faad980df0
45 changed files with 1074 additions and 30 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -65,3 +65,6 @@ dump.rdb
|
||||||
|
|
||||||
#Rubinius's JIT
|
#Rubinius's JIT
|
||||||
*.rbc
|
*.rbc
|
||||||
|
|
||||||
|
#IDE
|
||||||
|
diaspora.iml
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
* Added comment count to statistic to enable calculations of posts/comments ratios [#4799](https://github.com/diaspora/diaspora/pull/4799)
|
* Added comment count to statistic to enable calculations of posts/comments ratios [#4799](https://github.com/diaspora/diaspora/pull/4799)
|
||||||
* Add filters to notifications controller [#4814](https://github.com/diaspora/diaspora/pull/4814)
|
* Add filters to notifications controller [#4814](https://github.com/diaspora/diaspora/pull/4814)
|
||||||
* Activate hovercards in SPV and conversations [#4870](https://github.com/diaspora/diaspora/pull/4870)
|
* Activate hovercards in SPV and conversations [#4870](https://github.com/diaspora/diaspora/pull/4870)
|
||||||
|
* Added possibility to conduct polls [#4861](https://github.com/diaspora/diaspora/pull/4861)
|
||||||
|
|
||||||
# 0.3.0.3
|
# 0.3.0.3
|
||||||
|
|
||||||
|
|
@ -58,6 +59,7 @@
|
||||||
## Bug fixes
|
## Bug fixes
|
||||||
* Fix regression caused by using after_commit with nested '#save' which lead to an infinite recursion [#4715](https://github.com/diaspora/diaspora/issues/4715)
|
* Fix regression caused by using after_commit with nested '#save' which lead to an infinite recursion [#4715](https://github.com/diaspora/diaspora/issues/4715)
|
||||||
* Save textarea value before rendering comments when clicked 'show more...' [#4514](https://github.com/diaspora/diaspora/issues/4514)
|
* Save textarea value before rendering comments when clicked 'show more...' [#4514](https://github.com/diaspora/diaspora/issues/4514)
|
||||||
|
|
||||||
# 0.3.0.0
|
# 0.3.0.0
|
||||||
|
|
||||||
## Pod statistics
|
## Pod statistics
|
||||||
|
|
|
||||||
5
app/assets/javascripts/app/models/poll_participation.js
Normal file
5
app/assets/javascripts/app/models/poll_participation.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
app.models.PollParticipation = Backbone.Model.extend({
|
||||||
|
url : function(){
|
||||||
|
"/poll_participations"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -13,7 +13,8 @@ app.models.StatusMessage = app.models.Post.extend({
|
||||||
status_message : _.clone(this.attributes),
|
status_message : _.clone(this.attributes),
|
||||||
aspect_ids : this.get("aspect_ids"),
|
aspect_ids : this.get("aspect_ids"),
|
||||||
photos : this.photos && this.photos.pluck("id"),
|
photos : this.photos && this.photos.pluck("id"),
|
||||||
services : this.get("services")
|
services : this.get("services"),
|
||||||
|
poll : this.get("poll")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
94
app/assets/javascripts/app/views/poll_view.js
Normal file
94
app/assets/javascripts/app/views/poll_view.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
app.views.Poll = app.views.Base.extend({
|
||||||
|
templateName : "poll",
|
||||||
|
|
||||||
|
events : {
|
||||||
|
"click .submit" : "vote",
|
||||||
|
"click .toggle_result" : "toggleResult"
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function(options) {
|
||||||
|
this.poll = this.model.attributes.poll;
|
||||||
|
this.progressBarFactor = 3;
|
||||||
|
this.toggleMode = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
postRenderTemplate : function() {
|
||||||
|
if(this.poll) {
|
||||||
|
this.setProgressBar();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeForm : function() {
|
||||||
|
var cnt = this.$("form").contents();
|
||||||
|
this.$("form").replaceWith(cnt);
|
||||||
|
this.$('input').remove();
|
||||||
|
this.$('submit').remove();
|
||||||
|
this.$('.toggle_result_wrapper').remove();
|
||||||
|
},
|
||||||
|
|
||||||
|
setProgressBar : function() {
|
||||||
|
var answers = this.poll.poll_answers;
|
||||||
|
for(index = 0; index < answers.length; ++index) {
|
||||||
|
var percentage = 0;
|
||||||
|
if(this.poll.participation_count != 0) {
|
||||||
|
percentage = Math.round(answers[index].vote_count / this.poll.participation_count * 100);
|
||||||
|
}
|
||||||
|
var progressBar = this.$(".poll_progress_bar[data-answerid="+answers[index].id+"]");
|
||||||
|
progressBar.parent().next().html(" - " + percentage + "%");
|
||||||
|
var width = percentage * this.progressBarFactor;
|
||||||
|
progressBar.css("width", width + "px");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleResult : function(e) {
|
||||||
|
this.$('.poll_progress_bar_wrapper').toggle();
|
||||||
|
this.$('.percentage').toggle();
|
||||||
|
if(this.toggleMode == 0) {
|
||||||
|
this.$('.toggle_result').html(Diaspora.I18n.t("poll.close_result"));
|
||||||
|
this.toggleMode = 1;
|
||||||
|
}else{
|
||||||
|
this.$('.toggle_result').html(Diaspora.I18n.t("poll.show_result"));
|
||||||
|
this.toggleMode = 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshResult : function(answerId) {
|
||||||
|
this.updateCounter(answerId);
|
||||||
|
this.setProgressBar();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCounter : function(answerId) {
|
||||||
|
this.poll.participation_count++;
|
||||||
|
this.$('.poll_statistic').html(Diaspora.I18n.t("poll.count", {"count" : this.poll.participation_count}));
|
||||||
|
var answers = this.poll.poll_answers;
|
||||||
|
for(index = 0; index < answers.length; ++index) {
|
||||||
|
if(answers[index].id == answerId) {
|
||||||
|
answers[index].vote_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
vote : function(evt){
|
||||||
|
var result = parseInt($(evt.target).parent().find("input[name=vote]:checked").val());
|
||||||
|
var pollParticipation = new app.models.PollParticipation();
|
||||||
|
var parent = this;
|
||||||
|
pollParticipation.save({
|
||||||
|
"poll_answer_id" : result,
|
||||||
|
"poll_id" : this.poll.poll_id
|
||||||
|
},{
|
||||||
|
url : "/posts/"+this.poll.post_id+"/poll_participations",
|
||||||
|
success : function(model, response) {
|
||||||
|
parent.removeForm();
|
||||||
|
parent.refreshResult(result);
|
||||||
|
if(parent.toggleMode == 0) {
|
||||||
|
parent.toggleResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -23,12 +23,16 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
"click .post_preview_button" : "createPostPreview",
|
"click .post_preview_button" : "createPostPreview",
|
||||||
"textchange #status_message_fake_text": "handleTextchange",
|
"textchange #status_message_fake_text": "handleTextchange",
|
||||||
"click #locator" : "showLocation",
|
"click #locator" : "showLocation",
|
||||||
|
"click #poll_creator" : "showPollCreator",
|
||||||
|
"click #add_poll_answer" : "addPollAnswer",
|
||||||
|
"click .remove_poll_answer" : "removePollAnswer",
|
||||||
"click #hide_location" : "destroyLocation",
|
"click #hide_location" : "destroyLocation",
|
||||||
"keypress #location_address" : "avoidEnter"
|
"keypress #location_address" : "avoidEnter"
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize : function(opts){
|
initialize : function(opts){
|
||||||
this.standalone = opts ? opts.standalone : false;
|
this.standalone = opts ? opts.standalone : false;
|
||||||
|
this.option_counter = 1;
|
||||||
|
|
||||||
// init shortcut references to the various elements
|
// init shortcut references to the various elements
|
||||||
this.el_input = this.$('#status_message_fake_text');
|
this.el_input = this.$('#status_message_fake_text');
|
||||||
|
|
@ -37,6 +41,8 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
this.el_submit = this.$('input[type=submit], button#submit');
|
this.el_submit = this.$('input[type=submit], button#submit');
|
||||||
this.el_preview = this.$('button.post_preview_button');
|
this.el_preview = this.$('button.post_preview_button');
|
||||||
this.el_photozone = this.$('#photodropzone');
|
this.el_photozone = this.$('#photodropzone');
|
||||||
|
this.el_poll_creator = this.$('#poll_creator_wrapper');
|
||||||
|
this.el_poll_answer = this.$('#poll_creator_wrapper .poll_answer');
|
||||||
|
|
||||||
// init mentions plugin
|
// init mentions plugin
|
||||||
Mentions.initialize(this.el_input);
|
Mentions.initialize(this.el_input);
|
||||||
|
|
@ -69,7 +75,7 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
});
|
});
|
||||||
|
|
||||||
this.initSubviews();
|
this.initSubviews();
|
||||||
|
this.addPollAnswer();
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -136,7 +142,9 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
"photos" : serializedForm["photos[]"],
|
"photos" : serializedForm["photos[]"],
|
||||||
"services" : serializedForm["services[]"],
|
"services" : serializedForm["services[]"],
|
||||||
"location_address" : $("#location_address").val(),
|
"location_address" : $("#location_address").val(),
|
||||||
"location_coords" : serializedForm["location[coords]"]
|
"location_coords" : serializedForm["location[coords]"],
|
||||||
|
"poll_question" : serializedForm["poll_question"],
|
||||||
|
"poll_answers" : serializedForm["poll_answers[]"]
|
||||||
}, {
|
}, {
|
||||||
url : "/status_messages",
|
url : "/status_messages",
|
||||||
success : function() {
|
success : function() {
|
||||||
|
|
@ -171,6 +179,36 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showPollCreator: function(){
|
||||||
|
this.el_poll_creator.toggle();
|
||||||
|
},
|
||||||
|
|
||||||
|
addPollAnswer: function(){
|
||||||
|
if($(".poll_answer").size() == 1) {
|
||||||
|
$(".remove_poll_answer").css("visibility","visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.option_counter++;
|
||||||
|
var clone = this.el_poll_answer.clone();
|
||||||
|
|
||||||
|
var answer = clone.find('.poll_answer_input');
|
||||||
|
answer.val("");
|
||||||
|
|
||||||
|
var placeholder = answer.attr("placeholder");
|
||||||
|
var expression = /[^0-9]+/;
|
||||||
|
answer.attr("placeholder", expression.exec(placeholder) + this.option_counter);
|
||||||
|
|
||||||
|
$('#poll_creator_wrapper .poll_answer').last().after(clone);
|
||||||
|
},
|
||||||
|
|
||||||
|
removePollAnswer: function(evt){
|
||||||
|
$(evt.currentTarget).parent().remove();
|
||||||
|
if($(".poll_answer").size() == 1) {
|
||||||
|
$(".remove_poll_answer").css("visibility","hidden");;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
// avoid submitting form when pressing Enter key
|
// avoid submitting form when pressing Enter key
|
||||||
avoidEnter: function(evt){
|
avoidEnter: function(evt){
|
||||||
if(evt.keyCode == 13)
|
if(evt.keyCode == 13)
|
||||||
|
|
@ -295,6 +333,9 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
// clear location
|
// clear location
|
||||||
this.destroyLocation();
|
this.destroyLocation();
|
||||||
|
|
||||||
|
// clear poll form
|
||||||
|
this.clearPollForm();
|
||||||
|
|
||||||
// force textchange plugin to update lastValue
|
// force textchange plugin to update lastValue
|
||||||
this.el_input.data('lastValue', '');
|
this.el_input.data('lastValue', '');
|
||||||
this.el_hiddenInput.data('lastValue', '');
|
this.el_hiddenInput.data('lastValue', '');
|
||||||
|
|
@ -302,6 +343,11 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearPollForm : function(){
|
||||||
|
this.$('#poll_question').val('');
|
||||||
|
this.$('.poll_answer_input').val('');
|
||||||
|
},
|
||||||
|
|
||||||
tryClose : function(){
|
tryClose : function(){
|
||||||
// if it is not submittable, close it.
|
// if it is not submittable, close it.
|
||||||
if( !this._submittable() ){
|
if( !this._submittable() ){
|
||||||
|
|
@ -323,7 +369,7 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
$(this.el).addClass("closed");
|
$(this.el).addClass("closed");
|
||||||
this.el_wrapper.removeClass("active");
|
this.el_wrapper.removeClass("active");
|
||||||
this.el_input.css('height', '');
|
this.el_input.css('height', '');
|
||||||
|
this.el_poll_creator.hide();
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ app.views.StreamPost = app.views.Post.extend({
|
||||||
".post-content" : "postContentView",
|
".post-content" : "postContentView",
|
||||||
".oembed" : "oEmbedView",
|
".oembed" : "oEmbedView",
|
||||||
".opengraph" : "openGraphView",
|
".opengraph" : "openGraphView",
|
||||||
|
".poll" : "pollView",
|
||||||
".status-message-location" : "postLocationStreamView"
|
".status-message-location" : "postLocationStreamView"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -31,6 +32,7 @@ app.views.StreamPost = app.views.Post.extend({
|
||||||
this.commentStreamView = new app.views.CommentStream({model : this.model});
|
this.commentStreamView = new app.views.CommentStream({model : this.model});
|
||||||
this.oEmbedView = new app.views.OEmbed({model : this.model});
|
this.oEmbedView = new app.views.OEmbed({model : this.model});
|
||||||
this.openGraphView = new app.views.OpenGraph({model : this.model});
|
this.openGraphView = new app.views.OpenGraph({model : this.model});
|
||||||
|
this.pollView = new app.views.Poll({model : this.model});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
@import 'header'
|
@import 'header'
|
||||||
@import 'footer'
|
@import 'footer'
|
||||||
@import 'opengraph'
|
@import 'opengraph'
|
||||||
|
@import 'poll'
|
||||||
@import 'help'
|
@import 'help'
|
||||||
@import 'profile'
|
@import 'profile'
|
||||||
@import 'publisher_blueprint'
|
@import 'publisher_blueprint'
|
||||||
|
|
|
||||||
45
app/assets/stylesheets/poll.css.scss
Normal file
45
app/assets/stylesheets/poll.css.scss
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
.poll_form {
|
||||||
|
display: block;
|
||||||
|
margin: 10px 0px 10px 0px;
|
||||||
|
border-top: solid 1px $border-grey;
|
||||||
|
border-bottom: solid 1px $border-grey;
|
||||||
|
padding: 10px 0px 5px 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_form input[type="radio"] {
|
||||||
|
display:inline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_result {
|
||||||
|
width:100%px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_progress_bar {
|
||||||
|
position:absolute;
|
||||||
|
width:0px;
|
||||||
|
height:15px;
|
||||||
|
top:-12px;
|
||||||
|
z-index:-1;
|
||||||
|
background-color:$background-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_statistic{
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_progress_bar_wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_answer_entry{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.percentage {
|
||||||
|
display:inline;
|
||||||
|
}
|
||||||
|
|
@ -82,6 +82,16 @@
|
||||||
&.with_attachments .row-fluid#photodropzone_container {
|
&.with_attachments .row-fluid#photodropzone_container {
|
||||||
border-top: 1px dashed $border-grey;
|
border-top: 1px dashed $border-grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#poll_creator_wrapper {
|
||||||
|
display:none;
|
||||||
|
border-top: 1px dashed $border-grey;
|
||||||
|
padding:4px 6px 4px 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
&.with_location .row-fluid#location_container {
|
&.with_location .row-fluid#location_container {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
#hide_location { display: none !important; }
|
#hide_location { display: none !important; }
|
||||||
|
|
@ -162,6 +172,7 @@
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
#file-upload,
|
#file-upload,
|
||||||
#locator,
|
#locator,
|
||||||
|
#poll_creator,
|
||||||
#hide_location {
|
#hide_location {
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
|
||||||
|
|
@ -309,8 +309,75 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#poll_creator {
|
||||||
|
bottom: 1px !important;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute !important;
|
||||||
|
right: 55px;
|
||||||
|
i {
|
||||||
|
@include opacity(0.4);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: $text-dark-grey;
|
||||||
|
cursor: pointer;
|
||||||
|
i {
|
||||||
|
@include opacity(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
height: 19px;
|
height: 19px;
|
||||||
width: 19px;
|
width: 19px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#poll_creator_wrapper {
|
||||||
|
display:none;
|
||||||
|
border: 1px solid $border-dark-grey;
|
||||||
|
padding:5px;
|
||||||
|
margin-top:1em;
|
||||||
|
@include border-radius(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove_poll_answer {
|
||||||
|
visibility:hidden;
|
||||||
|
float:right;
|
||||||
|
display: table-cell;
|
||||||
|
|
||||||
|
.icons-deletelabel {
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
margin-top:5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_answer_input {
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add_poll_answer_wrapper {
|
||||||
|
padding:5px 0 5px 0;
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#poll_question_wrapper {
|
||||||
|
}
|
||||||
|
|
||||||
|
#poll_question {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_answer {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll_answer_input_wrapper {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
32
app/assets/templates/poll_tpl.jst.hbs
Normal file
32
app/assets/templates/poll_tpl.jst.hbs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{{#if poll}}
|
||||||
|
<div class="poll_form">
|
||||||
|
<p class="poll_statistic">{{t "poll.count" count=poll.participation_count}}</p>
|
||||||
|
<strong>{{poll.question}}</strong><br/>
|
||||||
|
{{#unless already_participated_in_poll}}
|
||||||
|
<form action="/posts/{{poll.post_id}}/poll_participations" method="POST">
|
||||||
|
{{#poll.poll_answers}}
|
||||||
|
<input type="radio" name="vote" value="{{id}}"/>
|
||||||
|
<div class="poll_progress_bar_wrapper" style="display:none;">
|
||||||
|
<div class="poll_progress_bar" data-answerid="{{id}}"></div>
|
||||||
|
</div>
|
||||||
|
{{answer}}
|
||||||
|
<p class="percentage" style="display:none;"></p>
|
||||||
|
<br/>
|
||||||
|
{{/poll.poll_answers}}
|
||||||
|
<input type="submit" class="button submit" style="float:right;" value="{{t "poll.vote"}}"/>
|
||||||
|
</form>
|
||||||
|
<p class="toggle_result_wrapper">
|
||||||
|
<br/><a href="#" class="toggle_result">{{t "poll.show_result"}}</a><br/>
|
||||||
|
</p>
|
||||||
|
{{else}}
|
||||||
|
{{#poll.poll_answers}}
|
||||||
|
<div class="poll_progress_bar_wrapper">
|
||||||
|
<div class="poll_progress_bar" data-answerid="{{id}}"></div>
|
||||||
|
</div>
|
||||||
|
{{answer}}
|
||||||
|
<p class="percentage"></p>
|
||||||
|
<br/>
|
||||||
|
{{/poll.poll_answers}}
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
@ -18,4 +18,5 @@
|
||||||
{{{text}}}
|
{{{text}}}
|
||||||
<div class="oembed"></div>
|
<div class="oembed"></div>
|
||||||
<div class="opengraph"></div>
|
<div class="opengraph"></div>
|
||||||
|
<div class="poll"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
28
app/controllers/poll_participations_controller.rb
Normal file
28
app/controllers/poll_participations_controller.rb
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
class PollParticipationsController < ApplicationController
|
||||||
|
include ApplicationHelper
|
||||||
|
before_filter :authenticate_user!
|
||||||
|
|
||||||
|
def create
|
||||||
|
answer = PollAnswer.find(params[:poll_answer_id])
|
||||||
|
poll_participation = current_user.participate_in_poll!(target, answer) if target
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_to :back }
|
||||||
|
format.mobile { redirect_to stream_path }
|
||||||
|
format.json { render json: poll_participation, :status => 201 }
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordInvalid
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_to :back }
|
||||||
|
format.mobile { redirect_to stream_path }
|
||||||
|
format.json { render :nothing => true, :status => 403 }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def target
|
||||||
|
@target ||= if params[:post_id]
|
||||||
|
current_user.find_visible_shareable_by_id(Post, params[:post_id]) || raise(ActiveRecord::RecordNotFound.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -49,6 +49,14 @@ class StatusMessagesController < ApplicationController
|
||||||
|
|
||||||
@status_message = current_user.build_post(:status_message, params[:status_message])
|
@status_message = current_user.build_post(:status_message, params[:status_message])
|
||||||
@status_message.build_location(:address => params[:location_address], :coordinates => params[:location_coords]) if params[:location_address].present?
|
@status_message.build_location(:address => params[:location_address], :coordinates => params[:location_coords]) if params[:location_address].present?
|
||||||
|
if params[:poll_question].present?
|
||||||
|
@status_message.build_poll(:question => params[:poll_question])
|
||||||
|
[*params[:poll_answers]].each do |poll_answer|
|
||||||
|
@status_message.poll.poll_answers.build(:answer => poll_answer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
@status_message.attach_photos_by_ids(params[:photos])
|
@status_message.attach_photos_by_ids(params[:photos])
|
||||||
|
|
||||||
if @status_message.save
|
if @status_message.save
|
||||||
|
|
@ -78,7 +86,7 @@ class StatusMessagesController < ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { redirect_to :back }
|
format.html { redirect_to :back }
|
||||||
format.mobile { redirect_to stream_path }
|
format.mobile { redirect_to stream_path }
|
||||||
format.json { render :nothing => true , :status => 403 }
|
format.json { render :nothing => true, :status => 403 }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -117,4 +125,4 @@ class StatusMessagesController < ApplicationController
|
||||||
def remove_getting_started
|
def remove_getting_started
|
||||||
current_user.disable_getting_started
|
current_user.disable_getting_started
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
40
app/models/poll.rb
Normal file
40
app/models/poll.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
class Poll < ActiveRecord::Base
|
||||||
|
include Diaspora::Federated::Base
|
||||||
|
include Diaspora::Guid
|
||||||
|
attr_accessible :question, :poll_answers
|
||||||
|
belongs_to :status_message
|
||||||
|
has_many :poll_answers
|
||||||
|
has_many :poll_participations
|
||||||
|
|
||||||
|
xml_attr :question
|
||||||
|
xml_attr :poll_answers, :as => [PollAnswer]
|
||||||
|
|
||||||
|
#forward some requests to status message, because a poll is just attached to a status message and is not sharable itself
|
||||||
|
delegate :author, :author_id, :public?, :subscribers, to: :status_message
|
||||||
|
|
||||||
|
validate :enough_poll_answers
|
||||||
|
|
||||||
|
self.include_root_in_json = false
|
||||||
|
|
||||||
|
def enough_poll_answers
|
||||||
|
errors.add(:poll_answers, I18n.t("activerecord.errors.models.poll.attributes.poll_answers.not_enough_poll_answers")) if poll_answers.size < 2
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_json(options={})
|
||||||
|
{
|
||||||
|
:poll_id => self.id,
|
||||||
|
:post_id => self.status_message.id,
|
||||||
|
:question => self.question,
|
||||||
|
:poll_answers => self.poll_answers,
|
||||||
|
:participation_count => self.participation_count,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def participation_count
|
||||||
|
poll_answers.sum("vote_count")
|
||||||
|
end
|
||||||
|
|
||||||
|
def already_participated?(user)
|
||||||
|
poll_participations.where(:author_id => user.person.id).present?
|
||||||
|
end
|
||||||
|
end
|
||||||
13
app/models/poll_answer.rb
Normal file
13
app/models/poll_answer.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
class PollAnswer < ActiveRecord::Base
|
||||||
|
|
||||||
|
include Diaspora::Federated::Base
|
||||||
|
include Diaspora::Guid
|
||||||
|
|
||||||
|
belongs_to :poll
|
||||||
|
has_many :poll_participations
|
||||||
|
|
||||||
|
xml_attr :answer
|
||||||
|
|
||||||
|
self.include_root_in_json = false
|
||||||
|
|
||||||
|
end
|
||||||
66
app/models/poll_participation.rb
Normal file
66
app/models/poll_participation.rb
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
class PollParticipation < ActiveRecord::Base
|
||||||
|
|
||||||
|
include Diaspora::Federated::Base
|
||||||
|
|
||||||
|
include Diaspora::Guid
|
||||||
|
include Diaspora::Relayable
|
||||||
|
belongs_to :poll
|
||||||
|
belongs_to :poll_answer, counter_cache: :vote_count
|
||||||
|
belongs_to :author, :class_name => 'Person', :foreign_key => :author_id
|
||||||
|
xml_attr :diaspora_handle
|
||||||
|
xml_attr :poll_answer_guid
|
||||||
|
xml_convention :underscore
|
||||||
|
validate :not_already_participated
|
||||||
|
|
||||||
|
def parent_class
|
||||||
|
Poll
|
||||||
|
end
|
||||||
|
|
||||||
|
def parent
|
||||||
|
self.poll
|
||||||
|
end
|
||||||
|
|
||||||
|
def poll_answer_guid
|
||||||
|
poll_answer.guid
|
||||||
|
end
|
||||||
|
|
||||||
|
def poll_answer_guid= new_poll_answer_guid
|
||||||
|
self.poll_answer = PollAnswer.where(:guid => new_poll_answer_guid).first
|
||||||
|
end
|
||||||
|
|
||||||
|
def parent= parent
|
||||||
|
self.poll = parent
|
||||||
|
end
|
||||||
|
|
||||||
|
def diaspora_handle
|
||||||
|
self.author.diaspora_handle
|
||||||
|
end
|
||||||
|
|
||||||
|
def diaspora_handle= nh
|
||||||
|
self.author = Webfinger.new(nh).fetch
|
||||||
|
end
|
||||||
|
|
||||||
|
def not_already_participated
|
||||||
|
return if poll.nil?
|
||||||
|
|
||||||
|
other_participations = PollParticipation.where(author_id: self.author.id, poll_id: self.poll.id).to_a-[self]
|
||||||
|
if other_participations.present?
|
||||||
|
self.errors.add(:poll, I18n.t("activerecord.errors.models.poll_participations.attributes.poll.already_participated"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Generator < Federated::Generator
|
||||||
|
def self.federated_class
|
||||||
|
PollParticipation
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(person, target, poll_answer)
|
||||||
|
@poll_answer = poll_answer
|
||||||
|
super(person, target)
|
||||||
|
end
|
||||||
|
|
||||||
|
def relayable_options
|
||||||
|
{:poll => @target.poll, :poll_answer => @poll_answer}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -72,6 +72,9 @@ class Post < ActiveRecord::Base
|
||||||
def address
|
def address
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def poll
|
||||||
|
end
|
||||||
|
|
||||||
def self.excluding_blocks(user)
|
def self.excluding_blocks(user)
|
||||||
people = user.blocks.map{|b| b.person_id}
|
people = user.blocks.map{|b| b.person_id}
|
||||||
scope = scoped
|
scope = scoped
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,13 @@ class StatusMessage < Post
|
||||||
xml_attr :raw_message
|
xml_attr :raw_message
|
||||||
xml_attr :photos, :as => [Photo]
|
xml_attr :photos, :as => [Photo]
|
||||||
xml_attr :location, :as => Location
|
xml_attr :location, :as => Location
|
||||||
|
xml_attr :poll, :as => Poll
|
||||||
|
|
||||||
has_many :photos, :dependent => :destroy, :foreign_key => :status_message_guid, :primary_key => :guid
|
has_many :photos, :dependent => :destroy, :foreign_key => :status_message_guid, :primary_key => :guid
|
||||||
|
|
||||||
has_one :location
|
has_one :location
|
||||||
|
has_one :poll, autosave: true
|
||||||
|
|
||||||
|
|
||||||
# a StatusMessage is federated before its photos are so presence_of_content() fails erroneously if no text is present
|
# a StatusMessage is federated before its photos are so presence_of_content() fails erroneously if no text is present
|
||||||
# therefore, we put the validation in a before_destory callback instead of a validation
|
# therefore, we put the validation in a before_destory callback instead of a validation
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ module User::SocialActions
|
||||||
Like::Generator.new(self, target).create!(opts)
|
Like::Generator.new(self, target).create!(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def participate_in_poll!(target, answer, opts={})
|
||||||
|
find_or_create_participation!(target)
|
||||||
|
PollParticipation::Generator.new(self, target, answer).create!(opts)
|
||||||
|
end
|
||||||
|
|
||||||
def reshare!(target, opts={})
|
def reshare!(target, opts={})
|
||||||
find_or_create_participation!(target)
|
find_or_create_participation!(target)
|
||||||
reshare = build_post(:reshare, :root_guid => target.guid)
|
reshare = build_post(:reshare, :root_guid => target.guid)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ class PostPresenter
|
||||||
:root => root,
|
:root => root,
|
||||||
:title => title,
|
:title => title,
|
||||||
:address => @post.address,
|
:address => @post.address,
|
||||||
|
:poll => @post.poll(),
|
||||||
|
:already_participated_in_poll => already_participated_in_poll,
|
||||||
|
|
||||||
:interactions => {
|
:interactions => {
|
||||||
:likes => [user_like].compact,
|
:likes => [user_like].compact,
|
||||||
|
|
@ -72,6 +74,14 @@ class PostPresenter
|
||||||
@current_user.present?
|
@current_user.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def already_participated_in_poll
|
||||||
|
if @post.poll
|
||||||
|
@post.poll.already_participated?(current_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class PostInteractionPresenter
|
class PostInteractionPresenter
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,4 @@
|
||||||
#publisher_mobile
|
#publisher_mobile
|
||||||
= submit_tag t('shared.publisher.share'), :class => 'btn primary', :id => "submit_new_message"
|
= submit_tag t('shared.publisher.share'), :class => 'btn primary', :id => "submit_new_message"
|
||||||
|
|
||||||
#publisher_photo_upload
|
#publisher_photo_upload
|
||||||
|
|
@ -28,12 +28,26 @@
|
||||||
%span#publisher-images
|
%span#publisher-images
|
||||||
%span.markdownIndications
|
%span.markdownIndications
|
||||||
!= t('shared.publisher.formatWithMarkdown', markdown_link: link_to(t('help.markdown'), 'https://diasporafoundation.org/formatting', target: :blank))
|
!= t('shared.publisher.formatWithMarkdown', markdown_link: link_to(t('help.markdown'), 'https://diasporafoundation.org/formatting', target: :blank))
|
||||||
|
#poll_creator.btn{:title => t('shared.publisher.poll.add_a_poll')}
|
||||||
|
%i.entypo.bar-graph{:class => "publisher_image"}
|
||||||
|
|
||||||
#locator.btn{:title => t('shared.publisher.get_location')}
|
#locator.btn{:title => t('shared.publisher.get_location')}
|
||||||
= image_tag 'icons/marker.png', :alt => t('shared.publisher.get_location').titleize, :class => 'publisher_image'
|
= image_tag 'icons/marker.png', :alt => t('shared.publisher.get_location').titleize, :class => 'publisher_image'
|
||||||
#file-upload.btn{:title => t('shared.publisher.upload_photos')}
|
#file-upload.btn{:title => t('shared.publisher.upload_photos')}
|
||||||
= image_tag 'icons/camera.png', :alt => t('shared.publisher.upload_photos').titleize, :class => 'publisher_image'
|
= image_tag 'icons/camera.png', :alt => t('shared.publisher.upload_photos').titleize, :class => 'publisher_image'
|
||||||
= hidden_field :location, :coords
|
= hidden_field :location, :coords
|
||||||
#location_container
|
#location_container
|
||||||
|
#poll_creator_wrapper
|
||||||
|
#poll_question_wrapper
|
||||||
|
%input{:id => 'poll_question', :placeholder => t('shared.publisher.poll.question'), :name => 'poll_question'}
|
||||||
|
.poll_answer
|
||||||
|
%span{:class => 'poll_answer_input_wrapper'}
|
||||||
|
%input{:class => 'poll_answer_input', :placeholder => t('shared.publisher.poll.option'), :name => 'poll_answers[]'}
|
||||||
|
%a{:class => 'remove_poll_answer', :title => t('shared.publisher.poll.remove_poll_answer')}
|
||||||
|
.icons-deletelabel
|
||||||
|
#add_poll_answer_wrapper
|
||||||
|
#add_poll_answer{:class => 'button creation'}
|
||||||
|
= t('shared.publisher.poll.add_poll_answer')
|
||||||
|
|
||||||
- if publisher_public
|
- if publisher_public
|
||||||
= hidden_field_tag 'aspect_ids[]', "public"
|
= hidden_field_tag 'aspect_ids[]', "public"
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,20 @@
|
||||||
%ul#photodropzone
|
%ul#photodropzone
|
||||||
.row-fluid#location_container
|
.row-fluid#location_container
|
||||||
= hidden_field :location, :coords
|
= hidden_field :location, :coords
|
||||||
|
.row-fluid#poll_creator_wrapper
|
||||||
|
#poll_question_wrapper{:class => "input-block-level"}
|
||||||
|
%input{:id => 'poll_question', :placeholder => t('shared.publisher.poll.question'), :name => 'poll_question', :class=> "form-control"}
|
||||||
|
.poll_answer
|
||||||
|
%input{:class => 'form-control poll_answer_input', :placeholder => t('shared.publisher.poll.option'), :name => 'poll_answers[]'}
|
||||||
|
.remove_poll_answer.btn.btn-link{:title => t('shared.publisher.poll.remove_poll_answer')}
|
||||||
|
%i.entypo.trash
|
||||||
|
#add_poll_answer_wrapper
|
||||||
|
#add_poll_answer{:class => 'btn btn-default'}
|
||||||
|
= t('shared.publisher.poll.add_poll_answer')
|
||||||
.row-fluid#button_container
|
.row-fluid#button_container
|
||||||
#publisher-images.pull-right
|
#publisher-images.pull-right
|
||||||
|
#poll_creator.btn.btn-link{:title => t('shared.publisher.poll.add_a_poll')}
|
||||||
|
%i.entypo.bar-graph
|
||||||
#file-upload.btn.btn-link{:title => t('shared.publisher.upload_photos')}
|
#file-upload.btn.btn-link{:title => t('shared.publisher.upload_photos')}
|
||||||
%i.entypo.camera.publisher_image
|
%i.entypo.camera.publisher_image
|
||||||
#locator.btn.btn-link{:title => t('shared.publisher.get_location')}
|
#locator.btn.btn-link{:title => t('shared.publisher.get_location')}
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,11 @@
|
||||||
attributes:
|
attributes:
|
||||||
from_id:
|
from_id:
|
||||||
taken: "is a duplicate of a pre-existing request."
|
taken: "is a duplicate of a pre-existing request."
|
||||||
|
poll:
|
||||||
|
attributes:
|
||||||
|
poll_answers:
|
||||||
|
not_enough_poll_answers: "Not enough poll options provided."
|
||||||
|
poll_participation:
|
||||||
|
attributes:
|
||||||
|
poll:
|
||||||
|
already_participated: "You've already participated in this poll!"
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,11 @@
|
||||||
attributes:
|
attributes:
|
||||||
from_id:
|
from_id:
|
||||||
taken: "is a duplicate of a pre-existing request."
|
taken: "is a duplicate of a pre-existing request."
|
||||||
|
poll:
|
||||||
|
attributes:
|
||||||
|
poll_answers:
|
||||||
|
not_enough_poll_answers: "Not enough poll options provided."
|
||||||
|
poll_participation:
|
||||||
|
attributes:
|
||||||
|
poll:
|
||||||
|
already_participated: "You've already participated in this poll!"
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,11 @@
|
||||||
attributes:
|
attributes:
|
||||||
from_id:
|
from_id:
|
||||||
taken: "is a duplicate of a pre-existing request."
|
taken: "is a duplicate of a pre-existing request."
|
||||||
|
poll:
|
||||||
|
attributes:
|
||||||
|
poll_answers:
|
||||||
|
not_enough_poll_answers: "Not enough poll options provided."
|
||||||
|
poll_participation:
|
||||||
|
attributes:
|
||||||
|
poll:
|
||||||
|
already_participated: "You've already participated in this poll!"
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,14 @@ en:
|
||||||
attributes:
|
attributes:
|
||||||
root_guid:
|
root_guid:
|
||||||
taken: "That good, huh? You've already reshared that post!"
|
taken: "That good, huh? You've already reshared that post!"
|
||||||
|
poll:
|
||||||
|
attributes:
|
||||||
|
poll_answers:
|
||||||
|
not_enough_poll_answers: "Not enough poll options provided."
|
||||||
|
poll_participation:
|
||||||
|
attributes:
|
||||||
|
poll:
|
||||||
|
already_participated: "You've already participated in this poll!"
|
||||||
error_messages:
|
error_messages:
|
||||||
helper:
|
helper:
|
||||||
invalid_fields: "Invalid Fields"
|
invalid_fields: "Invalid Fields"
|
||||||
|
|
@ -1037,6 +1045,12 @@ en:
|
||||||
hello: "Hey everyone, I'm #%{new_user_tag}. "
|
hello: "Hey everyone, I'm #%{new_user_tag}. "
|
||||||
i_like: "I'm interested in %{tags}. "
|
i_like: "I'm interested in %{tags}. "
|
||||||
invited_by: "Thanks for the invite, "
|
invited_by: "Thanks for the invite, "
|
||||||
|
poll:
|
||||||
|
remove_poll_answer: "Remove option"
|
||||||
|
add_poll_answer: "Add option"
|
||||||
|
add_a_poll: "Add a poll"
|
||||||
|
question: "Question"
|
||||||
|
option: "Option 1"
|
||||||
add_contact:
|
add_contact:
|
||||||
enter_a_diaspora_username: "Enter a diaspora* username:"
|
enter_a_diaspora_username: "Enter a diaspora* username:"
|
||||||
your_diaspora_username_is: "Your diaspora* username is: %{diaspora_handle}"
|
your_diaspora_username_is: "Your diaspora* username is: %{diaspora_handle}"
|
||||||
|
|
|
||||||
|
|
@ -172,3 +172,12 @@ en:
|
||||||
reshared: "Reshared"
|
reshared: "Reshared"
|
||||||
comment: "Comment"
|
comment: "Comment"
|
||||||
home: "HOME"
|
home: "HOME"
|
||||||
|
|
||||||
|
poll:
|
||||||
|
vote: "Vote"
|
||||||
|
result: "Result"
|
||||||
|
count:
|
||||||
|
one: "1 vote so far"
|
||||||
|
other: "<%=count%> votes so far"
|
||||||
|
show_result: "Show result"
|
||||||
|
close_result: "Hide result"
|
||||||
|
|
@ -31,11 +31,15 @@ Diaspora::Application.routes.draw do
|
||||||
get :interactions
|
get :interactions
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :poll_participations, :only => [:create]
|
||||||
|
|
||||||
resources :likes, :only => [:create, :destroy, :index ]
|
resources :likes, :only => [:create, :destroy, :index ]
|
||||||
resources :participations, :only => [:create, :destroy, :index]
|
resources :participations, :only => [:create, :destroy, :index]
|
||||||
resources :comments, :only => [:new, :create, :destroy, :index]
|
resources :comments, :only => [:new, :create, :destroy, :index]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
get 'p/:id' => 'posts#show', :as => 'short_post'
|
get 'p/:id' => 'posts#show', :as => 'short_post'
|
||||||
get 'posts/:id/iframe' => 'posts#iframe', :as => 'iframe'
|
get 'posts/:id/iframe' => 'posts#iframe', :as => 'iframe'
|
||||||
|
|
||||||
|
|
|
||||||
38
db/migrate/20140308154022_create_polls.rb
Normal file
38
db/migrate/20140308154022_create_polls.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
class CreatePolls < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
create_table :polls do |t|
|
||||||
|
t.string :question, :null => false
|
||||||
|
t.belongs_to :status_message, :null => false
|
||||||
|
t.boolean :status
|
||||||
|
t.string :guid
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
add_index :polls, :status_message_id
|
||||||
|
|
||||||
|
create_table :poll_answers do |t|
|
||||||
|
t.string :answer, :null => false
|
||||||
|
t.belongs_to :poll, :null => false
|
||||||
|
t.string :guid
|
||||||
|
t.integer :vote_count, :default => 0
|
||||||
|
end
|
||||||
|
add_index :poll_answers, :poll_id
|
||||||
|
|
||||||
|
create_table :poll_participations do |t|
|
||||||
|
t.belongs_to :poll_answer, :null => false
|
||||||
|
t.belongs_to :author, :null => false
|
||||||
|
t.belongs_to :poll, :null => false
|
||||||
|
t.string :guid
|
||||||
|
t.text :author_signature
|
||||||
|
t.text :parent_author_signature
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
add_index :poll_participations, :poll_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_table :polls
|
||||||
|
drop_table :poll_answers
|
||||||
|
drop_table :poll_participations
|
||||||
|
end
|
||||||
|
end
|
||||||
71
db/schema.rb
71
db/schema.rb
|
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended to check this file into your version control system.
|
# It's strongly recommended to check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(:version => 20140222162826) do
|
ActiveRecord::Schema.define(:version => 20140308154022) do
|
||||||
|
|
||||||
create_table "account_deletions", :force => true do |t|
|
create_table "account_deletions", :force => true do |t|
|
||||||
t.string "diaspora_handle"
|
t.string "diaspora_handle"
|
||||||
|
|
@ -283,6 +283,39 @@ ActiveRecord::Schema.define(:version => 20140222162826) do
|
||||||
t.datetime "updated_at", :null => false
|
t.datetime "updated_at", :null => false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "poll_answers", :force => true do |t|
|
||||||
|
t.string "answer", :null => false
|
||||||
|
t.integer "poll_id", :null => false
|
||||||
|
t.string "guid"
|
||||||
|
t.integer "vote_count", :default => 0
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "poll_answers", ["poll_id"], :name => "index_poll_answers_on_poll_id"
|
||||||
|
|
||||||
|
create_table "poll_participations", :force => true do |t|
|
||||||
|
t.integer "poll_answer_id", :null => false
|
||||||
|
t.integer "author_id", :null => false
|
||||||
|
t.integer "poll_id", :null => false
|
||||||
|
t.string "guid"
|
||||||
|
t.text "author_signature"
|
||||||
|
t.text "parent_author_signature"
|
||||||
|
t.datetime "created_at", :null => false
|
||||||
|
t.datetime "updated_at", :null => false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "poll_participations", ["poll_id"], :name => "index_poll_participations_on_poll_id"
|
||||||
|
|
||||||
|
create_table "polls", :force => true do |t|
|
||||||
|
t.string "question", :null => false
|
||||||
|
t.integer "status_message_id", :null => false
|
||||||
|
t.boolean "status"
|
||||||
|
t.string "guid"
|
||||||
|
t.datetime "created_at", :null => false
|
||||||
|
t.datetime "updated_at", :null => false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "polls", ["status_message_id"], :name => "index_polls_on_status_message_id"
|
||||||
|
|
||||||
create_table "post_reports", :force => true do |t|
|
create_table "post_reports", :force => true do |t|
|
||||||
t.integer "post_id", :null => false
|
t.integer "post_id", :null => false
|
||||||
t.string "user_id"
|
t.string "user_id"
|
||||||
|
|
@ -502,36 +535,36 @@ ActiveRecord::Schema.define(:version => 20140222162826) do
|
||||||
add_index "users", ["invitation_token"], :name => "index_users_on_invitation_token"
|
add_index "users", ["invitation_token"], :name => "index_users_on_invitation_token"
|
||||||
add_index "users", ["username"], :name => "index_users_on_username", :unique => true
|
add_index "users", ["username"], :name => "index_users_on_username", :unique => true
|
||||||
|
|
||||||
add_foreign_key "aspect_memberships", "aspects", :name => "aspect_memberships_aspect_id_fk", :dependent => :delete
|
add_foreign_key "aspect_memberships", "aspects", name: "aspect_memberships_aspect_id_fk", dependent: :delete
|
||||||
add_foreign_key "aspect_memberships", "contacts", :name => "aspect_memberships_contact_id_fk", :dependent => :delete
|
add_foreign_key "aspect_memberships", "contacts", name: "aspect_memberships_contact_id_fk", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "aspect_visibilities", "aspects", :name => "aspect_visibilities_aspect_id_fk", :dependent => :delete
|
add_foreign_key "aspect_visibilities", "aspects", name: "aspect_visibilities_aspect_id_fk", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "comments", "people", :name => "comments_author_id_fk", :column => "author_id", :dependent => :delete
|
add_foreign_key "comments", "people", name: "comments_author_id_fk", column: "author_id", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "contacts", "people", :name => "contacts_person_id_fk", :dependent => :delete
|
add_foreign_key "contacts", "people", name: "contacts_person_id_fk", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "conversation_visibilities", "conversations", :name => "conversation_visibilities_conversation_id_fk", :dependent => :delete
|
add_foreign_key "conversation_visibilities", "conversations", name: "conversation_visibilities_conversation_id_fk", dependent: :delete
|
||||||
add_foreign_key "conversation_visibilities", "people", :name => "conversation_visibilities_person_id_fk", :dependent => :delete
|
add_foreign_key "conversation_visibilities", "people", name: "conversation_visibilities_person_id_fk", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "conversations", "people", :name => "conversations_author_id_fk", :column => "author_id", :dependent => :delete
|
add_foreign_key "conversations", "people", name: "conversations_author_id_fk", column: "author_id", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "invitations", "users", :name => "invitations_recipient_id_fk", :column => "recipient_id", :dependent => :delete
|
add_foreign_key "invitations", "users", name: "invitations_recipient_id_fk", column: "recipient_id", dependent: :delete
|
||||||
add_foreign_key "invitations", "users", :name => "invitations_sender_id_fk", :column => "sender_id", :dependent => :delete
|
add_foreign_key "invitations", "users", name: "invitations_sender_id_fk", column: "sender_id", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "likes", "people", :name => "likes_author_id_fk", :column => "author_id", :dependent => :delete
|
add_foreign_key "likes", "people", name: "likes_author_id_fk", column: "author_id", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "messages", "conversations", :name => "messages_conversation_id_fk", :dependent => :delete
|
add_foreign_key "messages", "conversations", name: "messages_conversation_id_fk", dependent: :delete
|
||||||
add_foreign_key "messages", "people", :name => "messages_author_id_fk", :column => "author_id", :dependent => :delete
|
add_foreign_key "messages", "people", name: "messages_author_id_fk", column: "author_id", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "notification_actors", "notifications", :name => "notification_actors_notification_id_fk", :dependent => :delete
|
add_foreign_key "notification_actors", "notifications", name: "notification_actors_notification_id_fk", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "posts", "people", :name => "posts_author_id_fk", :column => "author_id", :dependent => :delete
|
add_foreign_key "posts", "people", name: "posts_author_id_fk", column: "author_id", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "profiles", "people", :name => "profiles_person_id_fk", :dependent => :delete
|
add_foreign_key "profiles", "people", name: "profiles_person_id_fk", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "services", "users", :name => "services_user_id_fk", :dependent => :delete
|
add_foreign_key "services", "users", name: "services_user_id_fk", dependent: :delete
|
||||||
|
|
||||||
add_foreign_key "share_visibilities", "contacts", :name => "post_visibilities_contact_id_fk", :dependent => :delete
|
add_foreign_key "share_visibilities", "contacts", name: "post_visibilities_contact_id_fk", dependent: :delete
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
65
features/desktop/post_with_a_poll.feature
Normal file
65
features/desktop/post_with_a_poll.feature
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
@javascript
|
||||||
|
Feature: posting with a poll
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given following users exist:
|
||||||
|
| username |
|
||||||
|
| bob |
|
||||||
|
And I sign in as "bob@bob.bob"
|
||||||
|
And I am on the home page
|
||||||
|
|
||||||
|
Scenario: expanding the publisher
|
||||||
|
Given "#poll_creator_wrapper" is hidden
|
||||||
|
When I expand the publisher
|
||||||
|
Then I should see an element "#poll_creator"
|
||||||
|
|
||||||
|
Scenario: expanding the poll creator
|
||||||
|
Given "#poll_creator_wrapper" is hidden
|
||||||
|
When I expand the publisher
|
||||||
|
And I press the element "#poll_creator"
|
||||||
|
Then I should see an element "#poll_creator_wrapper"
|
||||||
|
|
||||||
|
Scenario: adding option to poll
|
||||||
|
Given "#poll_creator_wrapper" is hidden
|
||||||
|
When I expand the publisher
|
||||||
|
And I press the element "#poll_creator"
|
||||||
|
And I press the element "#add_poll_answer"
|
||||||
|
Then I should see 3 options
|
||||||
|
|
||||||
|
Scenario: delete an option
|
||||||
|
Given "#poll_creator_wrapper" is hidden
|
||||||
|
When I expand the publisher
|
||||||
|
And I press the element "#poll_creator"
|
||||||
|
And I delete the first option
|
||||||
|
Then I should see 1 option
|
||||||
|
And I should not see a remove icon
|
||||||
|
|
||||||
|
Scenario: post with an attached poll
|
||||||
|
Given I expand the publisher
|
||||||
|
And I press the element "#poll_creator"
|
||||||
|
When I fill in the following:
|
||||||
|
| status_message_fake_text | I am eating yogurt |
|
||||||
|
| poll_question | What kind of yogurt do you like? |
|
||||||
|
And I fill in the following for the options:
|
||||||
|
| normal |
|
||||||
|
| not normal |
|
||||||
|
And I press "Share"
|
||||||
|
Then I should see a ".poll_form" within ".stream_element"
|
||||||
|
And I should see a "form" within ".stream_element"
|
||||||
|
|
||||||
|
Scenario: vote for an option
|
||||||
|
Given I expand the publisher
|
||||||
|
And I press the element "#poll_creator"
|
||||||
|
When I fill in the following:
|
||||||
|
| status_message_fake_text | I am eating yogurt |
|
||||||
|
| poll_question | What kind of yogurt do you like? |
|
||||||
|
And I fill in the following for the options:
|
||||||
|
| normal |
|
||||||
|
| not normal |
|
||||||
|
And I press "Share"
|
||||||
|
|
||||||
|
And I check the first option
|
||||||
|
And I press "Vote" within ".stream_element"
|
||||||
|
Then I should see an element ".poll_progress_bar"
|
||||||
|
And I should see an element ".percentage"
|
||||||
|
And I should see "1 vote so far" within ".poll_statistic"
|
||||||
32
features/step_definitions/post_with_poll_steps.rb
Normal file
32
features/step_definitions/post_with_poll_steps.rb
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
Then /^I should see ([1-9]+) options?$/ do |number|
|
||||||
|
find("#poll_creator_wrapper").all(".poll_answer").count.should eql(number.to_i)
|
||||||
|
end
|
||||||
|
|
||||||
|
And /^I delete the first option$/ do
|
||||||
|
find("#poll_creator_wrapper").all(".poll_answer .remove_poll_answer").first.click
|
||||||
|
end
|
||||||
|
|
||||||
|
And /^I should not see a remove icon$/ do
|
||||||
|
page.should_not have_css(".remove_poll_answer")
|
||||||
|
end
|
||||||
|
|
||||||
|
When /^I fill in the following for the options:$/ do |table|
|
||||||
|
i = 0
|
||||||
|
table.raw.flatten.each do |value|
|
||||||
|
all(".poll_answer_input")[i].set(value)
|
||||||
|
i+=1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
When /^I check the first option$/ do
|
||||||
|
sleep 1
|
||||||
|
first(".poll_form input").click
|
||||||
|
end
|
||||||
|
|
||||||
|
And /^I press the element "([^"]*)"$/ do |selector|
|
||||||
|
find(selector).click
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should see an element "([^"]*)"$/ do |selector|
|
||||||
|
page.should have_css(selector)
|
||||||
|
end
|
||||||
|
|
@ -11,8 +11,6 @@ module Federated
|
||||||
FEDERATION_LOGGER.info("user:#{@user.id} dispatching #{relayable.class}:#{relayable.guid}")
|
FEDERATION_LOGGER.info("user:#{@user.id} dispatching #{relayable.class}:#{relayable.guid}")
|
||||||
Postzord::Dispatcher.defer_build_and_post(@user, relayable)
|
Postzord::Dispatcher.defer_build_and_post(@user, relayable)
|
||||||
relayable
|
relayable
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,12 @@ FactoryGirl.define do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
factory(:status_message_with_poll, :parent => :status_message) do
|
||||||
|
after(:build) do |sm|
|
||||||
|
FactoryGirl.create(:poll, :status_message => sm)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
factory(:status_message_with_photo, :parent => :status_message) do
|
factory(:status_message_with_photo, :parent => :status_message) do
|
||||||
sequence(:text) { |n| "There are #{n} ninjas in this photo." }
|
sequence(:text) { |n| "There are #{n} ninjas in this photo." }
|
||||||
after(:build) do |sm|
|
after(:build) do |sm|
|
||||||
|
|
@ -107,6 +113,18 @@ FactoryGirl.define do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
factory(:poll) do
|
||||||
|
sequence(:question) { |n| "What do you think about #{n} ninjas?" }
|
||||||
|
after(:build) do |p|
|
||||||
|
p.poll_answers << FactoryGirl.build(:poll_answer)
|
||||||
|
p.poll_answers << FactoryGirl.build(:poll_answer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
factory(:poll_answer) do
|
||||||
|
sequence(:answer) { |n| "#{n} questionmarks" }
|
||||||
|
end
|
||||||
|
|
||||||
factory(:photo) do
|
factory(:photo) do
|
||||||
sequence(:random_string) {|n| SecureRandom.hex(10) }
|
sequence(:random_string) {|n| SecureRandom.hex(10) }
|
||||||
association :author, :factory => :person
|
association :author, :factory => :person
|
||||||
|
|
|
||||||
45
spec/javascripts/app/views/poll_view_spec.js
Normal file
45
spec/javascripts/app/views/poll_view_spec.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
describe("app.views.Poll", function(){
|
||||||
|
beforeEach(function() {
|
||||||
|
loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
|
||||||
|
this.view = new app.views.Poll({ "model" : factory.postWithPoll()});
|
||||||
|
this.view.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("setProgressBar", function(){
|
||||||
|
it("sets the progress bar according to the voting result", function(){
|
||||||
|
var percentage = (this.view.poll.poll_answers[0].vote_count / this.view.poll.participation_count)*100;
|
||||||
|
expect(this.view.$('.poll_progress_bar:first').css('width')).toBe(this.view.progressBarFactor * percentage+"px");
|
||||||
|
expect(this.view.$(".percentage:first").text()).toBe(" - " + percentage + "%");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("toggleResult", function(){
|
||||||
|
it("toggles the progress bar and result", function(){
|
||||||
|
expect(this.view.$('.poll_progress_bar_wrapper:first').css('display')).toBe("none");
|
||||||
|
this.view.toggleResult(null);
|
||||||
|
expect(this.view.$('.poll_progress_bar_wrapper:first').css('display')).toBe("block");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateCounter", function(){
|
||||||
|
it("updates the counter after a vote", function(){
|
||||||
|
var pc = this.view.poll.participation_count;
|
||||||
|
var answerCount = this.view.poll.poll_answers[0].vote_count;
|
||||||
|
this.view.updateCounter(1);
|
||||||
|
expect(this.view.poll.participation_count).toBe(pc+1);
|
||||||
|
expect(this.view.poll.poll_answers[0].vote_count).toBe(answerCount+1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("vote", function(){
|
||||||
|
it("checks the ajax call for voting", function(){
|
||||||
|
spyOn($, "ajax");
|
||||||
|
var radio = this.view.$('input[name="vote"]:first');
|
||||||
|
radio.attr('checked', true);
|
||||||
|
this.view.vote({'target' : radio});
|
||||||
|
var obj = JSON.parse($.ajax.mostRecentCall.args[0].data);
|
||||||
|
expect(obj.poll_id).toBe(this.view.poll.poll_id);
|
||||||
|
expect(obj.poll_answer_id).toBe(this.view.poll.poll_answers[0].id);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
@ -277,6 +277,40 @@ describe("app.views.Publisher", function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context("poll", function(){
|
||||||
|
beforeEach(function() {
|
||||||
|
loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
|
||||||
|
spec.loadFixture("aspects_index");
|
||||||
|
$("#poll_creator_wrapper").hide(); //css not loaded? :-/
|
||||||
|
this.view = new app.views.Publisher();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#showPollCreator', function(){
|
||||||
|
it("Shows the poll creator", function(){
|
||||||
|
expect($("#poll_creator_wrapper").is(":visible")).toBe(false);
|
||||||
|
this.view.showPollCreator();
|
||||||
|
expect($("#poll_creator_wrapper").is(":visible")).toBe(true);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#addPollAnswer", function(){
|
||||||
|
it("should add a poll answer if clicked", function(){
|
||||||
|
expect($("#poll_creator_wrapper .poll_answer").length).toBe(2);
|
||||||
|
this.view.addPollAnswer();
|
||||||
|
expect($("#poll_creator_wrapper .poll_answer").length).toBe(3);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#removePollAnswer", function(){
|
||||||
|
it("should remove a poll answer if clicked", function(){
|
||||||
|
var answer_count = $('.poll_answer').length;
|
||||||
|
var evt = {'currentTarget' : $("#poll_creator_wrapper .poll_answer:first .remove_poll_answer")};
|
||||||
|
this.view.removePollAnswer(evt);
|
||||||
|
expect($("#poll_creator_wrapper .poll_answer").length).toBe(answer_count-1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
context("locator", function() {
|
context("locator", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
// should be jasmine helper
|
// should be jasmine helper
|
||||||
|
|
|
||||||
|
|
@ -124,11 +124,31 @@ factory = {
|
||||||
return new app.models.Post(_.extend(defaultAttrs, overrides))
|
return new app.models.Post(_.extend(defaultAttrs, overrides))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
postWithPoll : function(overrides) {
|
||||||
|
defaultAttrs = _.extend(factory.postAttrs(), {"author" : this.author()});
|
||||||
|
defaultAttrs = _.extend(defaultAttrs, {"already_participated_in_poll" : false});
|
||||||
|
defaultAttrs = _.extend(defaultAttrs, {"poll" : factory.poll()});
|
||||||
|
return new app.models.Post(_.extend(defaultAttrs, overrides));
|
||||||
|
},
|
||||||
|
|
||||||
statusMessage : function(overrides){
|
statusMessage : function(overrides){
|
||||||
//intentionally doesn't have an author to mirror creation process, maybe we should change the creation process
|
//intentionally doesn't have an author to mirror creation process, maybe we should change the creation process
|
||||||
return new app.models.StatusMessage(_.extend(factory.postAttrs(), overrides))
|
return new app.models.StatusMessage(_.extend(factory.postAttrs(), overrides))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
poll: function(overrides){
|
||||||
|
return {
|
||||||
|
"question" : "This is an awesome question",
|
||||||
|
"created_at" : "2012-01-03T19:53:13Z",
|
||||||
|
"author" : this.author(),
|
||||||
|
"post_id" : 1,
|
||||||
|
"poll_answers" : [{"answer" : "yes", "id" : 1, "vote_count" : 9}, {"answer" : "no", "id" : 2, "vote_count" : 1}],
|
||||||
|
"guid" : this.guid(),
|
||||||
|
"poll_id": this.id.next(),
|
||||||
|
"participation_count" : 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
comment: function(overrides) {
|
comment: function(overrides) {
|
||||||
var defaultAttrs = {
|
var defaultAttrs = {
|
||||||
"text" : "This is an awesome comment!",
|
"text" : "This is an awesome comment!",
|
||||||
|
|
|
||||||
21
spec/models/poll_answer_spec.rb
Normal file
21
spec/models/poll_answer_spec.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe PollAnswer do
|
||||||
|
before do
|
||||||
|
@status = FactoryGirl.create(:status_message_with_poll)
|
||||||
|
@user = alice
|
||||||
|
@answer = @status.poll.poll_answers.first
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'counter cache' do
|
||||||
|
it 'increments the counter cache on the answer' do
|
||||||
|
lambda {
|
||||||
|
alice.participate_in_poll!(@status, @answer)
|
||||||
|
}.should change{
|
||||||
|
@answer.reload.vote_count
|
||||||
|
}.by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
102
spec/models/poll_participation_spec.rb
Normal file
102
spec/models/poll_participation_spec.rb
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
require Rails.root.join("spec", "shared_behaviors", "relayable")
|
||||||
|
|
||||||
|
describe PollParticipation do
|
||||||
|
|
||||||
|
before do
|
||||||
|
@alices_aspect = alice.aspects.first
|
||||||
|
@status = bob.post(:status_message, :text => "hello", :to => bob.aspects.first.id)
|
||||||
|
@poll = Poll.new(:question => 'Who is in charge?')
|
||||||
|
@poll.poll_answers.build(:answer => "a")
|
||||||
|
@poll.poll_answers.build(:answer => "b")
|
||||||
|
@status.poll = @poll
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'validation' do
|
||||||
|
it 'forbids multiple participations in the same poll' do
|
||||||
|
expect {
|
||||||
|
2.times do |run|
|
||||||
|
bob.participate_in_poll!(@status, @poll.poll_answers.first)
|
||||||
|
end
|
||||||
|
}.to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows a one time participation in a poll' do
|
||||||
|
expect {
|
||||||
|
bob.participate_in_poll!(@status, @poll.poll_answers.first)
|
||||||
|
}.to_not raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'xml' do
|
||||||
|
before do
|
||||||
|
@poll_participant = FactoryGirl.create(:user)
|
||||||
|
@poll_participant_aspect = @poll_participant.aspects.create(:name => "bruisers")
|
||||||
|
connect_users(alice, @alices_aspect, @poll_participant, @poll_participant_aspect)
|
||||||
|
@poll = Poll.new(:question => "hi")
|
||||||
|
@poll.poll_answers.build(:answer => "a")
|
||||||
|
@poll.poll_answers.build(:answer => "b")
|
||||||
|
@post = alice.post :status_message, :text => "hello", :to => @alices_aspect.id
|
||||||
|
@post.poll = @poll
|
||||||
|
@poll_participation = @poll_participant.participate_in_poll!(@post, @poll.poll_answers.first)
|
||||||
|
@xml = @poll_participation.to_xml.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'serializes the class name' do
|
||||||
|
@xml.include?(PollParticipation.name.underscore.to_s).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'serializes the sender handle' do
|
||||||
|
@xml.include?(@poll_participation.diaspora_handle).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'serializes the poll_guid' do
|
||||||
|
@xml.should include(@poll.guid)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'serializes the poll_answer_guid' do
|
||||||
|
@xml.should include(@poll_participation.poll_answer.guid)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'marshalling' do
|
||||||
|
before do
|
||||||
|
@marshalled_poll_participation = PollParticipation.from_xml(@xml)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'marshals the author' do
|
||||||
|
@marshalled_poll_participation.author.should == @poll_participant.person
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'marshals the answer' do
|
||||||
|
@marshalled_poll_participation.poll_answer.should == @poll_participation.poll_answer
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'marshals the poll' do
|
||||||
|
@marshalled_poll_participation.poll.should == @poll
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'it is relayable' do
|
||||||
|
before do
|
||||||
|
@local_luke, @local_leia, @remote_raphael = set_up_friends
|
||||||
|
@remote_parent = FactoryGirl.build(:status_message_with_poll, :author => @remote_raphael)
|
||||||
|
|
||||||
|
@local_parent = @local_luke.post :status_message, :text => "hi", :to => @local_luke.aspects.first
|
||||||
|
@poll2 = Poll.new(:question => 'Who is now in charge?')
|
||||||
|
@poll2.poll_answers.build(:answer => "a")
|
||||||
|
@poll2.poll_answers.build(:answer => "b")
|
||||||
|
@local_parent.poll = @poll2
|
||||||
|
|
||||||
|
@object_by_parent_author = @local_luke.participate_in_poll!(@local_parent, @poll2.poll_answers.first)
|
||||||
|
@object_by_recipient = @local_leia.participate_in_poll!(@local_parent, @poll2.poll_answers.first)
|
||||||
|
@dup_object_by_parent_author = @object_by_parent_author.dup
|
||||||
|
|
||||||
|
@object_on_remote_parent = @local_luke.participate_in_poll!(@remote_parent, @remote_parent.poll.poll_answers.first)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:build_object) { PollParticipation::Generator.new(alice, @status, @poll.poll_answers.first).build }
|
||||||
|
it_should_behave_like 'it is relayable'
|
||||||
|
end
|
||||||
|
end
|
||||||
20
spec/models/poll_spec.rb
Normal file
20
spec/models/poll_spec.rb
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Poll do
|
||||||
|
before do
|
||||||
|
@poll = Poll.new(:question => "What do you think about apples?")
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'validation' do
|
||||||
|
it 'should not create a poll when it has less than two answers' do
|
||||||
|
@poll.poll_answers.build(:answer => '1')
|
||||||
|
@poll.should_not be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should create a poll when it has more than two answers' do
|
||||||
|
@poll.poll_answers.build(:answer => '1')
|
||||||
|
@poll.poll_answers.build(:answer => '2')
|
||||||
|
@poll.should be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -350,6 +350,35 @@ STR
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a poll' do
|
||||||
|
before do
|
||||||
|
@message.poll = FactoryGirl.create(:poll, :status_message => @message)
|
||||||
|
@xml = @message.to_xml.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'serializes the poll' do
|
||||||
|
@xml.should include "poll"
|
||||||
|
@xml.should include "question"
|
||||||
|
@xml.should include "poll_answer"
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".from_xml" do
|
||||||
|
before do
|
||||||
|
@marshalled = StatusMessage.from_xml(@xml)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'marshals the poll' do
|
||||||
|
@marshalled.poll.should be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'marshals the poll answers' do
|
||||||
|
@marshalled.poll.poll_answers.size.should == 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#after_dispatch' do
|
describe '#after_dispatch' do
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ describe User::SocialActions do
|
||||||
alice.participations.last.target.should == @status
|
alice.participations.last.target.should == @status
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates the like" do
|
it "creates the comment" do
|
||||||
lambda{ alice.comment!(@status, "bro") }.should change(Comment, :count).by(1)
|
lambda{ alice.comment!(@status, "bro") }.should change(Comment, :count).by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -83,4 +83,31 @@ describe User::SocialActions do
|
||||||
@status.reload.likes.should == likes
|
@status.reload.likes.should == likes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'User#participate_in_poll!' do
|
||||||
|
before do
|
||||||
|
@bobs_aspect = bob.aspects.where(:name => "generic").first
|
||||||
|
@status = bob.post(:status_message, :text => "hello", :to => @bobs_aspect.id)
|
||||||
|
@poll = FactoryGirl.create(:poll, :status_message => @status)
|
||||||
|
@answer = @poll.poll_answers.first
|
||||||
|
end
|
||||||
|
|
||||||
|
it "federates" do
|
||||||
|
Participation::Generator.any_instance.stub(:create!)
|
||||||
|
Postzord::Dispatcher.should_receive(:defer_build_and_post)
|
||||||
|
alice.participate_in_poll!(@status, @answer)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a partcipation" do
|
||||||
|
lambda{ alice.participate_in_poll!(@status, @answer) }.should change(Participation, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates the poll participation" do
|
||||||
|
lambda{ alice.participate_in_poll!(@status, @answer) }.should change(PollParticipation, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets the poll answer id" do
|
||||||
|
alice.participate_in_poll!(@status, @answer).poll_answer.should == @answer
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
Loading…
Reference in a new issue