Skip to main content

Multi-event .ICS file generation?

Rails • Asked by Brent C

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


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.


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!


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

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.


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


Hmm, can you post the stacktrace?


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'

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


Thanks Chris, super() did the trick.


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?


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.


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 27,623+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.