sinatra-admin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +176 -0
  6. data/Rakefile +2 -0
  7. data/dummy/config.ru +12 -0
  8. data/dummy/config/mongoid.yml +6 -0
  9. data/dummy/dummy.rb +19 -0
  10. data/dummy/models/admin.rb +6 -0
  11. data/dummy/models/comment.rb +5 -0
  12. data/dummy/models/user.rb +11 -0
  13. data/dummy/views/admin/customs/index.haml +4 -0
  14. data/dummy/views/admin/users/custom.haml +2 -0
  15. data/features/admin_login.feature +36 -0
  16. data/features/admin_logout.feature +14 -0
  17. data/features/creating_users.feature +44 -0
  18. data/features/custom_pages.feature +34 -0
  19. data/features/default_root.feature +43 -0
  20. data/features/editing_users.feature +46 -0
  21. data/features/listing_users.feature +46 -0
  22. data/features/main_menu_resources.feature +41 -0
  23. data/features/removing_users.feature +34 -0
  24. data/features/step_definitions/common_steps.rb +59 -0
  25. data/features/step_definitions/web_steps.rb +212 -0
  26. data/features/support/database_cleaner.rb +17 -0
  27. data/features/support/env.rb +18 -0
  28. data/features/support/paths.rb +30 -0
  29. data/features/support/sinatra_admin.rb +3 -0
  30. data/features/support/warden.rb +9 -0
  31. data/features/user_details.feature +31 -0
  32. data/lib/sinatra-admin.rb +42 -0
  33. data/lib/sinatra-admin/app.rb +74 -0
  34. data/lib/sinatra-admin/config.rb +45 -0
  35. data/lib/sinatra-admin/helpers/session.rb +24 -0
  36. data/lib/sinatra-admin/helpers/template_lookup.rb +7 -0
  37. data/lib/sinatra-admin/models/admin.rb +40 -0
  38. data/lib/sinatra-admin/register.rb +8 -0
  39. data/lib/sinatra-admin/register/base.rb +29 -0
  40. data/lib/sinatra-admin/register/custom.rb +10 -0
  41. data/lib/sinatra-admin/register/model.rb +75 -0
  42. data/lib/sinatra-admin/version.rb +3 -0
  43. data/lib/sinatra-admin/views/auth/login.haml +16 -0
  44. data/lib/sinatra-admin/views/edit.haml +21 -0
  45. data/lib/sinatra-admin/views/index.haml +34 -0
  46. data/lib/sinatra-admin/views/layout.haml +19 -0
  47. data/lib/sinatra-admin/views/new.haml +19 -0
  48. data/lib/sinatra-admin/views/show.haml +16 -0
  49. data/lib/sinatra-admin/warden_strategies/sinatra_admin.rb +27 -0
  50. data/sinatra-admin.gemspec +36 -0
  51. data/spec/sinatra-admin/app_spec.rb +15 -0
  52. data/spec/sinatra-admin/config_spec.rb +111 -0
  53. data/spec/sinatra-admin/models/admin_spec.rb +33 -0
  54. data/spec/sinatra-admin/register/base_spec.rb +13 -0
  55. data/spec/sinatra-admin/register/custom_spec.rb +40 -0
  56. data/spec/sinatra-admin/register/model_spec.rb +26 -0
  57. data/spec/sinatra-admin/register_spec.rb +15 -0
  58. data/spec/sinatra-admin/version_spec.rb +5 -0
  59. data/spec/sinatra-admin_spec.rb +73 -0
  60. data/spec/spec_helper.rb +81 -0
  61. metadata +343 -0
@@ -0,0 +1,17 @@
1
+ begin
2
+ require 'database_cleaner'
3
+ require 'database_cleaner/cucumber'
4
+
5
+ DatabaseCleaner.orm = "mongoid"
6
+ DatabaseCleaner.strategy = :truncation
7
+ rescue NameError
8
+ raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
9
+ end
10
+
11
+ Around do |scenario, block|
12
+ DatabaseCleaner.cleaning(&block)
13
+ end
14
+
15
+ Before do |scenario|
16
+ DatabaseCleaner.clean
17
+ end
@@ -0,0 +1,18 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require File.join(File.dirname(__FILE__), '..', '..', 'dummy/dummy.rb')
4
+ require 'capybara'
5
+ require 'capybara/cucumber'
6
+ require 'rspec'
7
+
8
+ Capybara.app = Rack::Builder.parse_file('./dummy/config.ru').first
9
+
10
+ class DummyWorld
11
+ include Capybara::DSL
12
+ include RSpec::Matchers
13
+ include RSpec::Expectations
14
+ end
15
+
16
+ World do
17
+ DummyWorld.new
18
+ end
@@ -0,0 +1,30 @@
1
+ # Taken from the cucumber-rails project.
2
+
3
+ module NavigationHelpers
4
+ # Maps a name to a path. Used by the
5
+ #
6
+ # When /^I go to (.+)$/ do |page_name|
7
+ #
8
+ # step definition in web_steps.rb
9
+ #
10
+ def path_to(page_name)
11
+ case page_name
12
+
13
+ when /the home page/
14
+ '/admin'
15
+ when /the login page/
16
+ '/admin/login'
17
+ when /my custom page/
18
+ '/admin/customs'
19
+ when /users listing/
20
+ '/admin/users'
21
+ when /users custom page/
22
+ '/admin/users/custom'
23
+ else
24
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
25
+ "Now, go and add a mapping in #{__FILE__}"
26
+ end
27
+ end
28
+ end
29
+
30
+ World(NavigationHelpers)
@@ -0,0 +1,3 @@
1
+ Before do |scenario|
2
+ SinatraAdmin.config.reset!
3
+ end
@@ -0,0 +1,9 @@
1
+ After do |scenario|
2
+ Warden.test_reset!
3
+ end
4
+
5
+ module WardenWorld
6
+ include Warden::Test::Helpers
7
+ end
8
+
9
+ World(WardenWorld)
@@ -0,0 +1,31 @@
1
+ Feature: User show
2
+ In order to use SinatraAdmin
3
+ As an Admin
4
+ I want to see the details for each user when I register the "User" resource
5
+ And I clik on an id link
6
+
7
+ Scenario: Admin tries to see user details thithout login
8
+ Given I add SinatraAdmin as middleware
9
+ And I register "User" resource
10
+ And I am an Admin
11
+ And There are users
12
+ When I go to users listing
13
+ Then I should see "Login - SinatraAdmin"
14
+ And I should see "You must log in"
15
+
16
+ Scenario: Admin sees user details when clicks the _id link
17
+ Given I add SinatraAdmin as middleware
18
+ And I register "User" resource
19
+ And I am an Admin
20
+ And I am logged in as admin
21
+ And There are users
22
+ And I am on users listing
23
+ When I click on Carlo id
24
+ Then I should see "User - Show"
25
+ And I should see "_id"
26
+ And I should see "first_name"
27
+ And I should see "last_name"
28
+ And I should see "email"
29
+ And I should see "Carlo"
30
+ And I should see "Cajucom"
31
+ And I should see "carlo@herbalife.com"
@@ -0,0 +1,42 @@
1
+ require 'active_support/inflector'
2
+
3
+ require "sinatra-admin/version"
4
+ require 'sinatra-admin/app'
5
+ require 'sinatra-admin/register'
6
+ require 'sinatra-admin/config'
7
+ require 'sinatra-admin/models/admin'
8
+
9
+ module SinatraAdmin
10
+ class << self
11
+ def register(constant_name, &block)
12
+ begin
13
+ model = constant_name.constantize
14
+ Register::Model.add(model, &block)
15
+ rescue NameError => error #Model does not exist
16
+ Register::Custom.add(constant_name, &block)
17
+ end
18
+ end
19
+
20
+ def config
21
+ @config ||= Config.new
22
+ end
23
+
24
+ def root(default)
25
+ config.root = default
26
+ end
27
+
28
+ def admin_model(constant_name)
29
+ config.admin_model = constant_name.constantize
30
+ end
31
+
32
+ def extend_views_from(target)
33
+ if target.instance_of?(String)
34
+ SinatraAdmin::App.views << "#{target}/admin"
35
+ else #Sinatra app
36
+ Array(target.views).each do |view|
37
+ SinatraAdmin::App.views << "#{view}/admin"
38
+ end
39
+ end
40
+ end
41
+ end #class << self
42
+ end #SinatraAdmin
@@ -0,0 +1,74 @@
1
+ require 'mongoid'
2
+ require 'warden'
3
+ require 'sinatra/base'
4
+ require 'sinatra/namespace'
5
+ require 'sinatra/flash'
6
+ require 'sinatra-admin/warden_strategies/sinatra_admin'
7
+ require 'sinatra-admin/helpers/session'
8
+ require "sinatra-admin/helpers/template_lookup"
9
+
10
+ module SinatraAdmin
11
+ class App < Sinatra::Base
12
+ Mongoid.raise_not_found_error = false
13
+ I18n.config.enforce_available_locales = false
14
+
15
+ set :sessions, true
16
+ set :views, [views]
17
+ set :session_secret, ENV['SINATRA_ADMIN_SECRET']
18
+
19
+ register Sinatra::Flash
20
+ register Sinatra::Namespace
21
+
22
+ helpers SinatraAdmin::SessionHelper
23
+ helpers SinatraAdmin::TemplateLookupHelper
24
+
25
+ use Rack::MethodOverride
26
+ use Warden::Manager do |config|
27
+ config.serialize_into_session(:sinatra_admin){|admin| admin.id }
28
+ config.serialize_from_session(:sinatra_admin){|id| SinatraAdmin.config.admin_model.find(id) }
29
+ config.scope_defaults :sinatra_admin,
30
+ strategies: [:sinatra_admin],
31
+ action: '/admin/unauthenticated'
32
+ config.failure_app = SinatraAdmin::App
33
+ end
34
+
35
+ Warden::Manager.before_failure do |env,opts|
36
+ env['REQUEST_METHOD'] = 'POST'
37
+ end
38
+
39
+ before do
40
+ unless public_routes.include?(request.path)
41
+ authenticate!
42
+ end
43
+ end
44
+
45
+ get '/?' do
46
+ redirect SinatraAdmin.config.default_route
47
+ end
48
+
49
+ get '/login/?' do
50
+ haml 'auth/login'.to_sym, format: :html5, layout: false
51
+ end
52
+
53
+ post '/login/?' do
54
+ if warden.authenticate(:sinatra_admin, scope: :sinatra_admin)
55
+ redirect SinatraAdmin.config.default_route
56
+ else
57
+ flash.now[:error] = warden.message
58
+ haml 'auth/login'.to_sym, format: :html5, layout: false
59
+ end
60
+ end
61
+
62
+ get '/logout/?' do
63
+ warden.logout(:sinatra_admin)
64
+ flash[:success] = 'Successfully logged out'
65
+ redirect '/admin/login'
66
+ end
67
+
68
+
69
+ post '/unauthenticated/?' do
70
+ flash[:error] = warden.message || "You must log in"
71
+ redirect '/admin/login'
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,45 @@
1
+ module SinatraAdmin
2
+ class Config
3
+ ATTRIBUTES = [:root, :admin_model]
4
+
5
+ ATTRIBUTES.each do |attr|
6
+ attr_accessor attr
7
+ end
8
+
9
+ def default_route
10
+ @default_route ||=
11
+ if root
12
+ route = root.underscore.pluralize
13
+ raise RegistrationException, "The resource #{root} was not registered" unless routes.include?(route)
14
+ "/admin/#{route}"
15
+ else
16
+ raise RegistrationException, 'You should register at least one resource' if routes.empty?
17
+ "/admin/#{routes.first}"
18
+ end
19
+ end
20
+
21
+ def admin_model
22
+ @admin_model ||= SinatraAdmin::Admin
23
+ end
24
+
25
+ # Store all registered routes.
26
+ # Executing:
27
+ # SinatraAdmin.register('User')
28
+ # SinatraAdmin.register('Tags')
29
+ #
30
+ # The result is going to be:
31
+ # @routes => ['users', 'tags']
32
+ def routes
33
+ @routes ||= []
34
+ end
35
+
36
+ ########## USE THIS METHOD CAREFULLY! ###########
37
+ # Sets ALL config to nil and remove ALL registered
38
+ # routes. It means that this method is going to remove
39
+ # the WHOLE SinatraAdmin functionality
40
+ def reset!
41
+ ATTRIBUTES.each {|attr| send("#{attr.to_s}=", nil)}
42
+ routes.clear
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ module SinatraAdmin
2
+ module SessionHelper
3
+ def public_routes
4
+ [
5
+ '/admin/login',
6
+ '/admin/login/',
7
+ '/admin/unauthenticated',
8
+ '/admin/unauthenticated/',
9
+ '/admin/not_found/'
10
+ ]
11
+ end
12
+
13
+ def authenticate!
14
+ unless warden.authenticated?(:sinatra_admin)
15
+ flash[:error] = "You must log in"
16
+ redirect to('login')
17
+ end
18
+ end
19
+
20
+ def warden
21
+ env['warden']
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module SinatraAdmin
2
+ module TemplateLookupHelper
3
+ def find_template(views, name, engine, &block)
4
+ Array(views).each { |v| super(v, name, engine, &block) }
5
+ end
6
+ end #TemplateLookupHelper
7
+ end #SinatraAdmin
@@ -0,0 +1,40 @@
1
+ require 'mongoid'
2
+ require 'bcrypt'
3
+
4
+ class EmailValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attribute, value)
6
+ unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
7
+ record.errors[attribute] << (options[:message] || "is not an email")
8
+ end
9
+ end
10
+ end
11
+
12
+ module SinatraAdmin
13
+ class Admin
14
+ include Mongoid::Document
15
+ include BCrypt
16
+
17
+ field :first_name, type: String
18
+ field :last_name, type: String
19
+ field :email, type: String
20
+ field :password_hash, type: String
21
+
22
+ validates :email,
23
+ :password_hash, presence: true
24
+
25
+ validates :email, email: true
26
+
27
+ def authenticate(attemp_password)
28
+ password == attemp_password
29
+ end
30
+
31
+ def password
32
+ @password ||= Password.new(password_hash)
33
+ end
34
+
35
+ def password=(new_password)
36
+ @password = Password.create(new_password)
37
+ self.password_hash = @password
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ require 'sinatra-admin/register/base'
2
+ require 'sinatra-admin/register/model'
3
+ require 'sinatra-admin/register/custom'
4
+
5
+ module SinatraAdmin
6
+ class RegistrationException < Exception; end
7
+ module Register; end
8
+ end
@@ -0,0 +1,29 @@
1
+ module SinatraAdmin
2
+ module Register
3
+ class Base
4
+ class << self
5
+ def add(resource_constant, &block)
6
+ route = resource_constant.to_s.downcase.split.join('_').pluralize
7
+ if SinatraAdmin.config.routes.include?(route)
8
+ raise RegistrationException, "The resource #{resource_constant.to_s} is already registered"
9
+ else
10
+ SinatraAdmin.config.routes << route
11
+ new(resource_constant, route).generate!(&block)
12
+ end
13
+ end
14
+ end
15
+
16
+ attr_reader :app, :resource_constant, :route
17
+
18
+ def initialize(resource_constant, route)
19
+ @app = SinatraAdmin::App
20
+ @route = route
21
+ @resource_constant = resource_constant
22
+ end
23
+
24
+ def generate!
25
+ raise NotImplementedError, 'You must implement me!'
26
+ end
27
+ end #Base
28
+ end #Register
29
+ end #SinatraAdmin
@@ -0,0 +1,10 @@
1
+ module SinatraAdmin
2
+ module Register
3
+ class Custom < Base
4
+ def generate!(&block)
5
+ raise RegistrationException, "You should pass a block in order to register #{resource_constant} custom resource" unless block_given?
6
+ app.namespace("/#{route}", &block)
7
+ end
8
+ end #Custom
9
+ end #Register
10
+ end #SinatraAdmin
@@ -0,0 +1,75 @@
1
+ module SinatraAdmin
2
+ module Register
3
+ class Model < Base
4
+ def generate!(&block)
5
+ app.namespace("/#{route}", &block) if block_given?
6
+ app.instance_exec(resource_constant, route) do |model, route|
7
+ before "/#{route}/?*" do
8
+ @model = model
9
+ @route = route
10
+ end
11
+
12
+ #INDEX
13
+ get "/#{route}/?" do
14
+ @collection ||= model.all.entries
15
+ haml :index, format: :html5
16
+ end
17
+
18
+ #NEW
19
+ get "/#{route}/new/?" do
20
+ @resource = model.new
21
+ haml :new, format: :html5
22
+ end
23
+
24
+ #CREATE
25
+ post "/#{route}/?" do
26
+ @resource = model.new(params[:data])
27
+ if @resource.save
28
+ puts "Resource was created"
29
+ redirect "/admin/#{@route}/#{@resource.id}"
30
+ else
31
+ puts "Validation Errors"
32
+ haml :new, format: :html5
33
+ end
34
+ end
35
+
36
+ #SHOW
37
+ get "/#{route}/:id/?" do
38
+ @resource = model.find(params[:id])
39
+ haml :show, format: :html5
40
+ end
41
+
42
+ #EDIT
43
+ get "/#{route}/:id/edit/?" do
44
+ @resource = model.find(params[:id])
45
+ haml :edit, format: :html5
46
+ end
47
+
48
+ #UPDATE
49
+ put "/#{route}/:id/?" do
50
+ @resource = model.find(params[:id])
51
+ if @resource.update_attributes(params[:data])
52
+ puts "Resource was updated"
53
+ redirect "/admin/#{@route}/#{@resource.id}"
54
+ else
55
+ puts "Validation Errors"
56
+ haml :edit, format: :html5
57
+ end
58
+ end
59
+
60
+ #DESTROY
61
+ delete "/#{route}/:id/?" do
62
+ @resource = model.find(params[:id])
63
+ if @resource.destroy
64
+ puts "Resource was destroyed"
65
+ redirect "/admin/#{route}/"
66
+ else
67
+ puts "Something wrong"
68
+ status 400
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end #Model
74
+ end #Register
75
+ end #SinatraAdmin