droom 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +23 -0
- data/Rakefile +27 -0
- data/app/assets/images/droom/arrows.png +0 -0
- data/app/assets/images/droom/big_icons.png +0 -0
- data/app/assets/images/droom/blueblob.png +0 -0
- data/app/assets/images/droom/close.png +0 -0
- data/app/assets/images/droom/cross.png +0 -0
- data/app/assets/images/droom/download.png +0 -0
- data/app/assets/images/droom/greyblob.png +0 -0
- data/app/assets/images/droom/help/goodreader.jpg +0 -0
- data/app/assets/images/droom/ical.png +0 -0
- data/app/assets/images/droom/icons.png +0 -0
- data/app/assets/images/droom/medium_icons.png +0 -0
- data/app/assets/images/droom/medium_object_icons.png +0 -0
- data/app/assets/images/droom/minimonth.png +0 -0
- data/app/assets/images/droom/minisymbols.png +0 -0
- data/app/assets/images/droom/object_icons.png +0 -0
- data/app/assets/images/droom/pinkblob.png +0 -0
- data/app/assets/images/droom/place_busy.png +0 -0
- data/app/assets/images/droom/place_quiet.png +0 -0
- data/app/assets/images/droom/plus_bullet.png +0 -0
- data/app/assets/images/droom/search.png +0 -0
- data/app/assets/images/droom/setup.png +0 -0
- data/app/assets/images/droom/small_go.png +0 -0
- data/app/assets/images/droom/smallbullet.png +0 -0
- data/app/assets/images/droom/smallspinner.gif +0 -0
- data/app/assets/images/droom/spinner.gif +0 -0
- data/app/assets/images/droom/spr_toolbar_icons_r2.png +0 -0
- data/app/assets/images/droom/tablesort.png +0 -0
- data/app/assets/images/droom/tick.png +0 -0
- data/app/assets/images/droom/tinysymbols.png +0 -0
- data/app/assets/images/droom/twister.png +0 -0
- data/app/assets/images/droom/vcard.png +0 -0
- data/app/assets/images/droom/venue_bullet.png +0 -0
- data/app/assets/javascripts/droom.js.coffee +88 -0
- data/app/assets/javascripts/droom/calendar.js.coffee +121 -0
- data/app/assets/javascripts/droom/drag_sort.js.coffee +22 -0
- data/app/assets/javascripts/droom/forms.js.coffee +746 -0
- data/app/assets/javascripts/droom/lib/extensions.js.coffee +60 -0
- data/app/assets/javascripts/droom/lib/jquery.animate-colors.js +109 -0
- data/app/assets/javascripts/droom/lib/jquery.cookie.js +71 -0
- data/app/assets/javascripts/droom/lib/jquery.sortable.js +97 -0
- data/app/assets/javascripts/droom/lib/kalendae.js +1692 -0
- data/app/assets/javascripts/droom/lib/modernizr.js +4 -0
- data/app/assets/javascripts/droom/lib/parser_rules/advanced.js +553 -0
- data/app/assets/javascripts/droom/lib/parser_rules/simple.js +32 -0
- data/app/assets/javascripts/droom/lib/wysihtml5.js +9550 -0
- data/app/assets/javascripts/droom/map.js.coffee +123 -0
- data/app/assets/javascripts/droom/sort.js.coffee +126 -0
- data/app/assets/javascripts/droom/suggester.js.coffee +230 -0
- data/app/assets/stylesheets/droom.css.sass +1151 -0
- data/app/assets/stylesheets/lib/_kalendae.css.sass +142 -0
- data/app/assets/stylesheets/lib/_toolbar.css.sass +192 -0
- data/app/controllers/droom/dashboard_controller.rb +27 -0
- data/app/controllers/droom/document_attachments_controller.rb +19 -0
- data/app/controllers/droom/documents_controller.rb +116 -0
- data/app/controllers/droom/engine_controller.rb +43 -0
- data/app/controllers/droom/events_controller.rb +120 -0
- data/app/controllers/droom/group_invitations_controller.rb +47 -0
- data/app/controllers/droom/groups_controller.rb +67 -0
- data/app/controllers/droom/invitations_controller.rb +43 -0
- data/app/controllers/droom/memberships_controller.rb +47 -0
- data/app/controllers/droom/pages_controller.rb +61 -0
- data/app/controllers/droom/people_controller.rb +92 -0
- data/app/controllers/droom/suggestions_controller.rb +58 -0
- data/app/controllers/droom/venues_controller.rb +39 -0
- data/app/helpers/droom/droom_helper.rb +74 -0
- data/app/models/droom/agenda_category.rb +8 -0
- data/app/models/droom/category.rb +27 -0
- data/app/models/droom/document.rb +110 -0
- data/app/models/droom/document_attachment.rb +71 -0
- data/app/models/droom/document_link.rb +31 -0
- data/app/models/droom/event.rb +409 -0
- data/app/models/droom/event_set.rb +6 -0
- data/app/models/droom/group.rb +66 -0
- data/app/models/droom/group_invitation.rb +23 -0
- data/app/models/droom/invitation.rb +30 -0
- data/app/models/droom/membership.rb +27 -0
- data/app/models/droom/page.rb +26 -0
- data/app/models/droom/person.rb +302 -0
- data/app/models/droom/personal_document.rb +98 -0
- data/app/models/droom/recurrence_rule.rb +82 -0
- data/app/models/droom/venue.rb +125 -0
- data/app/views/droom/dashboard/_marginalia.html.haml +3 -0
- data/app/views/droom/dashboard/_my_future_events.html.haml +9 -0
- data/app/views/droom/dashboard/_my_group_documents.html.haml +6 -0
- data/app/views/droom/dashboard/_my_past_events.haml +6 -0
- data/app/views/droom/dashboard/index.html.haml +5 -0
- data/app/views/droom/documents/_created.html.haml +2 -0
- data/app/views/droom/documents/_document.html.haml +14 -0
- data/app/views/droom/documents/_document_line.html.haml +2 -0
- data/app/views/droom/documents/_documents_list.html.haml +31 -0
- data/app/views/droom/documents/_documents_table.html.haml +13 -0
- data/app/views/droom/documents/_event_document_form.html.haml +15 -0
- data/app/views/droom/documents/_form.html.haml +22 -0
- data/app/views/droom/documents/_listing.html.haml +8 -0
- data/app/views/droom/documents/_suggested.html.haml +9 -0
- data/app/views/droom/documents/_table_document.html.haml +31 -0
- data/app/views/droom/documents/edit.html.haml +1 -0
- data/app/views/droom/documents/index.html.haml +29 -0
- data/app/views/droom/documents/new.html.haml +1 -0
- data/app/views/droom/errors/bang.html.haml +12 -0
- data/app/views/droom/errors/not_allowed.html.haml +12 -0
- data/app/views/droom/errors/not_found.html.haml +12 -0
- data/app/views/droom/events/_attachment.html.haml +1 -0
- data/app/views/droom/events/_attachment_list.html.haml +4 -0
- data/app/views/droom/events/_calendar.html.haml +54 -0
- data/app/views/droom/events/_created.html.haml +2 -0
- data/app/views/droom/events/_event.html.haml +77 -0
- data/app/views/droom/events/_event_line.html.haml +13 -0
- data/app/views/droom/events/_events.html.haml +2 -0
- data/app/views/droom/events/_form.html.haml +35 -0
- data/app/views/droom/events/_invitations.html.haml +20 -0
- data/app/views/droom/events/_other_page_parts.html.haml +0 -0
- data/app/views/droom/events/_popup_event.html.haml +6 -0
- data/app/views/droom/events/_suggested.html.haml +12 -0
- data/app/views/droom/events/_views.html.haml +7 -0
- data/app/views/droom/events/edit.html.haml +1 -0
- data/app/views/droom/events/index.html.haml +12 -0
- data/app/views/droom/events/index.rss.builder +20 -0
- data/app/views/droom/events/new.html.haml +1 -0
- data/app/views/droom/events/show.html.haml +4 -0
- data/app/views/droom/group_invitations/_attending_groups.html.haml +8 -0
- data/app/views/droom/group_invitations/_created.html.haml +3 -0
- data/app/views/droom/group_invitations/_form.html.haml +4 -0
- data/app/views/droom/group_invitations/new.html.haml +1 -0
- data/app/views/droom/groups/_created.html.haml +3 -0
- data/app/views/droom/groups/_form.html.haml +12 -0
- data/app/views/droom/groups/_group.html.haml +11 -0
- data/app/views/droom/groups/_groups.html.haml +3 -0
- data/app/views/droom/groups/edit.html.haml +1 -0
- data/app/views/droom/groups/index.html.haml +7 -0
- data/app/views/droom/groups/show.html.haml +13 -0
- data/app/views/droom/invitations/_attending_people.html.haml +8 -0
- data/app/views/droom/invitations/_created.html.haml +3 -0
- data/app/views/droom/invitations/_form.html.haml +5 -0
- data/app/views/droom/invitations/new.html.haml +1 -0
- data/app/views/droom/memberships/_button.html.haml +11 -0
- data/app/views/droom/memberships/_created.html.haml +4 -0
- data/app/views/droom/memberships/_form.html.haml +7 -0
- data/app/views/droom/memberships/_member.html.haml +33 -0
- data/app/views/droom/memberships/_memberships.html.haml +4 -0
- data/app/views/droom/pages/_contents.html.haml +10 -0
- data/app/views/droom/pages/_form.html.haml +36 -0
- data/app/views/droom/pages/_full_page.html.haml +17 -0
- data/app/views/droom/pages/_page.html.haml +5 -0
- data/app/views/droom/pages/_pages.html.haml +2 -0
- data/app/views/droom/pages/admin.html.haml +24 -0
- data/app/views/droom/pages/edit.html.haml +1 -0
- data/app/views/droom/pages/index.html.haml +10 -0
- data/app/views/droom/pages/new.html.haml +4 -0
- data/app/views/droom/pages/show.html.haml +5 -0
- data/app/views/droom/people/_created.html.haml +6 -0
- data/app/views/droom/people/_form.html.haml +40 -0
- data/app/views/droom/people/_people.html.haml +29 -0
- data/app/views/droom/people/_person.html.haml +34 -0
- data/app/views/droom/people/_suggested.html.haml +9 -0
- data/app/views/droom/people/edit.html.haml +1 -0
- data/app/views/droom/people/index.html.haml +11 -0
- data/app/views/droom/people/new.html.haml +1 -0
- data/app/views/droom/people/show.html.haml +1 -0
- data/app/views/droom/shared/_calendar_and_search.html.haml +2 -0
- data/app/views/droom/shared/_calendar_holder.haml +3 -0
- data/app/views/droom/shared/_controls.html.haml +8 -0
- data/app/views/droom/shared/_navigation.html.haml +8 -0
- data/app/views/droom/shared/_search_form.html.haml +4 -0
- data/app/views/droom/shared/_suggestions.html.haml +11 -0
- data/app/views/droom/shared/_toolbar.html.haml +17 -0
- data/app/views/droom/venues/_suggested.html.haml +6 -0
- data/app/views/droom/venues/index.html.haml +6 -0
- data/app/views/droom/venues/show.html.haml +21 -0
- data/app/views/layouts/droom/application.html.haml +36 -0
- data/config/initializers/dav.rb +2 -0
- data/config/initializers/paperclip.rb +26 -0
- data/config/initializers/snail.rb +2 -0
- data/config/locales/en.yml +191 -0
- data/config/routes.rb +51 -0
- data/db/migrate/20120910075016_create_droom_data.rb +134 -0
- data/db/migrate/20120917095804_agenda_sections.rb +13 -0
- data/db/migrate/20120918121352_add_postal_address_to_people.rb +10 -0
- data/db/migrate/20121009075049_give_groups_descriptions.rb +5 -0
- data/db/migrate/20121009105244_more_names.rb +5 -0
- data/db/migrate/20121009145944_event_agenda_sections.rb +13 -0
- data/db/migrate/20121011091230_create_group_invitations.rb +10 -0
- data/db/migrate/20121012144720_give_people_positions.rb +5 -0
- data/db/migrate/20121012154558_help_pages.rb +13 -0
- data/db/migrate/20121012163201_category_slugs.rb +5 -0
- data/db/migrate/20121101160102_document_links.rb +14 -0
- data/db/migrate/20121101181247_people_visibility.rb +7 -0
- data/db/migrate/20121102094738_shy_people.rb +6 -0
- data/db/migrate/20121102095856_visibility_defaults.rb +8 -0
- data/lib/droom.rb +73 -0
- data/lib/droom/dav_resource.rb +36 -0
- data/lib/droom/engine.rb +8 -0
- data/lib/droom/helpers.rb +25 -0
- data/lib/droom/monkeys.rb +15 -0
- data/lib/droom/renderers.rb +13 -0
- data/lib/droom/validators.rb +5 -0
- data/lib/droom/version.rb +3 -0
- data/lib/generators/droom/dashboard/dashboard_generator.rb +16 -0
- data/lib/generators/droom/install/USAGE +9 -0
- data/lib/generators/droom/install/install_generator.rb +15 -0
- data/lib/generators/droom/install/templates/droom_initializer.rb +6 -0
- data/lib/generators/droom/views/views_generator.rb +9 -0
- data/lib/tasks/droom_tasks.rake +4 -0
- metadata +635 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
module Droom
|
2
|
+
class SuggestionsController < Droom::EngineController
|
3
|
+
respond_to :json, :js
|
4
|
+
before_filter :authenticate_user!
|
5
|
+
before_filter :get_current_person
|
6
|
+
before_filter :get_classes
|
7
|
+
|
8
|
+
def index
|
9
|
+
fragment = params[:term]
|
10
|
+
max = params[:limit] || 10
|
11
|
+
|
12
|
+
if @types.include?('event') && span = Chronic.parse(fragment, :guess => false)
|
13
|
+
@suggestions = Droom::Event.falling_within(span).visible_to(@current_person)
|
14
|
+
|
15
|
+
else
|
16
|
+
@suggestions = @klasses.collect {|klass|
|
17
|
+
klass.constantize.visible_to(@current_person).name_matching(fragment).limit(max)
|
18
|
+
}.flatten.sort_by(&:name).slice(0, max)
|
19
|
+
end
|
20
|
+
|
21
|
+
respond_with @suggestions do |format|
|
22
|
+
format.json {
|
23
|
+
render :json => @suggestions.map { |suggestion| {
|
24
|
+
:type => suggestion.identifier,
|
25
|
+
:text => suggestion.name,
|
26
|
+
:id => suggestion.id
|
27
|
+
}}.to_json
|
28
|
+
}
|
29
|
+
format.js {
|
30
|
+
render :partial => "droom/shared/suggestions"
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def get_classes
|
38
|
+
if params[:type].blank?
|
39
|
+
@types = searchable_classes.keys
|
40
|
+
@klasses = searchable_classes.values
|
41
|
+
else
|
42
|
+
@types = searchable_classes.keys & [params[:type]].flatten
|
43
|
+
@klasses = searchable_classes.values_at(*@types)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def searchable_classes
|
48
|
+
{
|
49
|
+
"event" => "Droom::Event",
|
50
|
+
"person" => "Droom::Person",
|
51
|
+
"document" => "Droom::Document",
|
52
|
+
"group" => "Droom::Group",
|
53
|
+
"venue" => "Droom::Venue"
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Droom
|
2
|
+
class VenuesController < Droom::EngineController
|
3
|
+
respond_to :json, :html
|
4
|
+
|
5
|
+
before_filter :authenticate_user!
|
6
|
+
before_filter :get_current_person
|
7
|
+
before_filter :get_venues, :only => ["index"]
|
8
|
+
before_filter :get_venue, :only => [:show, :update]
|
9
|
+
|
10
|
+
def index
|
11
|
+
respond_with @venues do |format|
|
12
|
+
format.json {
|
13
|
+
render :json => @venues.to_json(:person => @person)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def show
|
19
|
+
respond_with @venue
|
20
|
+
end
|
21
|
+
|
22
|
+
def update
|
23
|
+
@venue.update_attributes(params[:venue])
|
24
|
+
respond_with @venue
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def get_venues
|
30
|
+
@venues = Venue.all
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_venue
|
34
|
+
@venue = Venue.find(params[:id])
|
35
|
+
@events = @venue.events.visible_to(@current_person).future_and_current
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Droom
|
2
|
+
module DroomHelper
|
3
|
+
|
4
|
+
def nav_link_to(name, url, options={})
|
5
|
+
options[:class] ||= ""
|
6
|
+
options[:class] << "here" if (request.path == url) || (request.path =~ /^#{url}/ && url != "/")
|
7
|
+
link_to name, url, options
|
8
|
+
end
|
9
|
+
|
10
|
+
def month_header_for(date)
|
11
|
+
content_tag('h3', l(date, :format => :month_header))
|
12
|
+
end
|
13
|
+
|
14
|
+
def pagination_summary(collection, options = {})
|
15
|
+
entry_name = options[:entry_name] || (collection.empty?? 'entry' : collection.first.class.name.underscore.sub('_', ' '))
|
16
|
+
summary = if collection.num_pages < 2
|
17
|
+
case collection.total_count
|
18
|
+
when 0; "No #{entry_name.pluralize} found"
|
19
|
+
when 1; "Displaying <strong>1</strong> #{entry_name}:"
|
20
|
+
else; "Displaying <strong>all #{collection.total_count}</strong> #{entry_name.pluralize}:"
|
21
|
+
end
|
22
|
+
else
|
23
|
+
offset = (collection.current_page - 1) * collection.limit_value
|
24
|
+
%{Displaying <strong>%d - %d</strong> of <strong>%d</strong> #{entry_name.pluralize}: } % [
|
25
|
+
offset + 1,
|
26
|
+
offset + collection.length,
|
27
|
+
collection.total_count
|
28
|
+
]
|
29
|
+
end
|
30
|
+
summary.html_safe
|
31
|
+
end
|
32
|
+
|
33
|
+
# This will apply cloud-weighting to any list of items.
|
34
|
+
# They must have a 'weight' attribute
|
35
|
+
# and be ready to accept a 'cloud_size' attribute.
|
36
|
+
|
37
|
+
def cloud(these, threshold=0, biggest=3.0, smallest=1.3)
|
38
|
+
counts = these.map{|t| t.weight.to_i}.compact
|
39
|
+
if counts.any?
|
40
|
+
max = counts.max
|
41
|
+
min = counts.min
|
42
|
+
if max == min
|
43
|
+
these.each do |this|
|
44
|
+
this.cloud_size = sprintf("%.2f", biggest/2 + smallest/2)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
steepness = Math.log(max - (min-1))/(biggest - smallest)
|
48
|
+
these.each do |this|
|
49
|
+
offset = Math.log(this.weight.to_i - (min-1))/steepness
|
50
|
+
this.cloud_size = sprintf("%.2f", smallest + offset)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if block_given?
|
54
|
+
these.each do |this|
|
55
|
+
yield this
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def url_for_month(date)
|
62
|
+
calendar_url(:year => date.year, :month => date.month)
|
63
|
+
end
|
64
|
+
|
65
|
+
def url_for_date(date)
|
66
|
+
calendar_url(:year => date.year, :month => date.month, :mday => date.day)
|
67
|
+
end
|
68
|
+
|
69
|
+
def day_names
|
70
|
+
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Droom
|
2
|
+
class Category < ActiveRecord::Base
|
3
|
+
attr_accessible :name, :description, :slug
|
4
|
+
|
5
|
+
belongs_to :created_by, :class_name => 'User'
|
6
|
+
has_many :document_attachments
|
7
|
+
|
8
|
+
before_validation :check_slug
|
9
|
+
validates :slug, :presence => true, :uniqueness => true
|
10
|
+
|
11
|
+
default_scope order("droom_categories.name ASC")
|
12
|
+
|
13
|
+
# *for_selection* returns a set of [name, id] pairs suitable for use as select options.
|
14
|
+
def self.for_selection
|
15
|
+
categories = self.all.map{|c| [c.name, c.id] }
|
16
|
+
categories.unshift(['', ''])
|
17
|
+
categories
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def check_slug
|
23
|
+
ensure_presence_and_uniqueness_of(:slug, name.parameterize)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Droom
|
2
|
+
class Document < ActiveRecord::Base
|
3
|
+
attr_accessible :name, :file, :description, :attachment_category_id, :event_id
|
4
|
+
|
5
|
+
# attachment_category and event_id are used on document creation to create an associated attachment
|
6
|
+
# this is a temporary shortcut
|
7
|
+
attr_accessor :old_version, :attachment_category_id, :event_id
|
8
|
+
|
9
|
+
belongs_to :created_by, :class_name => 'User'
|
10
|
+
|
11
|
+
has_many :document_attachments, :dependent => :destroy
|
12
|
+
has_many :document_links, :through => :document_attachments
|
13
|
+
has_many :people, :through => :document_links
|
14
|
+
|
15
|
+
has_attached_file :file
|
16
|
+
|
17
|
+
before_save :set_version
|
18
|
+
validates :file, :presence => true
|
19
|
+
|
20
|
+
scope :all_private, where("secret = 1")
|
21
|
+
scope :not_private, where("secret <> 1")
|
22
|
+
scope :all_public, where("public = 1 AND secret <> 1")
|
23
|
+
scope :not_public, where("public <> 1 OR secret = 1)")
|
24
|
+
|
25
|
+
scope :visible_to, lambda { |person|
|
26
|
+
if person
|
27
|
+
select('droom_documents.*')
|
28
|
+
.joins('LEFT OUTER JOIN droom_document_attachments ON droom_documents.id = droom_document_attachments.document_id')
|
29
|
+
.joins('LEFT OUTER JOIN droom_document_links ON droom_document_attachments.id = droom_document_links.document_attachment_id')
|
30
|
+
.where(["(droom_documents.public = 1 OR droom_document_links.person_id = ?)", person.id])
|
31
|
+
.group('droom_documents.id')
|
32
|
+
else
|
33
|
+
all_public
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
scope :name_matching, lambda { |fragment|
|
38
|
+
fragment = "%#{fragment}%"
|
39
|
+
where('droom_documents.name like ?', fragment)
|
40
|
+
}
|
41
|
+
|
42
|
+
scope :attached_to_these_groups, lambda { |groups|
|
43
|
+
placeholders = groups.map{'?'}.join(',')
|
44
|
+
select('droom_documents.*')
|
45
|
+
.joins('INNER JOIN droom_document_attachments ON droom_documents.id = droom_document_attachments.document_id AND droom_document_attachments.attachee_type = "Droom::Group"')
|
46
|
+
.where(["droom_document_attachments.attachee_id IN(#{placeholders})", *groups.map(&:id)])
|
47
|
+
}
|
48
|
+
|
49
|
+
scope :with_latest_event,
|
50
|
+
select('droom_documents.*, droom_categories.name AS category_name, droom_events.id AS latest_event_id, droom_events.name AS latest_event_name')
|
51
|
+
.joins('LEFT OUTER JOIN droom_document_attachments AS dda ON droom_documents.id = dda.document_id
|
52
|
+
LEFT OUTER JOIN droom_categories ON dda.category_id = droom_categories.id
|
53
|
+
LEFT OUTER JOIN droom_events ON dda.attachee_id = droom_events.id AND dda.attachee_type = "Droom::Event"')
|
54
|
+
.group('droom_documents.id')
|
55
|
+
|
56
|
+
# so that we can apply the joined finders above to an existing object
|
57
|
+
#
|
58
|
+
scope :this_document, lambda { |doc|
|
59
|
+
where(["droom_documents.id = ?", doc.id])
|
60
|
+
}
|
61
|
+
|
62
|
+
scope :by_date, order("droom_documents.updated_at DESC, droom_documents.created_at DESC")
|
63
|
+
|
64
|
+
def identifier
|
65
|
+
'document'
|
66
|
+
end
|
67
|
+
|
68
|
+
def file_ok?
|
69
|
+
file.exists?
|
70
|
+
end
|
71
|
+
|
72
|
+
def changed_since_creation?
|
73
|
+
file_updated_at > created_at
|
74
|
+
end
|
75
|
+
|
76
|
+
def attachment_category_id=(id)
|
77
|
+
attach_to(Droom::Event.find(event_id), {:category_id => id})
|
78
|
+
end
|
79
|
+
|
80
|
+
def attach_to(attachee, attributes={})
|
81
|
+
save!
|
82
|
+
document_attachments.create(attributes.merge(:attachee => attachee))
|
83
|
+
end
|
84
|
+
|
85
|
+
def detach_from(attachee)
|
86
|
+
document_attachments.attached_to(attachee).destroy_all
|
87
|
+
end
|
88
|
+
|
89
|
+
def file_extension
|
90
|
+
if file_file_name
|
91
|
+
File.extname(file_file_name).sub(/^\./, '')
|
92
|
+
else
|
93
|
+
""
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def with_event
|
98
|
+
self.class.this_document(self).with_latest_event.first
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def set_version
|
104
|
+
if file.dirty?
|
105
|
+
self.version = (version || 0) + 1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Droom
|
2
|
+
class DocumentAttachment < ActiveRecord::Base
|
3
|
+
attr_accessible :attachee, :document, :agenda_section, :category_id
|
4
|
+
|
5
|
+
belongs_to :document
|
6
|
+
belongs_to :attachee, :polymorphic => true
|
7
|
+
belongs_to :category
|
8
|
+
belongs_to :created_by, :class_name => "User"
|
9
|
+
|
10
|
+
has_many :document_links, :dependent => :destroy
|
11
|
+
has_many :people, :through => :document_links
|
12
|
+
|
13
|
+
after_destroy :remove_private_document_if_unattached
|
14
|
+
after_create :link_people
|
15
|
+
|
16
|
+
default_scope order("(CASE WHEN droom_document_attachments.category_id IS NULL THEN 0 ELSE 1 END), droom_documents.updated_at ASC, droom_documents.created_at ASC").includes(:document)
|
17
|
+
|
18
|
+
scope :unfiled, where("category_id IS NULL")
|
19
|
+
|
20
|
+
scope :attached_to, lambda { |attachee|
|
21
|
+
where(["droom_document_attachments.attachee_type = :class AND droom_document_attachments.attachee_id = :id", :class => attachee.class.to_s, :id => attachee.id])
|
22
|
+
}
|
23
|
+
|
24
|
+
scope :to_event, lambda { |event|
|
25
|
+
where(["droom_document_attachments.attachee_type = 'Droom::Event' AND droom_document_attachments.attachee_id = ?", event.id])
|
26
|
+
}
|
27
|
+
|
28
|
+
scope :to_group, lambda { |group|
|
29
|
+
where(["droom_document_attachments.attachee_type = 'Droom::Group' AND droom_document_attachments.attachee_id = ?", group.id])
|
30
|
+
}
|
31
|
+
|
32
|
+
scope :to_events, lambda { |events|
|
33
|
+
placeholders = events.map{'?'}.join(',')
|
34
|
+
ids = events.map(&:id)
|
35
|
+
where(["droom_document_attachments.attachee_type = 'Droom::Event' AND droom_document_attachments.attachee_id IN (#{placeholders})", *ids])
|
36
|
+
}
|
37
|
+
|
38
|
+
scope :to_groups, lambda { |groups|
|
39
|
+
placeholders = groups.map{'?'}.join(',')
|
40
|
+
ids = groups.map(&:id)
|
41
|
+
where(["droom_document_attachments.attachee_type = 'Droom::Group' AND droom_document_attachments.attachee_id IN (#{placeholders})", *ids])
|
42
|
+
}
|
43
|
+
|
44
|
+
def slug
|
45
|
+
if attachee
|
46
|
+
attachee.slug
|
47
|
+
else
|
48
|
+
'Unattached'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def category_name
|
53
|
+
category ? category.name : "uncategorised"
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def remove_private_document_if_unattached
|
59
|
+
unless document.public? || document.document_attachments.count > 0
|
60
|
+
document.destroy
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Upon creation we create document links to all the people who are newly entitled to see the document.
|
65
|
+
# Deletion takes care of itself, as document_links are :dependent => :destroy.
|
66
|
+
#
|
67
|
+
def link_people
|
68
|
+
people << attachee.people if attachee
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# the links between a person and document are too various and too extended to make them
|
2
|
+
# easy to retrieve in a single query. Instead we maintain an index of person-document links.
|
3
|
+
|
4
|
+
module Droom
|
5
|
+
class DocumentLink < ActiveRecord::Base
|
6
|
+
attr_accessible :person, :document_attachment
|
7
|
+
belongs_to :person
|
8
|
+
belongs_to :document_attachment
|
9
|
+
has_one :personal_document, :dependent => :destroy
|
10
|
+
|
11
|
+
delegate :slug, :category, :to => :document_attachment
|
12
|
+
|
13
|
+
def document
|
14
|
+
document_attachment.document
|
15
|
+
end
|
16
|
+
|
17
|
+
def document=(doc)
|
18
|
+
create_document_attachment(:document => doc)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ensure_personal_document
|
22
|
+
create_personal_document unless personal_document
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.repair
|
26
|
+
Droom::Person.each do |person|
|
27
|
+
person.repair_document_links
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,409 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'zip/zip'
|
3
|
+
require 'uuidtools'
|
4
|
+
require 'chronic'
|
5
|
+
require 'ri_cal'
|
6
|
+
|
7
|
+
module Droom
|
8
|
+
class Event < ActiveRecord::Base
|
9
|
+
attr_accessible :start, :finish, :name, :description, :event_set_id, :created_by_id, :uuid, :all_day, :master_id, :url, :start_date, :start_time, :finish_date, :finish_time, :venue, :private, :public, :venue_name
|
10
|
+
|
11
|
+
belongs_to :created_by, :class_name => 'User'
|
12
|
+
|
13
|
+
has_many :invitations, :dependent => :destroy
|
14
|
+
has_many :people, :through => :invitations
|
15
|
+
|
16
|
+
has_many :group_invitations, :dependent => :destroy
|
17
|
+
has_many :groups, :through => :group_invitations
|
18
|
+
|
19
|
+
has_many :document_attachments, :as => :attachee, :dependent => :destroy
|
20
|
+
has_many :documents, :through => :document_attachments
|
21
|
+
|
22
|
+
has_many :agenda_categories, :dependent => :destroy
|
23
|
+
has_many :categories, :through => :agenda_categories
|
24
|
+
|
25
|
+
belongs_to :venue
|
26
|
+
accepts_nested_attributes_for :venue
|
27
|
+
|
28
|
+
belongs_to :event_set
|
29
|
+
accepts_nested_attributes_for :event_set
|
30
|
+
|
31
|
+
belongs_to :master, :class_name => 'Event'
|
32
|
+
has_many :occurrences, :class_name => 'Event', :foreign_key => 'master_id', :dependent => :destroy
|
33
|
+
has_many :recurrence_rules, :dependent => :destroy, :conditions => {:active => true}
|
34
|
+
accepts_nested_attributes_for :recurrence_rules, :allow_destroy => true
|
35
|
+
|
36
|
+
validates :start, :presence => true, :date => true
|
37
|
+
validates :finish, :date => {:after => :start, :allow_nil => true}
|
38
|
+
validates :uuid, :presence => true, :uniqueness => true
|
39
|
+
validates :name, :presence => true
|
40
|
+
|
41
|
+
before_validation :set_uuid
|
42
|
+
before_save :check_slug
|
43
|
+
after_save :update_occurrences
|
44
|
+
|
45
|
+
scope :primary, where("master_id IS NULL")
|
46
|
+
scope :recurrent, where(:conditions => "master_id IS NOT NULL")
|
47
|
+
default_scope order('start ASC').includes(:venue)
|
48
|
+
|
49
|
+
## Event retrieval in various ways
|
50
|
+
#
|
51
|
+
scope :visible_to, lambda { |person|
|
52
|
+
if person
|
53
|
+
select('droom_events.*')
|
54
|
+
.joins('LEFT OUTER JOIN droom_invitations ON droom_events.id = droom_invitations.event_id')
|
55
|
+
.where(["(droom_events.public = 1 OR droom_invitations.person_id = ?)", person.id])
|
56
|
+
.group('droom_events.id')
|
57
|
+
else
|
58
|
+
all_public
|
59
|
+
end
|
60
|
+
}
|
61
|
+
|
62
|
+
scope :after, lambda { |datetime| # datetime. eg calendar.occurrences.after(Time.now)
|
63
|
+
where(['start > ?', datetime])
|
64
|
+
}
|
65
|
+
|
66
|
+
scope :before, lambda { |datetime| # datetime. eg calendar.occurrences.before(Time.now)
|
67
|
+
where(['start < :date AND (finish IS NULL or finish < :date)', :date => datetime])
|
68
|
+
}
|
69
|
+
|
70
|
+
scope :between, lambda { |start, finish| # datetimable objects. eg. Event.between(reader.last_login, Time.now)
|
71
|
+
where(['start > :start AND start < :finish AND (finish IS NULL or finish < :finish)', :start => start, :finish => finish])
|
72
|
+
}
|
73
|
+
|
74
|
+
scope :future_and_current, lambda {
|
75
|
+
where(['(finish > :now) OR (finish IS NULL AND start > :now)', :now => Time.now])
|
76
|
+
}
|
77
|
+
|
78
|
+
scope :unfinished, lambda { |start| # datetimable object.
|
79
|
+
where(['start < :start AND finish > :start', :start => start])
|
80
|
+
}
|
81
|
+
|
82
|
+
scope :by_finish, order("finish ASC")
|
83
|
+
|
84
|
+
scope :coincident_with, lambda { |start, finish| # datetimable objects.
|
85
|
+
where(['(start < :finish AND finish > :start) OR (finish IS NULL AND start > :start AND start < :finish)', {:start => start, :finish => finish}])
|
86
|
+
}
|
87
|
+
|
88
|
+
scope :limited_to, lambda { |limit|
|
89
|
+
limit(limit)
|
90
|
+
}
|
91
|
+
|
92
|
+
scope :at_venue, lambda { |venue| # EventVenue object
|
93
|
+
where(["venue_id = ?", venue.id])
|
94
|
+
}
|
95
|
+
|
96
|
+
scope :except_these_uuids, lambda { |uuids| # array of uuid strings
|
97
|
+
placeholders = uuids.map{'?'}.join(',')
|
98
|
+
where(["uuid NOT IN (#{placeholders})", *uuids])
|
99
|
+
}
|
100
|
+
|
101
|
+
scope :without_invitations_to, lambda { |person| # Person object
|
102
|
+
select("droom_events.*")
|
103
|
+
.joins("LEFT OUTER JOIN droom_invitations ON droom_events.id = droom_invitations.event_id AND droom_invitations.person_id = #{sanitize(person.id)}")
|
104
|
+
.group("droom_events.id")
|
105
|
+
.having("COUNT(droom_invitations.id) = 0")
|
106
|
+
}
|
107
|
+
|
108
|
+
scope :with_documents,
|
109
|
+
select("droom_events.*")
|
110
|
+
.joins("INNER JOIN droom_document_attachments ON droom_events.id = droom_document_attachments.attachee_id AND droom_document_attachments.attachee_type = 'Droom::Event'")
|
111
|
+
.group("droom_events.id")
|
112
|
+
|
113
|
+
|
114
|
+
scope :all_private, where("private = 1 OR private = 't'")
|
115
|
+
scope :not_private, where("private = 0 OR private = 'f'")
|
116
|
+
scope :all_public, where("public = 1 OR public = 't'")
|
117
|
+
scope :not_public, where("public = 0 OR public = 'f'")
|
118
|
+
|
119
|
+
scope :name_matching, lambda { |fragment|
|
120
|
+
fragment = "%#{fragment}%"
|
121
|
+
where('droom_events.name like ?', fragment)
|
122
|
+
}
|
123
|
+
|
124
|
+
# All of these class methods also return scopes.
|
125
|
+
#
|
126
|
+
def self.in_the_last(period) # seconds. eg calendar.occurrences.in_the_last(1.week)
|
127
|
+
finish = Time.now
|
128
|
+
start = finish - period
|
129
|
+
between(start, finish)
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.in_year(year) # just a number. eg calendar.occurrences.in_year(2010)
|
133
|
+
start = DateTime.civil(year)
|
134
|
+
finish = start + 1.year
|
135
|
+
between(start, finish)
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.in_month(year, month) # numbers. eg calendar.occurrences.in_month(2010, 12)
|
139
|
+
start = DateTime.civil(year, month)
|
140
|
+
finish = start + 1.month
|
141
|
+
between(start, finish)
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.in_week(year, week) # numbers, with a commercial week: eg calendar.occurrences.in_week(2010, 35)
|
145
|
+
start = DateTime.commercial(year, week)
|
146
|
+
finish = start + 1.week
|
147
|
+
between(start, finish)
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.on_day (year, month, day) # numbers: eg calendar.occurrences.on_day(2010, 12, 12)
|
151
|
+
start = DateTime.civil(year, month, day)
|
152
|
+
finish = start + 1.day
|
153
|
+
between(start, finish)
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.in_span(span) # Chronic::Span
|
157
|
+
between(span.begin, span.end)
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.falling_within(span) # Chronic::Span
|
161
|
+
coincident_with(span.begin, span.end)
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.future
|
165
|
+
after(Time.now)
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.past
|
169
|
+
before(Time.now)
|
170
|
+
end
|
171
|
+
|
172
|
+
## Instance methods
|
173
|
+
#
|
174
|
+
def invite(person)
|
175
|
+
self.people << person
|
176
|
+
end
|
177
|
+
|
178
|
+
def attach(doc)
|
179
|
+
self.documents << doc
|
180
|
+
end
|
181
|
+
|
182
|
+
def identifier
|
183
|
+
'event'
|
184
|
+
end
|
185
|
+
|
186
|
+
# We store the start and end points of the event as a single DateTime value to make comparison simple.
|
187
|
+
# The setters for date and time are overridden to pass strings through chronic's natural language parser
|
188
|
+
# and to treat numbers as epoch seconds. These should all work as you'd expect:
|
189
|
+
#
|
190
|
+
# event.start = "Tuesday at 11pm"
|
191
|
+
# event.start = "12/12/1969 at 10pm"
|
192
|
+
# event.start = "1347354111"
|
193
|
+
# event.start = Time.now + 1.hour
|
194
|
+
#
|
195
|
+
def start=(value)
|
196
|
+
write_attribute :start, parse_date(value)
|
197
|
+
end
|
198
|
+
|
199
|
+
def finish=(value)
|
200
|
+
write_attribute :finish, parse_date(value)
|
201
|
+
end
|
202
|
+
|
203
|
+
# For interface purposes we often want to separate date and time parts. These getters will return the
|
204
|
+
# corresponding Date or Time object.
|
205
|
+
#
|
206
|
+
# The `time_of_day` gem makes time handling a bit more intuitive by concealing the date part of a Time object.
|
207
|
+
#
|
208
|
+
def start_time
|
209
|
+
start.time_of_day if start
|
210
|
+
end
|
211
|
+
|
212
|
+
def start_date
|
213
|
+
start.to_date if start
|
214
|
+
end
|
215
|
+
|
216
|
+
def finish_time
|
217
|
+
finish.time_of_day if finish
|
218
|
+
end
|
219
|
+
|
220
|
+
def finish_date
|
221
|
+
finish.to_date if finish
|
222
|
+
end
|
223
|
+
|
224
|
+
# And these setters will adjust the current value so that its date or time part corresponds to the given
|
225
|
+
# value. The value is passed through the same parsing mechanism as above, so:
|
226
|
+
#
|
227
|
+
# event.start = "Tuesday at 11pm" -> next Tuesday at 11pm
|
228
|
+
# event.start_time = "8pm" -> next Tuesday at 8pm
|
229
|
+
# event.start_date = "Wednesday" -> next Wednesday at 8pm
|
230
|
+
# event.start_date = "26 February 2016" -> 26/2/16 at 8pm
|
231
|
+
# event.start_time = "18:00" -> 26/2/16 at 6pm
|
232
|
+
#
|
233
|
+
# If the time is set before the date, we default to that time today. Times default to 00:00 in the usual way.
|
234
|
+
#
|
235
|
+
def start_time=(value)
|
236
|
+
self.start = (start_date || Date.today).to_time + parse_date(value).seconds_since_midnight
|
237
|
+
end
|
238
|
+
|
239
|
+
def start_date=(value)
|
240
|
+
self.start = parse_date(value).to_date# + start_time
|
241
|
+
end
|
242
|
+
|
243
|
+
def finish_time=(value)
|
244
|
+
self.finish = (finish_date || start_date || Date.today).to_time + parse_date(value).seconds_since_midnight
|
245
|
+
end
|
246
|
+
|
247
|
+
def finish_date=(value)
|
248
|
+
self.finish = parse_date(value).to_date# + finish_time
|
249
|
+
end
|
250
|
+
|
251
|
+
def duration
|
252
|
+
if finish
|
253
|
+
finish - start
|
254
|
+
else
|
255
|
+
0
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def venue_name
|
260
|
+
venue.name if venue
|
261
|
+
end
|
262
|
+
|
263
|
+
def venue_name=(name)
|
264
|
+
self.venue = Droom::Venue.find_or_create_by_name(name)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Agenda sections are global, and we don't want to have to manage the extra workload of associating a section with an event.
|
268
|
+
# This call retrieves all the agenda sections that are associated with any of our document attachments, and is suitable for looping
|
269
|
+
# on a template to display attachments per section.
|
270
|
+
#
|
271
|
+
def agenda_sections
|
272
|
+
Droom::AgendaSection.associated_with_event(self)
|
273
|
+
end
|
274
|
+
|
275
|
+
# and this sorts our attachment list into agenda section buckets so that we don't have to go back to the database for each section.
|
276
|
+
def attachments_by_category
|
277
|
+
cats = {}
|
278
|
+
document_attachments.each_with_object({}) do |att, hash|
|
279
|
+
key = att.category_name
|
280
|
+
cats[key] ||= []
|
281
|
+
cats[key].push(att)
|
282
|
+
end
|
283
|
+
categories.each do |category|
|
284
|
+
key = category.name
|
285
|
+
cats[key] ||= []
|
286
|
+
end
|
287
|
+
cats
|
288
|
+
end
|
289
|
+
|
290
|
+
def categories_for_selection
|
291
|
+
cats = categories.map{|c| [c.name, c.id] }
|
292
|
+
cats.unshift(['', ''])
|
293
|
+
cats
|
294
|
+
end
|
295
|
+
|
296
|
+
def visible_to?(user_or_person)
|
297
|
+
return true if self.public?
|
298
|
+
return false unless user_or_person
|
299
|
+
return true if user_or_person.admin?
|
300
|
+
return true if user_or_person.person.invited_to?(self)
|
301
|
+
return false if self.private?
|
302
|
+
return true
|
303
|
+
end
|
304
|
+
|
305
|
+
def one_day?
|
306
|
+
all_day? && within_day?
|
307
|
+
end
|
308
|
+
|
309
|
+
def within_day?
|
310
|
+
(!finish || start.to.jd == finish.to.jd || finish == start + 1.day)
|
311
|
+
end
|
312
|
+
|
313
|
+
def continuing?
|
314
|
+
finish && start < Time.now && finish > Time.now
|
315
|
+
end
|
316
|
+
|
317
|
+
def finished?
|
318
|
+
start < Time.now && (!finish || finish < Time.now)
|
319
|
+
end
|
320
|
+
|
321
|
+
def recurs?
|
322
|
+
master || occurrences.any?
|
323
|
+
end
|
324
|
+
|
325
|
+
def recurrence
|
326
|
+
recurrence_rules.first.to_s
|
327
|
+
end
|
328
|
+
|
329
|
+
def add_recurrence(rule)
|
330
|
+
self.recurrence_rules << Droom::RecurrenceRule.from(rule)
|
331
|
+
end
|
332
|
+
|
333
|
+
def documents_zipped
|
334
|
+
if self.documents.any?
|
335
|
+
tempfile = Tempfile.new("droom-temp-#{slug}-#{Time.now}.zip")
|
336
|
+
Zip::ZipOutputStream.open(tempfile.path) do |z|
|
337
|
+
self.documents.each do |doc|
|
338
|
+
z.add(doc.file_file_name, open(doc.file.url))
|
339
|
+
end
|
340
|
+
end
|
341
|
+
tempfile
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def to_rical
|
346
|
+
RiCal.Event do |cal_event|
|
347
|
+
cal_event.uid = uuid
|
348
|
+
cal_event.summary = name
|
349
|
+
cal_event.description = description if description
|
350
|
+
cal_event.dtstart = (all_day? ? start_date : start) if start
|
351
|
+
cal_event.dtend = (all_day? ? finish_date : finish) if finish
|
352
|
+
cal_event.url = url if url
|
353
|
+
cal_event.rrules = recurrence_rules.map(&:to_rical) if recurrence_rules.any?
|
354
|
+
cal_event.location = venue.name if venue
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def to_ics
|
359
|
+
to_rical.to_s
|
360
|
+
end
|
361
|
+
|
362
|
+
def as_json(options={})
|
363
|
+
json = super
|
364
|
+
json[:datestring] = I18n.l start, :format => :natural_with_date
|
365
|
+
json
|
366
|
+
end
|
367
|
+
|
368
|
+
protected
|
369
|
+
|
370
|
+
def check_slug
|
371
|
+
ensure_presence_and_uniqueness_of(:slug, "#{start.strftime("%Y %m %d")} #{name}".parameterize)
|
372
|
+
end
|
373
|
+
|
374
|
+
def set_uuid
|
375
|
+
self.uuid = UUIDTools::UUID.timestamp_create.to_s if uuid.blank?
|
376
|
+
end
|
377
|
+
|
378
|
+
# doesn't yet observe exceptions
|
379
|
+
def update_occurrences
|
380
|
+
occurrences.destroy_all
|
381
|
+
if recurrence_rules.any?
|
382
|
+
recurrence_horizon = Time.now + 10.years
|
383
|
+
to_rical.occurrences(:before => recurrence_horizon).each do |occ|
|
384
|
+
occurrences.create!({
|
385
|
+
:name => self.name,
|
386
|
+
:url => self.url,
|
387
|
+
:description => self.description,
|
388
|
+
:venue => self.venue,
|
389
|
+
:start => occ.dtstart,
|
390
|
+
:finish => occ.dtend,
|
391
|
+
:uuid => nil
|
392
|
+
}) unless occ.dtstart == self.start
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def parse_date(value)
|
398
|
+
case value
|
399
|
+
when Numeric
|
400
|
+
Time.at(value)
|
401
|
+
when String
|
402
|
+
Chronic.parse(value)
|
403
|
+
else
|
404
|
+
value
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
end
|
409
|
+
end
|