diff --git a/Gemfile b/Gemfile index 78191a3..20ac4e6 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ ruby '2.1.1' gem 'dotenv-rails', '~> 0.10.0' gem 'rails', '~> 3.2.17' gem 'mysql2', '~> 0.3.15' +gem 'oj', '~> 2.5.5' # Libraries gem 'jquery-rails', '~> 2.0.2' diff --git a/Gemfile.lock b/Gemfile.lock index adbb2c9..1053ce3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -127,6 +127,7 @@ GEM mini_portile (~> 0.5.0) nokogiri (1.6.1-x86-mingw32) mini_portile (~> 0.5.0) + oj (2.5.5) poltergeist (1.5.0) capybara (~> 2.1) cliver (~> 0.3.1) @@ -267,6 +268,7 @@ DEPENDENCIES mysql2 (~> 0.3.15) newrelic_rpm (~> 3.7.2.195) nokogiri (~> 1.6.1) + oj (~> 2.5.5) poltergeist (~> 1.5.0) pry-debugger (~> 0.2.2) rails (~> 3.2.17) diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb new file mode 100644 index 0000000..fddcd58 --- /dev/null +++ b/app/controllers/api/v1/base_controller.rb @@ -0,0 +1,3 @@ +class Api::V1::BaseController < ActionController::Base + respond_to :json +end \ No newline at end of file diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb new file mode 100644 index 0000000..ef6385b --- /dev/null +++ b/app/controllers/api/v1/users_controller.rb @@ -0,0 +1,5 @@ +class Api::V1::UsersController < Api::V1::BaseController + def index + respond_with Api::V1::UsersCollection.as_json + end +end \ No newline at end of file diff --git a/app/services/api/v1/collection.rb b/app/services/api/v1/collection.rb new file mode 100644 index 0000000..60eada4 --- /dev/null +++ b/app/services/api/v1/collection.rb @@ -0,0 +1,5 @@ +class Api::V1::Collection + def execute_query + ActiveRecord::Base.connection.execute(arel_query.to_sql) + end +end \ No newline at end of file diff --git a/app/services/api/v1/users_collection.rb b/app/services/api/v1/users_collection.rb new file mode 100644 index 0000000..4b9d494 --- /dev/null +++ b/app/services/api/v1/users_collection.rb @@ -0,0 +1,58 @@ +class Api::V1::UsersCollection < Api::V1::Collection + def self.as_json + self.new.data.to_json + end + + def data + { users: map_query } + end + + private + + def users_table + User.arel_table + end + + def teams_table + Team.arel_table + end + + def joins + [ + users_table[:team_id].eq(teams_table[:id]) + ] + end + + def columns + [ + users_table[:username], + users_table[:steamid], + teams_table[:name], + teams_table[:tag], + teams_table[:logo] + ] + end + + def arel_query + users_table + .project(columns) + .join(teams_table, Arel::Nodes::OuterJoin) + .on(joins) + .where(users_table[:team_id].not_eq(nil)) + .order(users_table[:id]) + end + + def map_query + execute_query.map do |row| + { + username: row[0], + steamid: row[1], + team: { + name: row[2], + tag: row[3], + logo: row[4] + } + } + end + end +end \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 6ef6ad8..b1f033f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -10,7 +10,7 @@ module Ensl # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - # config.autoload_paths += %W(#{config.root}/extras) + config.autoload_paths += Dir["#{config.root}/app/services/**/"] # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins nowt explicitly named. diff --git a/config/routes.rb b/config/routes.rb index 23122f1..e46dd5a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -115,4 +115,10 @@ Ensl::Application.routes.draw do match ':controller/:action/:id' match ':controller/:action/:id.:format' match ':controller/:action/:id/:id2' + + namespace :api do + namespace :v1 do + resources :users + end + end end diff --git a/spec/controllers/api/v1/users_controller_spec.rb b/spec/controllers/api/v1/users_controller_spec.rb new file mode 100644 index 0000000..7a73530 --- /dev/null +++ b/spec/controllers/api/v1/users_controller_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Api::V1::UsersController do + before do + request.accept = 'application/json' + end + + describe '#index' do + before do + 10.times { create(:user_with_team) } + end + + it 'returns all users and associated teams' do + users = User.all + + get :index + + expect(response).to be_success + expect(json["users"].size).to eq(users.size) + end + + it 'returns the excpected JSON keys' do + get :index + + user_json = json["users"].first + nested_team_json = user_json["team"] + + expect(user_json).to have_key("username") + expect(user_json).to have_key("steamid") + expect(user_json).to have_key("team") + expect(nested_team_json).to have_key("name") + expect(nested_team_json).to have_key("tag") + expect(nested_team_json).to have_key("logo") + end + end +end diff --git a/spec/factories/team.rb b/spec/factories/team.rb new file mode 100644 index 0000000..02b3623 --- /dev/null +++ b/spec/factories/team.rb @@ -0,0 +1,14 @@ +FactoryGirl.define do + sequence :name do |n| + "Team ##{n}" + end + + factory :team do + name + irc "#team" + web "http://team.com" + tag "[TEAM]" + country "EU" + comment "We are a team" + end +end \ No newline at end of file diff --git a/spec/factories/user.rb b/spec/factories/user.rb index edd22ff..b1a6895 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -7,13 +7,23 @@ FactoryGirl.define do "player#{n}@ensl.org" end + sequence :steamid do |n| + "0:1:#{n}" + end + factory :user do username email + steamid firstname "ENSL" lastname "Player" - steamid "0:1:23456789" country "EU" raw_password "PasswordABC123" + + factory :user_with_team do + after(:create) do |user| + create(:team, founder: user) + end + end end -end \ No newline at end of file +end diff --git a/spec/services/api/v1/users_collection_spec.rb b/spec/services/api/v1/users_collection_spec.rb new file mode 100644 index 0000000..a4844cc --- /dev/null +++ b/spec/services/api/v1/users_collection_spec.rb @@ -0,0 +1,29 @@ +describe Api::V1::UsersCollection do + let(:collection) { Api::V1::UsersCollection.new } + + describe '#execute_query' do + describe 'when there are users with no teams' do + before do + 3.times { create(:user) } + end + + it 'returns 0 results' do + expect(collection.execute_query.size).to eq(0) + end + end + + describe 'when there are some users with teams' do + before do + 3.times { create(:user_with_team) } + end + + it 'returns 3 results' do + expect(collection.execute_query.size).to eq(3) + end + + it 'returns 5 columns' do + expect(collection.execute_query.first.size).to eq(5) + end + end + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7ec93df..2aeaec0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,6 +16,7 @@ Capybara.javascript_driver = :poltergeist RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods + config.include Controllers::JsonHelpers, :type => :controller config.fixture_path = "#{::Rails.root}/spec/fixtures" config.use_transactional_fixtures = true diff --git a/spec/support/mixins/controllers.rb b/spec/support/mixins/controllers.rb new file mode 100644 index 0000000..cb2d02d --- /dev/null +++ b/spec/support/mixins/controllers.rb @@ -0,0 +1,7 @@ +module Controllers + module JsonHelpers + def json + @json ||= JSON.parse(response.body) + end + end +end