diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1fc9be1..47ab4ec 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,6 +10,9 @@ class ApplicationController < ActionController::Base # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + # Enable CanCan + enable_authorization + # Allows decent exposure to use strong parameters. decent_configuration do strategy DecentExposure::StrongParametersStrategy diff --git a/app/controllers/characters_controller.rb b/app/controllers/characters_controller.rb new file mode 100644 index 0000000..31f8141 --- /dev/null +++ b/app/controllers/characters_controller.rb @@ -0,0 +1,12 @@ +class CharactersController < ApplicationController + expose :character, attributes: :character_params + expose(:characters){ current_user.characters } + load_and_authorize_resource + +private + + def character_params + params.require(:character).permit(:name) + end + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c455360..71cc522 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,6 @@ class UsersController < ApplicationController expose :user, attributes: :user_params + load_and_authorize_resource def update user.save diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 0000000..dc3fdd7 --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,21 @@ +class Ability + include CanCan::Ability + + def initialize(user) + user ||= User.new + + can :access, :sessions + can :read, :application + can :read, :characters + + # Registered users + if user.persisted? + can :update, :users, id: user.id + + if user.valid? + can :update, :characters, user_id: user.id + can :access, :users, id: user.id + end + end + end +end diff --git a/app/models/character.rb b/app/models/character.rb new file mode 100644 index 0000000..91d4d3f --- /dev/null +++ b/app/models/character.rb @@ -0,0 +1,10 @@ +class Character + include Mongoid::Document + include Mongoid::Timestamps + + field :name, type: String + + belongs_to :user + + validates :name, presence: true +end diff --git a/app/models/user.rb b/app/models/user.rb index d82b8cd..c7f622e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -22,6 +22,9 @@ class User validates :age, presence: true, confirmation: true, numericality: { greater_than_or_equal_to: 16 } + # Associations + has_many :characters + # Returns a user object based on the hash schema returned by the omniauth # gem. # diff --git a/app/views/characters/index.html.haml b/app/views/characters/index.html.haml new file mode 100644 index 0000000..f5868aa --- /dev/null +++ b/app/views/characters/index.html.haml @@ -0,0 +1,15 @@ +.container + .row + .span12 + %h1= t(".title") + %p.lead= t(".lead") + + .row + .span12 + .media + - characters.each do |character| + = link_to [:edit, character], class: "pull-left" do + %img(data-source="http://placehold.it/64x64" class="media-object") + .media-body + = link_to [:edit, character] do + %h2= character.name diff --git a/config/initializers/cancan.rb b/config/initializers/cancan.rb new file mode 100644 index 0000000..9f15d2f --- /dev/null +++ b/config/initializers/cancan.rb @@ -0,0 +1,16 @@ +module CanCan + class ControllerResource + + # Extends CanCan in such a way that it supports the way that decent + # exposure sets collection variables. + # + def collection_instance + collection_name = instance_name.to_s.pluralize + if @controller.instance_variable_defined? "@#{collection_name}" + @controller.instance_variable_get("@#{collection_name}") + elsif @controller.respond_to?(collection_name, true) + @controller.send(collection_name) + end + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index da6d53a..bf81b70 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,4 +1,16 @@ en: + sessions: + new: + sign_in: Sign in + twitter_sign_in: "Sign in with twitter" + developer_sign_in: "Sign in as a developer" + + characters: + index: + title: "My characters" + lead: | + Below you can see your current characters, and their progress. + layouts: menu: sign_out_tip: "Sign out" @@ -23,7 +35,7 @@ en: create: success: You have been signed in missing_information: | - To somplete you sign in, we need a little more information + To complete you sign in, we need a little more information application: render_404: Sorry. That page isn't available. render_401: You are not authorized to view that page! diff --git a/db/seeds.rb b/db/seeds.rb index a4aa5e4..a2a990b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -6,3 +6,6 @@ role: :admin, email: nil emil.save validation: false + +FactoryGirl.create :character, user: emil +FactoryGirl.create :character, user: emil diff --git a/spec/controllers/characters_controller_spec.rb b/spec/controllers/characters_controller_spec.rb new file mode 100644 index 0000000..47dd07d --- /dev/null +++ b/spec/controllers/characters_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe CharactersController do + +end diff --git a/spec/factories/characters.rb b/spec/factories/characters.rb new file mode 100644 index 0000000..eb37844 --- /dev/null +++ b/spec/factories/characters.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :character do + name { Faker::Name.name } + user nil + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index bca890b..4b30b45 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -4,7 +4,7 @@ factory :user do name { Faker::Name.name } email { Faker::Internet.email } - uid 345345 + uid { Faker::Base.numerify "#######" } provider "twitter" role "user" session_id { SecureRandom.urlsafe_base64 } @@ -19,5 +19,13 @@ credentials: { token: u.token } end end + + trait :with_characters do + after :create do |u| + create :character, user: u + create :character, user: u + u = u.reload + end + end end end diff --git a/spec/features/characters_spec.rb b/spec/features/characters_spec.rb new file mode 100644 index 0000000..a9d5d46 --- /dev/null +++ b/spec/features/characters_spec.rb @@ -0,0 +1,24 @@ +require "spec_helper" + +describe "Listing characters" do + let(:user) { create :user, :with_characters, :mock_omniauth } + let(:other_user) { create :user, :with_characters } + + before do + user # execute the block to stub the user + visit sign_in_path + click_link "twitter_sign_in" + visit characters_path + end + + describe "page" do + subject{ page } + + it { should have_content I18n.translate("characters.index.title") } + it "should have all the user's characters" do + user.characters.each do |character| + should have_content character.name + end + end + end +end diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index a735972..19428a2 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -60,7 +60,7 @@ describe "Update settings" do before do - @user = create :user, :mock_omniauth, provider: :developer + @user = create :user, :mock_omniauth end context "when logged in" do diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb new file mode 100644 index 0000000..1870116 --- /dev/null +++ b/spec/models/ability_spec.rb @@ -0,0 +1,34 @@ +require "spec_helper" +require "cancan/matchers" + +describe Ability do + subject{ Ability.new(user) } + + describe "as guest" do + let(:user) { build :user } + + it { should be_able_to :access, :sessions } + it { should be_able_to :read, :application } + it { should_not be_able_to :access, :characters } + it { should_not be_able_to :update, :user } + end + + describe "as registered, incomplete user" do + let(:user) { u = build(:user, email: nil); u.save(validate: false); u } + + it { should be_able_to :access, :sessions } + it { should_not be_able_to :access, :characters } + it { should be_able_to :update, user } + end + + describe "as registered, complete user" do + let(:user) { create :user } + let(:other_user) { create :user } + + it { should be_able_to :access, :sessions } + it { should be_able_to :update, :characters, user_id: user.id } + it { should be_able_to :update, build(:character, user: user) } + it { should_not be_able_to :update, build(:character, user: other_user) } + it { should be_able_to :access, user } + end +end diff --git a/spec/models/character_spec.rb b/spec/models/character_spec.rb new file mode 100644 index 0000000..8e48999 --- /dev/null +++ b/spec/models/character_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Character do + subject{ build :character } + + # Fields + it { should be_timestamped_document } + it { should have_field(:name).with_default_value_of(nil) } + + # Validations + it { should be_valid } + it { should validate_presence_of(:name) } + + # Associations + it { should belong_to :user } + +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3fc94d0..57fc86e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -43,6 +43,16 @@ it { should validate_presence_of(:age) } it { should validate_numericality_of(:age).greater_than_or_equal_to(16) } + describe "factories" do + describe "with_characters" do + subject{ create(:user, :with_characters).characters } + its(:count) { should eq 2 } + end + end + + # Associations + it { should have_many :characters } + describe "class methods" do describe ".find_or_build_from_omniauth_hash" do subject{ User.find_or_build_from_omniauth_hash(hash) }