rooftop-rails-events 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +154 -0
  6. data/Rakefile +2 -0
  7. data/app/controllers/concerns/rooftop/rails/events/event_collections.rb +88 -0
  8. data/app/controllers/concerns/rooftop/rails/events/event_handler.rb +49 -0
  9. data/app/controllers/concerns/rooftop/rails/events/event_search.rb +10 -0
  10. data/app/models/concerns/rooftop/rails/events/booking_information.rb +22 -0
  11. data/app/models/concerns/rooftop/rails/events/cache.rb +44 -0
  12. data/app/models/concerns/rooftop/rails/events/instance_cache.rb +76 -0
  13. data/app/models/concerns/rooftop/rails/events/scopes.rb +48 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +8 -0
  16. data/lib/rooftop/rails/events.rb +14 -0
  17. data/lib/rooftop/rails/events/decorators.rb +18 -0
  18. data/lib/rooftop/rails/events/decorators/cast.rb +25 -0
  19. data/lib/rooftop/rails/events/decorators/content.rb +21 -0
  20. data/lib/rooftop/rails/events/decorators/event_detail_decorator.rb +142 -0
  21. data/lib/rooftop/rails/events/decorators/instance_decorator.rb +63 -0
  22. data/lib/rooftop/rails/events/decorators/media.rb +114 -0
  23. data/lib/rooftop/rails/events/decorators/person_decorator.rb +47 -0
  24. data/lib/rooftop/rails/events/decorators/reviews.rb +33 -0
  25. data/lib/rooftop/rails/events/decorators/times.rb +39 -0
  26. data/lib/rooftop/rails/events/engine.rb +31 -0
  27. data/lib/rooftop/rails/events/instance_decorators.rb +13 -0
  28. data/lib/rooftop/rails/events/version.rb +7 -0
  29. data/rooftop-rails-events.gemspec +28 -0
  30. metadata +129 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 43c3a9fa04042ebd6c88977baa161e2f99554620
4
+ data.tar.gz: dae897de59297fbf2ef58ed85984d44d1497a2db
5
+ SHA512:
6
+ metadata.gz: b232cf857b16de7483e1ba8bb02223b3a055bccfab23575c1d21d5c8489236d0962e441cebe8f5dda4a256f674dc622e4519ea011acc2d91e53455881f6f322f
7
+ data.tar.gz: cff98c0085dd4f4e82d775c45fde94566af04a69eb47e28c8c16a46fdf44a00e47dab48ef713d13fbeef063d9896e9ebf515b899f43a39cae972e2aa78248738
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rooftop-rails-events.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ed Jones
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,154 @@
1
+ # Rooftop::Rails::Events
2
+
3
+ A library to make it super-easy to integrate events from [Rooftop CMS](https://www.rooftopcms.com) into your Rails site.
4
+
5
+ ## What's included
6
+ A bit of a mix of stuff, and all work in progress. Here's a flavour:
7
+
8
+ ## Controller Mixins
9
+ You need to `include` these yourself.
10
+
11
+ ### Rooftop::Rails::Events::EventHandler
12
+ A mixin for a controller which handles events. It has methods for show(), instances() and book_instance() which get the event from Rooftop for you.
13
+
14
+ In the simplest form, you can just include this in your controller
15
+
16
+ ```
17
+ class EventsController < ApplicationController
18
+ include Rooftop::Rails::Events::EventHandler
19
+ end
20
+ ````
21
+
22
+ But each method yields to a block so you can do other stuff if you want. Here's a more representative EventsController from a real project:
23
+
24
+ ```
25
+ class EventsController < ApplicationController
26
+ include Rooftop::Rails::Events::EventHandler
27
+ decorates_assigned :events, :event, with: EventDecorator
28
+ layout :determine_layout
29
+
30
+ def instances
31
+ # Pass a block into the EventHandler mixin's method, which is yielded to at the end of the method
32
+ super do
33
+ # If the event has an external ticketing url, redirect to it instead of rendering a list of instances
34
+ # n.b. pretty obviously you'll need to define has_external_ticketing_url?() and external_ticketing_url() on your model
35
+ if @event.has_external_ticketing_url?
36
+ redirect_to @event.external_ticketing_url and return
37
+ end
38
+
39
+ # If the event only has 1 instance, it seems a bit mean to list that instance and make the user click again; go straight to the book_instance method
40
+ if @event.instances.count == 1
41
+ redirect_to action: :book_instance, instance_id: @event.instances.first.meta_attributes[:spektrix_id] and return
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+ private
48
+ def determine_layout
49
+ case action_name.to_sym
50
+ when :show
51
+ "templates/event_detail"
52
+ when :instances
53
+ "templates/instance_list"
54
+ when :book_instance
55
+ "templates/iframe_wide"
56
+ end
57
+ end
58
+ end
59
+ ```
60
+
61
+ ## Model Mixins
62
+ These are `include`d automatically when you use this gem.
63
+
64
+
65
+ ### Rooftop::Rails::Events::Cache
66
+ A mixin added to the Rooftop::Events::Event to cache / expire it.
67
+
68
+ ### Rooftop::Rails::Events::InstanceCache
69
+ The same as `Rooftop::Rails::Events::Cache` but for instances. Allows you to receive a webhook for just an instance and be able to clear the cache for its associated `Event`.
70
+
71
+ ### Rooftop::Rails::Events::Scopes
72
+ Handy scopes for dealing with events - finding after a date, between dates, in the future etc.
73
+
74
+ ## Object Decorators
75
+ We use a lot of [Draper](https://github.com/drapergem/draper) as our decorator pattern at [Error Agency](https://error.agency). So there are some mixins you add to your EventDecorator to provide utility methods for use in views.
76
+
77
+ ```
78
+ #in your project
79
+ class EventDecorator < Draper::Decorator
80
+ include Rooftop::Rails::Events::Decorators
81
+ end
82
+ ```
83
+
84
+ ## Controller mixin for Event collections
85
+ The standard pattern we've found works well for Rooftop is to have a PagesController with mixins which check the template of the page you're rendering, and makes requests to get collections of objects you'll need.
86
+
87
+ ```
88
+ class PagesController < ApplicationController
89
+ include Rooftop::Rails::Events::EventCollections
90
+ end
91
+ ```
92
+ You get a bunch of stuff for free with this:
93
+
94
+ * Rooftop events in the future will be returned as a collection called @events by default
95
+ * if you pass in query params, you can filter these:
96
+ * `q` will do a free-text search
97
+ * `from` will take an dd-mm-yyyy date string and return things happening on or after that date
98
+ * `to` will work likewise, in reverse
99
+
100
+ ### Customising querystring filters
101
+ You might want to filter by something else. That's relatively easy.
102
+
103
+ ```
104
+ class PagesController < ApplicationController
105
+ include Rooftop::Rails::Events::EventCollections
106
+ self.add_event_filter :genre, ->(events,params) {
107
+ # add something here which returns a collection when genre is passed in, for example
108
+ # very important - you must return something here
109
+ if params[:genre].present?
110
+ events.with_genre(params[:genre])
111
+ else
112
+ events
113
+ end
114
+ }
115
+ end
116
+ ```
117
+
118
+
119
+
120
+ ## Installation
121
+
122
+ Add this line to your application's Gemfile:
123
+
124
+ ```ruby
125
+ gem 'rooftop-rails-events'
126
+ ```
127
+
128
+ And then execute:
129
+
130
+ $ bundle
131
+
132
+ Or install it yourself as:
133
+
134
+ $ gem install rooftop-rails-events
135
+
136
+ ## Usage
137
+
138
+ TODO: Write usage instructions here
139
+
140
+ ## Development
141
+
142
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
143
+
144
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
145
+
146
+ ## Contributing
147
+
148
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/rooftop-rails-events. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
149
+
150
+
151
+ ## License
152
+
153
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
154
+
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,88 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ # A mixin for your PagesController, to get events based on your page template. If the appropriate template is present in the response for a page from Rooftop, we generate an @events instance var with the events. @events is filtered according to some basic things like to and from date, if the params are present.
5
+ module EventCollections
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before_action :get_index_events, only: :show, if: :template_matches?
10
+ end
11
+
12
+ class_methods do
13
+ attr_reader :event_index_template, :event_filters, :filter_keys
14
+ def event_index_template=(template)
15
+ @event_index_template = template.to_s
16
+ end
17
+
18
+ # A method to allow us to filter on extra things from the command line (e.g. genre)
19
+ # To use in your controller:
20
+ # self.add_event_filter :genre, ->(events) {
21
+ # return the matching set here
22
+ # }
23
+
24
+ def add_event_filter(key,filter)
25
+ if filter.is_a?(Proc)
26
+ @filter_keys ||= []
27
+ @event_filters ||= []
28
+ @filter_keys << key
29
+ @event_filters << filter
30
+ else
31
+ raise ArgumentError, "add_event_filter takes a proc which is evaluated in the filter_events method, and a params key to check for"
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+ def template_matches?
38
+ self.class.event_index_template.present? && defined?(@page) && @page.template.present? && @page.template.underscore == self.class.event_index_template
39
+ end
40
+
41
+ def get_index_events
42
+ @events = filter_events(Rooftop::Events::Event.in_future).sort_by {|e| e.event_instance_dates[:first]} rescue []
43
+ end
44
+
45
+ def has_filter_keys?
46
+ fixed_filter_keys = ["from", "to", "q"]
47
+ self.class.filter_keys ||= []
48
+ all_filter_keys = fixed_filter_keys + self.class.filter_keys.collect(&:to_s)
49
+ (params.keys & all_filter_keys).any? && all_filter_keys.collect {|k| params[k].present?}.any?
50
+ end
51
+
52
+ def filter_events(events)
53
+ return events unless has_filter_keys?
54
+
55
+ # Build a collection of collections, which we will intersect to get only ones which match all
56
+ all_match = []
57
+
58
+
59
+ if params[:from].present? && params[:to].present?
60
+ from = DateTime.parse(params[:from]) rescue DateTime.now
61
+ to = DateTime.parse(params[:to]) rescue DateTime.now
62
+ all_match << events.showing_between(from,to)
63
+ elsif params[:from].present?
64
+ from = DateTime.parse(params[:from]) rescue DateTime.now
65
+ all_match << events.showing_on(from)
66
+ end
67
+
68
+ # free-text search
69
+ if params[:q].present?
70
+ all_match << events.matching_query(params[:q])
71
+ end
72
+
73
+ # Iterate over any other filters supplied in procs
74
+ if self.class.event_filters.present?
75
+ self.class.event_filters.each do |filter|
76
+ all_match << filter.call(events, params)
77
+ end
78
+ end
79
+
80
+ # intersect all the alls
81
+ all_match.inject(:&)
82
+
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,49 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module EventHandler
5
+ # A module for showing an event and listing event instances. Note that we don't have an index endpoint in here - for that we use a template in the pages controller, and a mixin to get a collection of events if you're hitting that template. See Rooftop::Rails::Events::EventCollections for that.
6
+ extend ActiveSupport::Concern
7
+
8
+ def show
9
+ @event = Rooftop::Events::Event.where(
10
+ slug: params[:id],
11
+ per_page: 1,
12
+ include_embedded_resources: true,
13
+ instances_per_page: -1,
14
+ no_filter: [
15
+ :include_embedded_resources,
16
+ :instances_per_page
17
+ ]).first
18
+ if @event.nil?
19
+ raise Rooftop::RecordNotFoundError, "No event with slug #{params[:id]} found"
20
+ end
21
+
22
+ @instances = @event.embedded_instances
23
+
24
+ yield if block_given?
25
+ end
26
+
27
+ def instances
28
+ @event = Rooftop::Events::Event.where(slug: params[:event_id]).first
29
+
30
+ @instances = Rooftop::Events::Instance.all(_event_id: @event.id, include_embedded_resources: true).sort_by {|i| DateTime.parse(i.meta_attributes[:availability][:starts_at])}.to_a.reject! {|e| DateTime.parse(e.meta_attributes[:availability][:starts_at]) < DateTime.now}
31
+
32
+ @event_details = EventDetailDecorator.new(@event, @instances)
33
+
34
+ yield if block_given?
35
+ end
36
+
37
+ def book_instance
38
+ @event = Rooftop::Events::Event.where(slug: params[:event_id]).first
39
+ @instance_id = params[:instance_id]
40
+
41
+ yield if block_given?
42
+ end
43
+
44
+
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module EventSearch
5
+
6
+
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module BookingInformation
5
+ extend ActiveSupport::Concern
6
+
7
+ def date_from
8
+ if event_instance_dates[:first].present?
9
+ DateTime.parse(event_instance_dates[:first])
10
+ end
11
+ end
12
+
13
+ def date_to
14
+ if event_instance_dates[:last].present?
15
+ DateTime.parse(event_instance_dates[:last])
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module Cache
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ if ::Rails.configuration.action_controller.perform_caching
8
+ base.send(:alias_method_chain, :related_events, :caching)
9
+ end
10
+
11
+ base.class_eval do
12
+ class << self
13
+ alias_method_chain :expire_cache_for, :related
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ def related_cache_key
20
+ "#{self.class.related_cache_key_base}/#{id}"
21
+ end
22
+
23
+
24
+ def related_events_with_caching(opts = {})
25
+ Rails.cache.fetch(related_cache_key) do
26
+ related_events_without_caching(opts)
27
+ end
28
+ end
29
+
30
+ module ClassMethods
31
+ def related_cache_key_base
32
+ "#{cache_key_base}/related"
33
+ end
34
+
35
+ def expire_cache_for_with_related(*args)
36
+ ::Rails.cache.delete_matched("#{related_cache_key_base}*")
37
+ expire_cache_for_without_related(args)
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,76 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module InstanceCache
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+
8
+ base.class_eval do
9
+ class << self
10
+ alias_method_chain :expire_cache_for, :parent
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ def parent_event_id_cache_key
17
+ "#{self.class.parent_event_id_cache_key_base}/#{id}"
18
+ end
19
+
20
+ # Cache the parent ID into a key
21
+ def cache_parent_id
22
+ if self.respond_to?(:event_id)
23
+ Rails.cache.write(parent_event_id_cache_key, self.event_id)
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+
29
+ def parent_event_id_cache_key_base
30
+ "#{cache_key_base}/parent_event"
31
+ end
32
+
33
+ def expire_cache_for_with_parent(*args)
34
+ ids = args.collect {|a| a.respond_to?(:id) ? a.id : a}.flatten.collect(&:to_i)
35
+
36
+ #Parent events IDs are stored in a series of keys, one for each instance id. We get all the event ID keys into an array
37
+ parent_event_id_cache_keys = get_parent_id_cache_keys(ids)
38
+ # then we get read_multi to get them all from the cache, and derive a unique set of event ids
39
+
40
+ event_ids = get_event_ids_from_cache(parent_event_id_cache_keys)
41
+ if event_ids.count != ids.count #we have some missing event IDs, so we need to do an expensive lookup
42
+ Rooftop::Events::Event.all(
43
+ include_embedded_resources: true,
44
+ instances_per_page: -1,
45
+ no_filter: [
46
+ :include_embedded_resources,
47
+ :instance_per_page
48
+ ]
49
+ )
50
+ end
51
+
52
+ # Now we've hopefully cached them all, do the cache lookup again
53
+
54
+ get_event_ids_from_cache(parent_event_id_cache_keys).each do |event_id|
55
+ Rooftop::Events::Event.expire_cache_for(event_id.to_i)
56
+ end
57
+
58
+ #then we do the normal cache expiry
59
+ expire_cache_for_without_parent(*args)
60
+ end
61
+
62
+ private
63
+ def get_parent_id_cache_keys(ids)
64
+ ids.collect {|id| "#{parent_event_id_cache_key_base}/#{id}"}
65
+ end
66
+
67
+ def get_event_ids_from_cache(keys)
68
+ Rails.cache.read_multi(*keys).collect {|key, event_id| event_id}.uniq
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,48 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module Scopes
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+
9
+ scope :after_date, ->(date) {
10
+ where(
11
+ meta_key: :last_event_instance,
12
+ meta_query: {
13
+ key: :last_event_instance,
14
+ value: date,
15
+ compare: '>='
16
+ },
17
+ orderby: :meta_value_num,
18
+ order: :asc
19
+ )
20
+ }
21
+
22
+ scope :in_future, -> {
23
+ after_date(Date.today)
24
+ }
25
+
26
+ scope :showing_between, ->(from,to) {
27
+ to_a.select { |e|
28
+ from.beginning_of_day <= DateTime.parse(e.event_instance_dates[:last]) && to.end_of_day >= DateTime.parse(e.event_instance_dates[:first])
29
+ }
30
+ }
31
+
32
+ scope :showing_on, ->(date) {
33
+ showing_between(date,date)
34
+ }
35
+
36
+ scope :showing_from, ->(date) {
37
+ to_a.select {|e| date.beginning_of_day >= DateTime.parse(e.event_instance_dates[:first])}
38
+ }
39
+
40
+ scope :matching_query, ->(q) {
41
+ search(q)
42
+ }
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rooftop/rails/events"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,14 @@
1
+ require 'require_all'
2
+ require 'rooftop'
3
+ require 'rooftop/rails'
4
+
5
+ require "rooftop/rails/events/version"
6
+ require "rooftop/rails/events/engine"
7
+ require "rooftop/rails/events/decorators"
8
+ require "rooftop/rails/events/instance_decorators"
9
+ module Rooftop
10
+ module Rails
11
+ module Events
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ require_rel 'decorators'
2
+ module Rooftop
3
+ module Rails
4
+ module Events
5
+ module Decorators
6
+
7
+ def self.included(base)
8
+ raise StandardError, "Rooftop::Rails::Events::Decorators expects to be mixed into a Draper decorator" unless defined?(Draper) && base.ancestors.include?(Draper::Decorator)
9
+ base.send(:include, Rooftop::Rails::Events::Content)
10
+ base.send(:include, Rooftop::Rails::Events::Cast)
11
+ base.send(:include, Rooftop::Rails::Events::Media)
12
+ base.send(:include, Rooftop::Rails::Events::Reviews)
13
+ base.send(:include, Rooftop::Rails::Events::Times)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module Cast
5
+
6
+ def cast_members
7
+ if object.has_field?(:cast, Array)
8
+ object.fields.cast.collect{|cm| PersonDecorator.new(cm)}
9
+ else
10
+ []
11
+ end
12
+ end
13
+
14
+ def creatives
15
+ if object.has_field?(:creatives, Array)
16
+ object.fields.creatives.collect{|cm| PersonDecorator.new(cm)}
17
+ else
18
+ []
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module Content
5
+
6
+ def title
7
+ object.title.html_safe
8
+ end
9
+
10
+ def content
11
+ h.parse_content object.fields.content
12
+ end
13
+
14
+ def excerpt
15
+ h.parse_content object.fields.excerpt
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,142 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ class EventDetailDecorator
5
+ attr_reader :grouped, :price_lists, :date_ranges, :month_ranges, :accessibility, :instances, :month_ranges_with_sorted_instances
6
+
7
+ def initialize(event, instances)
8
+ @instances = instances ? instances : Rooftop::Events::Instance.all(event_id: event.id,include_embedded_resources: true)
9
+
10
+ price_list_ids = @instances.collect{|i| i.price_list_id.to_i}.sort.uniq
11
+ @price_lists = Rooftop::Events::PriceList.where(post__in: price_list_ids,include_embedded_resources: true).to_a
12
+
13
+ @date_ranges = grouped_by_performance_type!
14
+
15
+ @accessibility = grouped_by_access_feature!
16
+
17
+ @month_ranges = grouped_by_month!
18
+
19
+ @month_ranges_with_sorted_instances = grouped_by_month_with_sorted_instances
20
+ end
21
+
22
+ # group events by their performance types.
23
+ # type being, perview, evening, matinee, or other (special)
24
+ def grouped_by_performance_type!
25
+ performance_types = {'preview' => [], 'special_performance' => []}
26
+
27
+ @instances.each do |instance|
28
+ performance_type = instance.meta_attributes["time_of_day"]
29
+ performance_types[performance_type] ||= []
30
+
31
+ if instance.meta_attributes["preview"]=="true"
32
+ performance_types["preview"].push(instance)
33
+ end
34
+
35
+ if instance.meta_attributes.has_key?("time_of_day")
36
+ time_of_day = instance.meta_attributes["time_of_day"]
37
+ performance_types[time_of_day] ||= []
38
+
39
+ performance_types[time_of_day].push(instance)
40
+ else
41
+ performance_types['special_performance'].push(instance)
42
+ end
43
+ end
44
+
45
+ performance_types.each do |type, instances|
46
+ performance_types[type] = slice_instances_by_date(instances.reverse)
47
+ end
48
+
49
+ Hash[performance_types.reject{|k,v| k.nil? || k.empty? || v.empty?}.sort]
50
+ end
51
+
52
+ # Group the event instances by any known available accessibility facilities
53
+ #
54
+ def grouped_by_access_feature!
55
+ groups = {'audio_described_performance' => [], 'captioned_performance' => [], 'signed_performance' => [], 'touch_tour' => [], 'relaxed_performance' => [], 'talk_back' => []}
56
+
57
+ @instances.each do |instance|
58
+ instance_types = instance.meta_attributes.select{|attr_key, attr_value| groups.keys.include?(attr_key) && attr_value=="true"}.keys
59
+
60
+ if instance_types.any?
61
+ instance_types.each do |type|
62
+ groups[type].push(instance)
63
+ end
64
+ end
65
+ end
66
+
67
+ groups.each do |type, instances|
68
+ groups[type] = slice_instances_by_date(instances.reverse)
69
+ end
70
+
71
+ groups
72
+ end
73
+
74
+ def grouped_by_month!
75
+ sorted_instances = @instances.sort_by{|instance| DateTime.parse(instance.meta_attributes[:availability][:starts_at]).at_midnight}
76
+
77
+ sorted_instances.group_by do |instance|
78
+ DateTime.parse(instance.meta_attributes[:availability][:starts_at]).strftime('%B %Y')
79
+ end
80
+ end
81
+
82
+ def grouped_by_month_with_sorted_instances
83
+ sorted_instances = @instances.sort_by{|instance| DateTime.parse(instance.meta_attributes[:availability][:starts_at])}
84
+ sorted_instances.group_by do |instance| DateTime.parse(instance.meta_attributes[:availability][:starts_at]).strftime('%B %Y') end
85
+ end
86
+
87
+ def today
88
+ for_date(Date.today)
89
+ end
90
+
91
+ def tomorrow
92
+ for_date(Date.tomorrow)
93
+ end
94
+
95
+ def for_date(date)
96
+ @instances.sort_by{|instance| DateTime.parse(instance.meta_attributes[:availability][:starts_at])}.select {|instance| DateTime.parse(instance.meta_attributes[:availability][:starts_at]).to_date == date}
97
+ end
98
+
99
+
100
+
101
+ private
102
+
103
+ # sort the given instances and return a grouped result-set, splitting on sets of event instances that have > 1 day between them.
104
+ # for example, given an array of instances with the dates:
105
+ #
106
+ # [1st, 2nd, 3rd, 8th, 9th, 15th]
107
+ #
108
+ # we should return a grouped array:
109
+ #
110
+ # [[1st, 2nd, 3rd], [8th], [9th], [15th]]
111
+ #
112
+ # We can then iterate over these arrays and present the data (1st - 3rd, 8th, 9th and 15th)
113
+ #
114
+ def slice_instances_by_date(instances)
115
+ instance_dates = instances.collect{|i| instance_date(i)}.sort.uniq
116
+ instance_dates = instance_dates.slice_when{|prev, curr| prev != curr-1.day}.to_a
117
+
118
+ group = {}
119
+
120
+ instance_dates.each_with_index do |date_range, i|
121
+ date_range.each do |date|
122
+ group[i] ||= []
123
+ group[i].push(instances.select{|i| instance_date(i)==date})
124
+ end
125
+ end
126
+
127
+ group.inject({}) do |h,(k,v)|
128
+ h[k] = v.flatten unless v.empty?
129
+ h
130
+ end.values.reject(&:empty?)
131
+ end
132
+
133
+ # return the event instance (ignores the time)
134
+ #
135
+ def instance_date(instance)
136
+ Time.parse(instance.meta_attributes[:availability][:starts_at]).at_midnight
137
+ end
138
+ end
139
+
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,63 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module InstanceDecorator
5
+
6
+ def start_date_object
7
+ DateTime.parse(object.meta_attributes[:availability][:starts_at])
8
+ end
9
+
10
+ def start_date
11
+ start_date_object.strftime("%a %-d %-b")
12
+ end
13
+
14
+ def start_time
15
+ start_date_object.strftime("%l:%M%P")
16
+ end
17
+
18
+
19
+ def price(event_details)
20
+ price_list = event_details.price_lists.find{|pl| pl.id == object.price_list_id.to_i}
21
+
22
+ if price_list
23
+ price_list_range(price_list)
24
+ else
25
+ ""
26
+ end
27
+ end
28
+
29
+ def limited_availability?
30
+ available = event_instance_meta[:availability][:seats_available].to_i
31
+ capacity = event_instance_meta[:availability][:seats_capacity].to_i
32
+
33
+ available <= capacity/10
34
+ end
35
+
36
+ def in_future?
37
+ DateTime.parse(object.event_instance_meta[:availability][:starts_at]).future?
38
+ end
39
+
40
+ def in_past?
41
+ !in_future?
42
+ end
43
+
44
+ def bookable?
45
+ return false if in_past?
46
+
47
+ available = event_instance_meta[:availability][:seats_available].to_i
48
+ available > 0
49
+ end
50
+
51
+ def is_public_dress_rehearsal?
52
+ event_instance_meta.has_key?(:public_dress_rehearsal) && event_instance_meta[:public_dress_rehearsal]=="true"
53
+ end
54
+
55
+ def price_list_range(price_list)
56
+ prices = price_list.prices.collect{|p| p.event_price_meta[:ticket_price].to_f}.flatten.sort
57
+ [prices[0], prices[-1]].uniq.sort
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,114 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module Media
5
+
6
+ # Images. Requires ACF setup.
7
+
8
+ def has_hero_image?
9
+ has_field?(:hero_image) && fields.hero_image.present?
10
+ end
11
+
12
+ def has_poster_image?
13
+ has_field(:poster_image) && fields.poster_image.present?
14
+ end
15
+
16
+ def has_list_image?
17
+ has_field?(:event_list_image) && fields.event_list_image.present?
18
+ end
19
+
20
+ def hero_image
21
+ has_hero_image? ? fields.hero_image[:sizes][:large] : nil
22
+ end
23
+
24
+ def header_image
25
+ if has_hero_image?
26
+ hero_image
27
+ else
28
+ 'https://placeholdit.imgix.net/~text?txtsize=33&txt=350×150&w=1200&h=670'
29
+ end
30
+ end
31
+
32
+ def list_image
33
+ if has_list_image?
34
+ fields.event_list_image[:sizes][:medium]
35
+ elsif has_hero_image?
36
+ hero_image
37
+ end
38
+ end
39
+
40
+ def poster_image
41
+ if has_poster_image?
42
+ fields.poster_image[:sizes][:medium]
43
+ end
44
+ end
45
+
46
+ # Media. Requires ACF.
47
+
48
+ def media
49
+ if object.has_field?(:media, Array) && object.fields.media.any?
50
+ object.fields.media.collect do |media_item|
51
+ collection = Rooftop::Content::Collection.new(media_item.collect(&:to_h))
52
+
53
+ next unless collection.video_or_image.present?
54
+
55
+ if collection.video_or_image == "video" && collection.youtube_url.present?
56
+ collection << Rooftop::Content::Field.new({name: "youtube_id", value: collection.youtube_url.match(/(youtu.be\/(\S+)$|v=(\S+))/).to_a.compact.last})
57
+ end
58
+ collection
59
+ end
60
+ else
61
+ []
62
+ end
63
+ end
64
+
65
+ def media_collection
66
+ media.collect do |item|
67
+ if item.video_or_image == 'image'
68
+ next unless item.image.is_a?(Hash)
69
+ {
70
+ title: item.caption.html_safe,
71
+ thumbnail: item.image[:sizes][:medium],
72
+ href: item.image[:sizes][:large],
73
+ media_type: item.video_or_image,
74
+ }
75
+ elsif item.video_or_image == "video"
76
+ {
77
+ title: item.caption.html_safe,
78
+ href: item.youtube_url,
79
+ type: 'text/html',
80
+ youtube: item.youtube_id,
81
+ thumbnail: '//img.youtube.com/vi/' + item.youtube_id + '/0.jpg',
82
+ poster: '//img.youtube.com/vi/' + item.youtube_id + '/0.jpg',
83
+ media_type: item.video_or_image,
84
+ }
85
+ end
86
+ end
87
+ end
88
+
89
+ def media_json
90
+ media_collection.compact.to_json.html_safe
91
+ end
92
+
93
+ def has_trailer?
94
+ has_field?(:trailer) && fields.trailer.present?
95
+ end
96
+
97
+ def trailer
98
+ if has_trailer?
99
+ "https://www.youtube.com/embed/" + fields.trailer.partition('=').last + "?rel=0&amp;showinfo=0"
100
+ end
101
+ end
102
+
103
+ def media_images
104
+ media.select {|m| m.video_or_image == 'image'}
105
+ end
106
+
107
+ def media_videos
108
+ media.select {|m| m.video_or_image == 'video'}
109
+ end
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,47 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ class PersonDecorator
5
+ attr_reader :attributes, :object, :advanced_fields
6
+
7
+ def initialize(relationship)
8
+ @attributes = {}
9
+ @object = relationship
10
+
11
+ @object.each do |key, value|
12
+ @attributes[key] = value
13
+ self.class.send(:attr_accessor, "#{key}")
14
+ instance_variable_set "@#{key}", value
15
+ end
16
+
17
+ if relationship.has_key?(:advanced) && relationship[:advanced].first && relationship[:advanced].first
18
+ @advanced_fields = Rooftop::Content::Collection.new(relationship[:advanced].collect{|adv| adv[:fields].first})
19
+
20
+ relationship[:advanced].collect{|adv| adv[:fields]}.flatten.each do |field|
21
+ self.class.send(:attr_accessor, "#{field[:name]}_field")
22
+ instance_variable_set("@#{field[:name]}_field", field[:value])
23
+ end
24
+ end
25
+ end
26
+
27
+ def role
28
+ self.respond_to?(:role_field) ? self.role_field : ''
29
+ end
30
+
31
+ def character
32
+ self.respond_to?(:character_played_field) ? self.character_played_field : ''
33
+ end
34
+
35
+ def image
36
+ (self.respond_to?(:headshot_field) && self.headshot_field.is_a?(Hash)) ? self.headshot_field[:sizes][:medium] : ''
37
+ end
38
+
39
+ def content
40
+ h.parse_content(self.fields.content).html_safe
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module Reviews
5
+
6
+ # Reviews. Requires ACF setup.
7
+
8
+ def has_featured_review?
9
+ has_field?(:featured_review) && fields.featured_review.present?
10
+ end
11
+
12
+ def has_reviews?
13
+ has_field?(:reviews) && fields.reviews.present?
14
+ end
15
+
16
+ def featured_review
17
+ has_featured_review? ? Rooftop::Content::Collection.new(fields.featured_review.first[:advanced].first[:fields]) : nil
18
+ end
19
+
20
+ def reviews
21
+ if has_reviews?
22
+ fields.reviews.collect do |review|
23
+ Rooftop::Content::Collection.new(review[:advanced].first[:fields])
24
+ end
25
+ else
26
+ return []
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ module Times
5
+
6
+ def has_instance_availabilities?
7
+ respond_to?(:event_instance_availabilities) && event_instance_availabilities.size>0
8
+ end
9
+
10
+ def date_range
11
+ dates = event_instance_availabilities.first.values.collect{|instance| DateTime.parse(instance[:starts_at])}.sort
12
+ range = [dates[0], dates[-1]].uniq
13
+ end
14
+
15
+ def has_running_time?
16
+ meta_attributes[:duration].present?
17
+ end
18
+
19
+ def running_time
20
+ if has_running_time?
21
+ t = meta_attributes[:duration].to_i
22
+ mm, ss = t.divmod(60) #=> [4515, 21]
23
+ hh, mm = mm.divmod(60) #=> [75, 15]
24
+ dd, hh = hh.divmod(24) #=> [3, 3]
25
+ time = ""
26
+ if mm == 0
27
+ time = "About #{h.pluralize(hh,'hour')}"
28
+ elsif hh == 0
29
+ time = "About #{h.pluralize(mm,'minute')}"
30
+ else
31
+ time = "About #{h.pluralize(hh, 'hour')} and #{h.pluralize(mm,'minute')}"
32
+ end
33
+ time
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ class Engine < ::Rails::Engine
5
+
6
+ isolate_namespace Rooftop::Rails::Events
7
+
8
+ config.before_initialize do
9
+
10
+ end
11
+
12
+ initializer "add_helpers" do
13
+ ActiveSupport.on_load(:action_view) do
14
+ # include Rooftop::Rails::Events::YourHelper
15
+
16
+ end
17
+ end
18
+
19
+ config.to_prepare do
20
+ ::Rails.application.eager_load!
21
+ Rooftop::Events::Event.send(:include, Rooftop::Rails::Events::Cache)
22
+ Rooftop::Events::Event.send(:include, Rooftop::Rails::Events::Scopes)
23
+ # Rooftop::Events::Instance.send(:include, Rooftop::Rails::Events::InstanceCache)
24
+ Rooftop::Events::Instance.send(:include, Rooftop::Rails::Events::BookingInformation)
25
+ end
26
+
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ require_rel "./decorators"
2
+ module Rooftop
3
+ module Rails
4
+ module Events
5
+ module InstanceDecorators
6
+ def self.included(base)
7
+ raise StandardError, "Rooftop::Rails::Events::InstanceDecorators expects to be mixed into a Draper decorator" unless defined?(Draper) && base.ancestors.include?(Draper::Decorator)
8
+ base.send(:include, Rooftop::Rails::Events::InstanceDecorator)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module Rooftop
2
+ module Rails
3
+ module Events
4
+ VERSION = "0.1.9"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rooftop/rails/events/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rooftop-rails-events"
8
+ spec.version = Rooftop::Rails::Events::VERSION
9
+ spec.authors = ["Ed Jones"]
10
+ spec.email = ["ed@errorstudio.co.uk"]
11
+
12
+ spec.summary = %q{Quickly add events to your Rails website with Rooftop CMS.}
13
+ spec.description = %q{This library integrates with Rooftop CMS's events plugin to make it easy to add events to your site.}
14
+ spec.homepage = "https://github.com/rooftopcms/rooftop-rails-events"
15
+ spec.license = "GPLv3"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.11"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+
25
+ spec.add_dependency "require_all"
26
+ spec.add_dependency "rooftop-rails"
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rooftop-rails-events
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.9
5
+ platform: ruby
6
+ authors:
7
+ - Ed Jones
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-09-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: require_all
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rooftop-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: This library integrates with Rooftop CMS's events plugin to make it easy
70
+ to add events to your site.
71
+ email:
72
+ - ed@errorstudio.co.uk
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - app/controllers/concerns/rooftop/rails/events/event_collections.rb
83
+ - app/controllers/concerns/rooftop/rails/events/event_handler.rb
84
+ - app/controllers/concerns/rooftop/rails/events/event_search.rb
85
+ - app/models/concerns/rooftop/rails/events/booking_information.rb
86
+ - app/models/concerns/rooftop/rails/events/cache.rb
87
+ - app/models/concerns/rooftop/rails/events/instance_cache.rb
88
+ - app/models/concerns/rooftop/rails/events/scopes.rb
89
+ - bin/console
90
+ - bin/setup
91
+ - lib/rooftop/rails/events.rb
92
+ - lib/rooftop/rails/events/decorators.rb
93
+ - lib/rooftop/rails/events/decorators/cast.rb
94
+ - lib/rooftop/rails/events/decorators/content.rb
95
+ - lib/rooftop/rails/events/decorators/event_detail_decorator.rb
96
+ - lib/rooftop/rails/events/decorators/instance_decorator.rb
97
+ - lib/rooftop/rails/events/decorators/media.rb
98
+ - lib/rooftop/rails/events/decorators/person_decorator.rb
99
+ - lib/rooftop/rails/events/decorators/reviews.rb
100
+ - lib/rooftop/rails/events/decorators/times.rb
101
+ - lib/rooftop/rails/events/engine.rb
102
+ - lib/rooftop/rails/events/instance_decorators.rb
103
+ - lib/rooftop/rails/events/version.rb
104
+ - rooftop-rails-events.gemspec
105
+ homepage: https://github.com/rooftopcms/rooftop-rails-events
106
+ licenses:
107
+ - GPLv3
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.2.2
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Quickly add events to your Rails website with Rooftop CMS.
129
+ test_files: []