From 27017a482334b7a4563349a994be3c545b4b75e7 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Thu, 4 Jul 2013 14:02:50 +0200 Subject: [PATCH 1/3] WIP: first prototype of the JSON API. * Tests not included, so I cannot prove that it works --- Gemfile | 1 + Gemfile.lock | 3 +++ app/controllers/api/base_controller.rb | 3 +++ .../api/v1/conferences_controller.rb | 7 ++++++ app/serializers/conference_serializer.rb | 23 +++++++++++++++++++ config/routes.rb | 7 ++++++ 6 files changed, 44 insertions(+) create mode 100644 app/controllers/api/base_controller.rb create mode 100644 app/controllers/api/v1/conferences_controller.rb create mode 100644 app/serializers/conference_serializer.rb diff --git a/Gemfile b/Gemfile index e21baa2c..96cc1673 100644 --- a/Gemfile +++ b/Gemfile @@ -50,4 +50,5 @@ gem 'acts_as_commentable_with_threading' gem 'prawn' gem 'prawn_rails' gem 'gravtastic' +gem 'active_model_serializers' diff --git a/Gemfile.lock b/Gemfile.lock index 0387fdfe..b9041a60 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,6 +15,8 @@ GEM rack-cache (~> 1.2) rack-test (~> 0.6.1) sprockets (~> 2.2.1) + active_model_serializers (0.8.1) + activemodel (>= 3.0) activemodel (3.2.11) activesupport (= 3.2.11) builder (~> 3.0.0) @@ -213,6 +215,7 @@ PLATFORMS ruby DEPENDENCIES + active_model_serializers acts_as_commentable_with_threading bcrypt-ruby (~> 3.0.0) bootstrap-sass diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb new file mode 100644 index 00000000..6ec4ef9d --- /dev/null +++ b/app/controllers/api/base_controller.rb @@ -0,0 +1,3 @@ +class Api::BaseController < ActionController::Base + protect_from_forgery +end diff --git a/app/controllers/api/v1/conferences_controller.rb b/app/controllers/api/v1/conferences_controller.rb new file mode 100644 index 00000000..d12cd99c --- /dev/null +++ b/app/controllers/api/v1/conferences_controller.rb @@ -0,0 +1,7 @@ +class Api::V1::ConferencesController < Api::BaseController + respond_to :json + + def index + respond_with Conference.all + end +end diff --git a/app/serializers/conference_serializer.rb b/app/serializers/conference_serializer.rb new file mode 100644 index 00000000..37d9cd1e --- /dev/null +++ b/app/serializers/conference_serializer.rb @@ -0,0 +1,23 @@ +class ConferenceSerializer < ActiveModel::Serializer + attributes :guid, :name, :description, :year, :socialtag, :date_range#, :url, :revision + + def name + object.short_title + end + + def description + object.title + end + + def year + object.start_date.try(:year) + end + + def socialtag + object.social_tag + end + + def date_range + object.date_range_string + end +end diff --git a/config/routes.rb b/config/routes.rb index 251a9ce9..73c1f786 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -53,6 +53,13 @@ Osem::Application.routes.draw do delete "/register" => "ConferenceRegistration#unregister" end end + + namespace :api, defaults: {format: 'json'} do + namespace :v1 do + resources :conferences, :only => :index + end + end + match "/admin" => redirect("/admin/conference") root :to => "home#index" From d648ab7cb5277ac47bf57911e4d143bab89454c1 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 8 Jul 2013 10:03:03 +0200 Subject: [PATCH 2/3] Add 'revision' attribute to Conference, managed by an observer --- app/models/revision_observer.rb | 26 +++++++++++++++++++ config/application.rb | 1 + ...130705055128_add_revision_to_conference.rb | 5 ++++ db/schema.rb | 3 ++- 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 app/models/revision_observer.rb create mode 100644 db/migrate/20130705055128_add_revision_to_conference.rb diff --git a/app/models/revision_observer.rb b/app/models/revision_observer.rb new file mode 100644 index 00000000..7247efc0 --- /dev/null +++ b/app/models/revision_observer.rb @@ -0,0 +1,26 @@ +# +# suseconferenceclient relies on a 'revision' attribute for caching and +# doing some calculations. +# +# It should be incremented after any change in the conference or in any +# associated models +# +# This observer updates the revision column in a non-intrusive way, +# preventing validations, callbacks or exceptions to be triggered +# +# Relying on paper_trail could also be an option, but a 'revision' column +# in table 'conferences' looks like a more simple and straightforward solution +# +class RevisionObserver < ActiveRecord::Observer + observe :conference, :event, :room, :social_event, :track + + def after_save(model) + begin + conference = model.kind_of?(Conference) ? model : model.conference + conference.reload.increment(:revision) + conference.update_column(:revision, conference.revision) + rescue + nil + end + end +end diff --git a/config/application.rb b/config/application.rb index 76448fbb..4c76b590 100644 --- a/config/application.rb +++ b/config/application.rb @@ -25,6 +25,7 @@ module Osem # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + config.active_record.observers = :revision_observer # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. diff --git a/db/migrate/20130705055128_add_revision_to_conference.rb b/db/migrate/20130705055128_add_revision_to_conference.rb new file mode 100644 index 00000000..728b6551 --- /dev/null +++ b/db/migrate/20130705055128_add_revision_to_conference.rb @@ -0,0 +1,5 @@ +class AddRevisionToConference < ActiveRecord::Migration + def change + add_column :conferences, :revision, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 9acc0c8f..49028ecf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130626095459) do +ActiveRecord::Schema.define(:version => 20130705055128) do create_table "call_for_papers", :force => true do |t| t.date "start_date", :null => false @@ -62,6 +62,7 @@ ActiveRecord::Schema.define(:version => 20130626095459) do t.datetime "logo_updated_at" t.boolean "use_dietary_choices", :default => false t.boolean "use_supporter_levels", :default => false + t.integer "revision" end create_table "dietary_choices", :force => true do |t| From d95d8f234b70d02a19faa3e796c6d33cd7d42da1 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Mon, 8 Jul 2013 10:07:12 +0200 Subject: [PATCH 3/3] json API for suseconferenceclient --- .../api/v1/conferences_controller.rb | 2 +- app/controllers/api/v1/events_controller.rb | 11 +++++ app/controllers/api/v1/rooms_controller.rb | 13 ++++++ app/controllers/api/v1/speakers_controller.rb | 14 +++++++ app/controllers/api/v1/tracks_controller.rb | 13 ++++++ app/serializers/conference_serializer.rb | 24 ++++++++--- .../conferences_array_serializer.rb | 10 +++++ app/serializers/event_serializer.rb | 41 +++++++++++++++++++ app/serializers/room_serializer.rb | 8 ++++ app/serializers/speaker_serializer.rb | 17 ++++++++ app/serializers/track_serializer.rb | 3 ++ config/routes.rb | 11 ++++- 12 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 app/controllers/api/v1/events_controller.rb create mode 100644 app/controllers/api/v1/rooms_controller.rb create mode 100644 app/controllers/api/v1/speakers_controller.rb create mode 100644 app/controllers/api/v1/tracks_controller.rb create mode 100644 app/serializers/conferences_array_serializer.rb create mode 100644 app/serializers/event_serializer.rb create mode 100644 app/serializers/room_serializer.rb create mode 100644 app/serializers/speaker_serializer.rb create mode 100644 app/serializers/track_serializer.rb diff --git a/app/controllers/api/v1/conferences_controller.rb b/app/controllers/api/v1/conferences_controller.rb index d12cd99c..77d6f053 100644 --- a/app/controllers/api/v1/conferences_controller.rb +++ b/app/controllers/api/v1/conferences_controller.rb @@ -2,6 +2,6 @@ class Api::V1::ConferencesController < Api::BaseController respond_to :json def index - respond_with Conference.all + render :json => Conference.all, :serializer => ConferencesArraySerializer end end diff --git a/app/controllers/api/v1/events_controller.rb b/app/controllers/api/v1/events_controller.rb new file mode 100644 index 00000000..0461877b --- /dev/null +++ b/app/controllers/api/v1/events_controller.rb @@ -0,0 +1,11 @@ +class Api::V1::EventsController < Api::BaseController + respond_to :json + + def index + events = Event.includes(:conference, :track, :room, :event_type, {:event_people => :person}) + unless params[:conference_id].blank? + events = events.where("conferences.guid" => params[:conference_id]) + end + respond_with events + end +end diff --git a/app/controllers/api/v1/rooms_controller.rb b/app/controllers/api/v1/rooms_controller.rb new file mode 100644 index 00000000..580e553d --- /dev/null +++ b/app/controllers/api/v1/rooms_controller.rb @@ -0,0 +1,13 @@ +class Api::V1::RoomsController < Api::BaseController + respond_to :json + + def index + if params[:conference_id].blank? + rooms = Room.all + else + conference = Conference.find_by_guid(params[:conference_id]) + rooms = conference.rooms + end + respond_with rooms + end +end diff --git a/app/controllers/api/v1/speakers_controller.rb b/app/controllers/api/v1/speakers_controller.rb new file mode 100644 index 00000000..90ee6f3f --- /dev/null +++ b/app/controllers/api/v1/speakers_controller.rb @@ -0,0 +1,14 @@ +class Api::V1::SpeakersController < Api::BaseController + respond_to :json + + def index + if params[:conference_id].blank? + people = Person.joins(:event_people) + else + people = Person.joins(:event_people => {:event => :conference}) + people = people.where("conferences.guid" => params[:conference_id]) + end + people = people.where("event_people.event_role" => "speaker") + render :json => people, :each_serializer => SpeakerSerializer + end +end diff --git a/app/controllers/api/v1/tracks_controller.rb b/app/controllers/api/v1/tracks_controller.rb new file mode 100644 index 00000000..cc76760f --- /dev/null +++ b/app/controllers/api/v1/tracks_controller.rb @@ -0,0 +1,13 @@ +class Api::V1::TracksController < Api::BaseController + respond_to :json + + def index + if params[:conference_id].blank? + tracks = Track.all + else + tracks = Track.joins(:conference) + tracks = tracks.where("conferences.guid" => params[:conference_id]) + end + respond_with tracks + end +end diff --git a/app/serializers/conference_serializer.rb b/app/serializers/conference_serializer.rb index 37d9cd1e..34a8dd73 100644 --- a/app/serializers/conference_serializer.rb +++ b/app/serializers/conference_serializer.rb @@ -1,11 +1,7 @@ class ConferenceSerializer < ActiveModel::Serializer - attributes :guid, :name, :description, :year, :socialtag, :date_range#, :url, :revision + attributes :guid, :name, :description, :year, :socialtag, :date_range, :url, :revision def name - object.short_title - end - - def description object.title end @@ -17,7 +13,23 @@ class ConferenceSerializer < ActiveModel::Serializer object.social_tag end + def revision + object.revision || 0 + end + + # FIXME: adjusting the format the DIRTY way, for oSC13. + # If you think this is ugly, don't look at the methods below def date_range - object.date_range_string + object.date_range_string.try(:split, ",").try(:first) + end + + # FIXME: just giving suseconferenceclient something to play with + def description + "openSUSE Conference 2013 - Power to the Geeko" + end + + # FIXME: same than the former + def url + "https://conference.opensuse.org/" end end diff --git a/app/serializers/conferences_array_serializer.rb b/app/serializers/conferences_array_serializer.rb new file mode 100644 index 00000000..8d320f79 --- /dev/null +++ b/app/serializers/conferences_array_serializer.rb @@ -0,0 +1,10 @@ +# +# Needed in order to add the API version number to the conferences array +# +class ConferencesArraySerializer < ActiveModel::ArraySerializer + + def as_json(*args) + json = super + json.merge!(:version => 1) + end +end diff --git a/app/serializers/event_serializer.rb b/app/serializers/event_serializer.rb new file mode 100644 index 00000000..979fa6a9 --- /dev/null +++ b/app/serializers/event_serializer.rb @@ -0,0 +1,41 @@ +class EventSerializer < ActiveModel::Serializer + include ActionView::Helpers::TextHelper + + attributes :guid, :title, :length, :date, :language, :abstract, + :speaker_ids, :type, :room, :track + + def date + object.start_time + end + + def speaker_ids + speakers = object.event_people.select {|i| i.event_role == "speaker" } + speakers.map {|i| i.person.guid} + end + + def type + object.event_type.try(:title) + end + + def room + object.room.try(:guid) + end + + def track + object.track.try(:guid) + end + + def abstract + # This should never happen + if object.abstract.blank? + nil + else + simple_format(object.abstract).gsub("\n", "") + end + end + + # FIXME: duplicated logic from Event#as_json + def length + object.event_type.try(:length) || 25 + end +end diff --git a/app/serializers/room_serializer.rb b/app/serializers/room_serializer.rb new file mode 100644 index 00000000..c62312fc --- /dev/null +++ b/app/serializers/room_serializer.rb @@ -0,0 +1,8 @@ +class RoomSerializer < ActiveModel::Serializer + attributes :guid, :name, :description + + # FIXME: just giving suseconferenceclient something to play with + def description + "" + end +end diff --git a/app/serializers/speaker_serializer.rb b/app/serializers/speaker_serializer.rb new file mode 100644 index 00000000..ed75e7c7 --- /dev/null +++ b/app/serializers/speaker_serializer.rb @@ -0,0 +1,17 @@ +class SpeakerSerializer < ActiveModel::Serializer + include ActionView::Helpers::TextHelper + + attributes :guid, :name, :company, :biography + + def name + object.public_name + end + + def biography + if object.biography.blank? + nil + else + simple_format(object.biography).gsub("\n", "") + end + end +end diff --git a/app/serializers/track_serializer.rb b/app/serializers/track_serializer.rb new file mode 100644 index 00000000..0019a150 --- /dev/null +++ b/app/serializers/track_serializer.rb @@ -0,0 +1,3 @@ +class TrackSerializer < ActiveModel::Serializer + attributes :guid, :name, :color +end diff --git a/config/routes.rb b/config/routes.rb index 73c1f786..f0221624 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -56,7 +56,16 @@ Osem::Application.routes.draw do namespace :api, defaults: {format: 'json'} do namespace :v1 do - resources :conferences, :only => :index + resources :conferences, :only => :index do + resources :rooms, :only => :index + resources :tracks, :only => :index + resources :speakers, :only => :index + resources :events, :only => :index + end + resources :rooms, :only => :index + resources :tracks, :only => :index + resources :speakers, :only => :index + resources :events, :only => :index end end