Ask A Question

Notifications

You’re not receiving notifications from this thread.

Multi-event .ICS file generation?

Brent C asked in Rails

Any suggestions for generating an .ICS calendar file containing multiple events from my models? Any experience with Gems(iCalendar vs Ri_cal), or anyone know of good blogposts, etc. detailing the process?

Thanks

Reply

I haven't, but it definitely looks like ri_cal hasn't been touched in several years. I'd probably have to recommend iCalendar just based on that but their documentation and API don't appear to be the cleanest.

Wish I had more information for you on it, but I haven't had to create .ics files before. Maybe that'll make for a good screencast.

Reply

Thanks Chris.

Yeah, I'm not thrilled with either option. Im going to dig around, might be easier to design a custom solution... or not, we'll see.

"Maybe that'll make for a good screencast."
Haha, It sure would, right about now!

Reply

OK after digging around some I decided to dive head into iCalendar. It wasn't to bad just had poor documentation so I had to read the gems codebase to get a feel for what was happening under the hood. Here is my solution for generating multi-event ics files using iCalendar.

I defined a custom method in my Events Controller:

def ics_export
  @myevents = Event.all
  respond_to do |format|
    format.html
    format.ics do
      cal = Icalendar::Calendar.new
      @myevents.each do |myevent|
         event = Icalendar::Event.new
         event.dtstart = myevent.event_date
         event.summary = myevent.name
         cal.add_event(event)
         cal.publish
       end
       render :text =>  cal.to_ical 
     end
   end
end
Reply

Awesome! That's not too bad.

I believe you could extract this out into its own class to clean up your controller a bit:

def ics_export
  @myevents = Event.all

  respond_to do |format|
    format.html
    format.ics { render text: Ical.new(@myevents).to_ical }
   end
end
# app/models/ical.rb
class Ical < Icalendar::Calendar
  def initialize(events)
    events.each do |myevent|
      event = Icalendar::Event.new
      event.dtstart = myevent.event_date
      event.summary = myevent.name
      add_event(event)
    end
    publish
  end
end

Note that in this file you don't need to call cal. on add_event and publish since you've inherited those methods and you're inside the object. I also pulled out publish to the end so it only gets called once. I think that doesn't need to be run each loop but maybe it does.

Reply

This is a much nicer implementation! However, I am running into an error and can't figure out what is happing here...

undefined method `keys' for nil:NilClass

I am not using any method with "keys" in it. But I did notice iCalendar is so perhaps it has Something to do with the inheritance?

Thanks

Reply

Hmm, can you post the stacktrace?

Reply
icalendar (2.2.2) lib/icalendar/component.rb:35:in `ical_properties'
icalendar (2.2.2) lib/icalendar/component.rb:26:in `to_ical'
app/controllers/events_controller.rb:74:in `block (2 levels) in ics_export'
actionpack (4.1.1) lib/action_controller/metal/mime_responds.rb:258:in `call'
actionpack (4.1.1) lib/action_controller/metal/mime_responds.rb:258:in `respond_to'
app/controllers/events_controller.rb:72:in `ics_export'
actionpack (4.1.1) lib/action_controller/metal/implicit_render.rb:4:in `send_action'
actionpack (4.1.1) lib/abstract_controller/base.rb:189:in `process_action'
actionpack (4.1.1) lib/action_controller/metal/rendering.rb:10:in `process_action'
actionpack (4.1.1) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
activesupport (4.1.1) lib/active_support/callbacks.rb:113:in `call'
activesupport (4.1.1) lib/active_support/callbacks.rb:113:in `call'
activesupport (4.1.1) lib/active_support/callbacks.rb:149:in `block in halting_and_conditional'
activesupport (4.1.1) lib/active_support/callbacks.rb:229:in `call'
activesupport (4.1.1) lib/active_support/callbacks.rb:229:in `block in halting'
activesupport (4.1.1) lib/active_support/callbacks.rb:229:in `call'
activesupport (4.1.1) lib/active_support/callbacks.rb:229:in `block in halting'
activesupport (4.1.1) lib/active_support/callbacks.rb:166:in `call'
activesupport (4.1.1) lib/active_support/callbacks.rb:166:in `block in halting'
activesupport (4.1.1) lib/active_support/callbacks.rb:166:in `call'
activesupport (4.1.1) lib/active_support/callbacks.rb:166:in `block in halting'
activesupport (4.1.1) lib/active_support/callbacks.rb:166:in `call'
activesupport (4.1.1) lib/active_support/callbacks.rb:166:in `block in halting'
activesupport (4.1.1) lib/active_support/callbacks.rb:86:in `call'
activesupport (4.1.1) lib/active_support/callbacks.rb:86:in `run_callbacks'
actionpack (4.1.1) lib/abstract_controller/callbacks.rb:19:in `process_action'
actionpack (4.1.1) lib/action_controller/metal/rescue.rb:29:in `process_action'
actionpack (4.1.1) lib/action_controller/metal/instrumentation.rb:31:in `block in process_action'
activesupport (4.1.1) lib/active_support/notifications.rb:159:in `block in instrument'
activesupport (4.1.1) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
activesupport (4.1.1) lib/active_support/notifications.rb:159:in `instrument'
actionpack (4.1.1) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
actionpack (4.1.1) lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
activerecord (4.1.1) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
actionpack (4.1.1) lib/abstract_controller/base.rb:136:in `process'
actionview (4.1.1) lib/action_view/rendering.rb:30:in `process'
actionpack (4.1.1) lib/action_controller/metal.rb:195:in `dispatch'
actionpack (4.1.1) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
actionpack (4.1.1) lib/action_controller/metal.rb:231:in `block in action'
actionpack (4.1.1) lib/action_dispatch/routing/route_set.rb:80:in `call'
actionpack (4.1.1) lib/action_dispatch/routing/route_set.rb:80:in `dispatch'
actionpack (4.1.1) lib/action_dispatch/routing/route_set.rb:48:in `call'
actionpack (4.1.1) lib/action_dispatch/journey/router.rb:71:in `block in call'
actionpack (4.1.1) lib/action_dispatch/journey/router.rb:59:in `each'
actionpack (4.1.1) lib/action_dispatch/journey/router.rb:59:in `call'
actionpack (4.1.1) lib/action_dispatch/routing/route_set.rb:676:in `call'
rack (1.5.2) lib/rack/etag.rb:23:in `call'
rack (1.5.2) lib/rack/conditionalget.rb:25:in `call'
rack (1.5.2) lib/rack/head.rb:11:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/flash.rb:254:in `call'
rack (1.5.2) lib/rack/session/abstract/id.rb:225:in `context'
rack (1.5.2) lib/rack/session/abstract/id.rb:220:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/cookies.rb:560:in `call'
activerecord (4.1.1) lib/active_record/query_cache.rb:36:in `call'
activerecord (4.1.1) lib/active_record/connection_adapters/abstract/connection_pool.rb:621:in `call'
activerecord (4.1.1) lib/active_record/migration.rb:380:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
activesupport (4.1.1) lib/active_support/callbacks.rb:82:in `run_callbacks'
actionpack (4.1.1) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/reloader.rb:73:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/remote_ip.rb:76:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
railties (4.1.1) lib/rails/rack/logger.rb:38:in `call_app'
railties (4.1.1) lib/rails/rack/logger.rb:20:in `block in call'
activesupport (4.1.1) lib/active_support/tagged_logging.rb:68:in `block in tagged'
activesupport (4.1.1) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (4.1.1) lib/active_support/tagged_logging.rb:68:in `tagged'
railties (4.1.1) lib/rails/rack/logger.rb:20:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/request_id.rb:21:in `call'
rack (1.5.2) lib/rack/methodoverride.rb:21:in `call'
rack (1.5.2) lib/rack/runtime.rb:17:in `call'
activesupport (4.1.1) lib/active_support/cache/strategy/local_cache_middleware.rb:26:in `call'
rack (1.5.2) lib/rack/lock.rb:17:in `call'
actionpack (4.1.1) lib/action_dispatch/middleware/static.rb:64:in `call'
rack (1.5.2) lib/rack/sendfile.rb:112:in `call'
railties (4.1.1) lib/rails/engine.rb:514:in `call'
railties (4.1.1) lib/rails/application.rb:144:in `call'
rack (1.5.2) lib/rack/lock.rb:17:in `call'
rack (1.5.2) lib/rack/content_length.rb:14:in `call'
rack (1.5.2) lib/rack/handler/webrick.rb:60:in `service'
Reply

I wonder if maybe you need to call super() at the beginning of the initialize method so the Icalendar gets initialized properly.

Reply

Thanks Chris, super() did the trick.

Reply

hmm, just stumbled over this thread.. trying to use this on an invoices calendar, but calender is always empty. I'm on rails 5 and using active admin 2.2 ..
you don't have a web cast about this yet?

Reply

well, nevermind, I put it in the controlller on the active admin index page like this:

def index
      @myevents = Invoice.ransack(params[:q]).result
      super do |format|
        format.ics do
          cal = Icalendar::Calendar.new
          @myevents.each do |myevent|
            event = Icalendar::Event.new
            event.dtstart = myevent.due
            event.summary = myevent.sm_contract.customer.name
            cal.add_event(event)
          end
          cal.publish
          render :plain =>  cal.to_ical
        end
      end
    end

maybe not as smart as your proposal but it's working :-) Still this thread was helpful for me.

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 87,400+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.