introspective_admin 0.0.1

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 (119) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +279 -0
  5. data/LICENSE +22 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +52 -0
  8. data/Rakefile +26 -0
  9. data/bin/rails +12 -0
  10. data/introspective_admin.gemspec +38 -0
  11. data/lib/introspective_admin/base.rb +170 -0
  12. data/lib/introspective_admin/version.rb +3 -0
  13. data/lib/introspective_admin.rb +4 -0
  14. data/lib/tasks/introspective_admin_tasks.rake +4 -0
  15. data/spec/admin/company_admin_spec.rb +72 -0
  16. data/spec/admin/job_admin_spec.rb +61 -0
  17. data/spec/admin/location_admin_spec.rb +65 -0
  18. data/spec/admin/project__admin_spec.rb +71 -0
  19. data/spec/dummy/README.rdoc +28 -0
  20. data/spec/dummy/Rakefile +6 -0
  21. data/spec/dummy/app/admin/company_admin.rb +4 -0
  22. data/spec/dummy/app/admin/job_admin.rb +4 -0
  23. data/spec/dummy/app/admin/location_admin.rb +4 -0
  24. data/spec/dummy/app/admin/project_admin.rb +6 -0
  25. data/spec/dummy/app/admin/role_admin.rb +5 -0
  26. data/spec/dummy/app/admin/user_admin.rb +9 -0
  27. data/spec/dummy/app/assets/images/.keep +0 -0
  28. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  29. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  30. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  31. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  32. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  33. data/spec/dummy/app/mailers/.keep +0 -0
  34. data/spec/dummy/app/models/.keep +0 -0
  35. data/spec/dummy/app/models/abstract_adapter.rb +13 -0
  36. data/spec/dummy/app/models/admin_user.rb +6 -0
  37. data/spec/dummy/app/models/chat.rb +18 -0
  38. data/spec/dummy/app/models/chat_message.rb +34 -0
  39. data/spec/dummy/app/models/chat_message_user.rb +17 -0
  40. data/spec/dummy/app/models/chat_user.rb +16 -0
  41. data/spec/dummy/app/models/company.rb +12 -0
  42. data/spec/dummy/app/models/concerns/.keep +0 -0
  43. data/spec/dummy/app/models/job.rb +10 -0
  44. data/spec/dummy/app/models/locatable.rb +6 -0
  45. data/spec/dummy/app/models/location.rb +26 -0
  46. data/spec/dummy/app/models/location_beacon.rb +16 -0
  47. data/spec/dummy/app/models/location_gps.rb +64 -0
  48. data/spec/dummy/app/models/project.rb +20 -0
  49. data/spec/dummy/app/models/project_job.rb +7 -0
  50. data/spec/dummy/app/models/role.rb +25 -0
  51. data/spec/dummy/app/models/team.rb +9 -0
  52. data/spec/dummy/app/models/team_user.rb +13 -0
  53. data/spec/dummy/app/models/user/chatter.rb +79 -0
  54. data/spec/dummy/app/models/user.rb +75 -0
  55. data/spec/dummy/app/models/user_location.rb +28 -0
  56. data/spec/dummy/app/models/user_project_job.rb +16 -0
  57. data/spec/dummy/app/views/layouts/application.html.erb +13 -0
  58. data/spec/dummy/bin/bundle +3 -0
  59. data/spec/dummy/bin/rails +4 -0
  60. data/spec/dummy/bin/rake +4 -0
  61. data/spec/dummy/bin/setup +29 -0
  62. data/spec/dummy/config/application.rb +32 -0
  63. data/spec/dummy/config/boot.rb +5 -0
  64. data/spec/dummy/config/database.yml +18 -0
  65. data/spec/dummy/config/environment.rb +11 -0
  66. data/spec/dummy/config/environments/development.rb +41 -0
  67. data/spec/dummy/config/environments/production.rb +79 -0
  68. data/spec/dummy/config/environments/test.rb +43 -0
  69. data/spec/dummy/config/initializers/active_admin.rb +7 -0
  70. data/spec/dummy/config/initializers/assets.rb +11 -0
  71. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  72. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  73. data/spec/dummy/config/initializers/devise.rb +260 -0
  74. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  75. data/spec/dummy/config/initializers/inflections.rb +16 -0
  76. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  77. data/spec/dummy/config/initializers/session_store.rb +3 -0
  78. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  79. data/spec/dummy/config/locales/en.yml +23 -0
  80. data/spec/dummy/config/routes.rb +9 -0
  81. data/spec/dummy/config/secrets.yml +22 -0
  82. data/spec/dummy/config.ru +4 -0
  83. data/spec/dummy/db/development.sqlite3 +0 -0
  84. data/spec/dummy/db/migrate/20141002205024_devise_create_users.rb +42 -0
  85. data/spec/dummy/db/migrate/20141002211055_devise_create_admin_users.rb +48 -0
  86. data/spec/dummy/db/migrate/20141002211057_create_active_admin_comments.rb +19 -0
  87. data/spec/dummy/db/migrate/20141002220722_add_lockable_to_users.rb +8 -0
  88. data/spec/dummy/db/migrate/20150406213646_create_companies.rb +11 -0
  89. data/spec/dummy/db/migrate/20150414213154_add_user_authentication_token.rb +11 -0
  90. data/spec/dummy/db/migrate/20150415222005_create_roles.rb +12 -0
  91. data/spec/dummy/db/migrate/20150505181635_create_chats.rb +9 -0
  92. data/spec/dummy/db/migrate/20150505181636_create_chat_users.rb +11 -0
  93. data/spec/dummy/db/migrate/20150505181640_create_chat_messages.rb +11 -0
  94. data/spec/dummy/db/migrate/20150507191529_create_chat_message_users.rb +11 -0
  95. data/spec/dummy/db/migrate/20150601200526_create_locations.rb +12 -0
  96. data/spec/dummy/db/migrate/20150601200533_create_locatables.rb +10 -0
  97. data/spec/dummy/db/migrate/20150601212924_create_location_beacons.rb +15 -0
  98. data/spec/dummy/db/migrate/20150601213542_create_location_gps.rb +12 -0
  99. data/spec/dummy/db/migrate/20150609201823_create_user_locations.rb +14 -0
  100. data/spec/dummy/db/migrate/20150617232519_create_projects.rb +10 -0
  101. data/spec/dummy/db/migrate/20150617232521_create_jobs.rb +9 -0
  102. data/spec/dummy/db/migrate/20150617232522_create_project_jobs.rb +11 -0
  103. data/spec/dummy/db/migrate/20150623170133_create_user_project_jobs.rb +12 -0
  104. data/spec/dummy/db/migrate/20150701234929_create_teams.rb +11 -0
  105. data/spec/dummy/db/migrate/20150701234930_create_team_users.rb +11 -0
  106. data/spec/dummy/db/migrate/20150727214950_add_confirmable_to_devise.rb +11 -0
  107. data/spec/dummy/db/migrate/20150820190524_add_user_names.rb +6 -0
  108. data/spec/dummy/db/migrate/20150909225019_add_password_to_project.rb +5 -0
  109. data/spec/dummy/db/schema.rb +261 -0
  110. data/spec/dummy/lib/assets/.keep +0 -0
  111. data/spec/dummy/log/.keep +0 -0
  112. data/spec/dummy/public/404.html +67 -0
  113. data/spec/dummy/public/422.html +67 -0
  114. data/spec/dummy/public/500.html +66 -0
  115. data/spec/dummy/public/favicon.ico +0 -0
  116. data/spec/rails_helper.rb +13 -0
  117. data/spec/support/blueprints.rb +119 -0
  118. data/spec/support/location_helper.rb +56 -0
  119. metadata +420 -0
@@ -0,0 +1,72 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe Admin::CompaniesController, :type => :controller do
4
+ render_views
5
+
6
+ before :each do
7
+ user = double('user')
8
+ allow(request.env['warden']).to receive(:authenticate!) { user }
9
+ allow(controller).to receive(:current_user) { user }
10
+ end
11
+
12
+ describe "GET index" do
13
+ it "finds all companies" do
14
+ c = Company.make!
15
+ get :index
16
+ response.status.should == 200
17
+ assigns(:companies).include?(c).should == true
18
+ end
19
+ end
20
+
21
+ describe "SHOW record" do
22
+ it "finds the record" do
23
+ c = Company.make!
24
+ get :show, id: c.id
25
+ response.status.should == 200
26
+ end
27
+ end
28
+
29
+ describe "NEW record" do
30
+ it "renders the form for a new record" do
31
+ get :new
32
+ response.status.should == 200
33
+ end
34
+ end
35
+
36
+ describe "CREATE record" do
37
+ it "creates the record" do
38
+ c = Company.make
39
+ post :create, company: c.attributes
40
+ response.should redirect_to action: :show, id: Company.last.id
41
+ Company.last.name.should == c.name
42
+ end
43
+
44
+ it "creates a record with an admin" do
45
+ u = User.make!
46
+ c = Company.make
47
+ post :create, company: c.attributes.merge({
48
+ roles_attributes: {'0'=>{ user_id: u.id }}
49
+ })
50
+ response.should redirect_to action: :show, id: Company.last.id
51
+ Company.last.admins.include?(u).should be_truthy
52
+ end
53
+
54
+ end
55
+
56
+ describe "EDIT record" do
57
+ it "renders the edit form for an existing record" do
58
+ r = Company.make!
59
+ get :edit, id: r.id
60
+ response.status.should == 200
61
+ end
62
+ end
63
+
64
+ describe "UPDATE record" do
65
+ it "updates the record" do
66
+ c = Company.make!
67
+ put :update, id: c.id, company: { name: "New Name" }
68
+ response.should redirect_to action: :show, id: c.id
69
+ Company.find(c.id).name.should == "New Name"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,61 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe Admin::JobsController, :type => :controller do
4
+ render_views
5
+
6
+ before :each do # Why can't I do this shit in a helper like I do for requests?
7
+ user = double('user')
8
+ allow(request.env['warden']).to receive(:authenticate!) { user }
9
+ allow(controller).to receive(:current_user) { user }
10
+ end
11
+
12
+ describe "GET index" do
13
+ it "finds all Jobs" do
14
+ r = Job.make!
15
+ get :index
16
+ response.status.should == 200
17
+ assigns(:jobs).include?(r).should == true
18
+ end
19
+ end
20
+
21
+ describe "SHOW record" do
22
+ it "finds the record" do
23
+ r = Job.make!
24
+ get :show, id: r.id
25
+ response.status.should == 200
26
+ end
27
+ end
28
+
29
+ describe "NEW record" do
30
+ it "renders the form for a new record" do
31
+ get :new
32
+ response.status.should == 200
33
+ end
34
+ end
35
+
36
+ describe "CREATE record" do
37
+ it "creates the record" do
38
+ r = Job.make
39
+ post :create, job: r.attributes
40
+ response.should redirect_to action: :show, id: Job.last.id
41
+ Job.last.title.should == r.title
42
+ end
43
+ end
44
+
45
+ describe "EDIT record" do
46
+ it "renders the edit form for an existing record" do
47
+ r = Job.make!
48
+ get :edit, id: r.id
49
+ response.status.should == 200
50
+ end
51
+ end
52
+
53
+ describe "UPDATE record" do
54
+ it "updates the record" do
55
+ r = Job.make!
56
+ put :update, id: r.id, job: { title: "New Name" }
57
+ response.should redirect_to action: :show, id: r.id
58
+ Job.find(r.id).title.should == "New Name"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,65 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe Admin::LocationsController, :type => :controller do
4
+ render_views
5
+
6
+ before :each do # Why can't I do this shit in a helper like I do for requests?
7
+ user = double('user')
8
+ allow(request.env['warden']).to receive(:authenticate!) { user }
9
+ allow(controller).to receive(:current_user) { user }
10
+ end
11
+
12
+ describe "GET index" do
13
+ it "finds all locations" do
14
+ c = Location.make!
15
+ get :index
16
+ response.status.should == 200
17
+ assigns(:locations).include?(c).should == true
18
+ end
19
+ end
20
+
21
+ describe "SHOW record" do
22
+ it "finds the record" do
23
+ c = Location.make!
24
+ get :show, id: c.id
25
+ response.status.should == 200
26
+ end
27
+ end
28
+
29
+ describe "NEW record" do
30
+ # will fail until https://github.com/activeadmin/activeadmin/pull/4010 is merged
31
+ it "renders the form for a new record" do
32
+ get :new
33
+ response.status.should == 200
34
+ end
35
+ end
36
+
37
+ describe "CREATE record" do
38
+ it "creates the record" do
39
+ c = Location.make
40
+ gps = LocationGps.make
41
+ post :create, location: c.attributes.merge(gps_attributes: gps.attributes)
42
+ response.should redirect_to action: :show, id: Location.last.id
43
+ Location.last.name.should == c.name
44
+ Location.last.gps.lat.round(10).should == gps.lat.round(10)
45
+ Location.last.gps.lng.round(10).should == gps.lng.round(10)
46
+ end
47
+ end
48
+
49
+ describe "EDIT record" do
50
+ it "renders the edit form for an existing record" do
51
+ r = Location.make!
52
+ get :edit, id: r.id
53
+ response.status.should == 200
54
+ end
55
+ end
56
+
57
+ describe "UPDATE record" do
58
+ it "updates the record" do
59
+ r = Location.make!
60
+ put :update, id: r.id, location: { name: "New Name" }
61
+ response.should redirect_to action: :show, id: r.id
62
+ Location.find(r.id).name.should == "New Name"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,71 @@
1
+ require 'rails_helper'
2
+ RSpec.describe Admin::ProjectsController, :type => :controller do
3
+ render_views
4
+
5
+ before :each do # Why can't I do this shit in a helper like I do for requests?
6
+ user = double('user')
7
+ allow(request.env['warden']).to receive(:authenticate!) { user }
8
+ allow(controller).to receive(:current_user) { user }
9
+ end
10
+
11
+ describe "GET index" do
12
+ it "finds all projects" do
13
+ r = Project.make!
14
+ get :index
15
+ response.status.should == 200
16
+ assigns(:projects).include?(r).should == true
17
+ end
18
+ end
19
+
20
+ describe "SHOW record" do
21
+ it "finds the record" do
22
+ r = Project.make!
23
+ get :show, id: r.id
24
+ response.status.should == 200
25
+ end
26
+ end
27
+
28
+ describe "NEW record" do
29
+ it "renders the form for a new record" do
30
+ get :new
31
+ response.status.should == 200
32
+ end
33
+ end
34
+
35
+ describe "CREATE record" do
36
+ it "creates the record" do
37
+ r = Project.make
38
+ post :create, project: r.attributes
39
+ response.should redirect_to action: :show, id: Project.last.id
40
+ Project.last.name.should == r.name
41
+ end
42
+
43
+ it "the inverse_of declaration allows a new project to be created with a project_job" do
44
+ j = Job.make!
45
+ r = Project.make
46
+ post :create, project: r.attributes.merge({project_jobs_attributes:{'0'=>{job_id: j.id}}})
47
+ p = Project.last
48
+ p.name.should == r.name
49
+ p.project_jobs.size.should == 1
50
+ p.project_jobs.first.job.should == j
51
+ end
52
+
53
+ end
54
+
55
+ describe "EDIT record" do
56
+ it "renders the edit form for an existing record" do
57
+ r = Project.make!
58
+ get :edit, id: r.id
59
+ response.status.should == 200
60
+ end
61
+ end
62
+
63
+ describe "UPDATE record" do
64
+ it "updates the record" do
65
+ r = Project.make!
66
+ put :update, id: r.id, project: { name: "New Name" }
67
+ response.should redirect_to action: :show, id: r.id
68
+ Project.find(r.id).name.should == "New Name"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,4 @@
1
+ class CompanyAdmin < IntrospectiveAdmin::Base
2
+ register Company do
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ class JobAdmin < IntrospectiveAdmin::Base
2
+ register Job do
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ class LocationAdmin < IntrospectiveAdmin::Base
2
+ register Location do
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ class ProjectAdmin < IntrospectiveAdmin::Base
2
+ register Project do
3
+
4
+ end
5
+
6
+ end
@@ -0,0 +1,5 @@
1
+ class RoleAdmin < IntrospectiveAdmin::Base
2
+ register Role do
3
+ end
4
+ end
5
+
@@ -0,0 +1,9 @@
1
+ class UserAdmin < IntrospectiveAdmin::Base
2
+ def self.exclude_params
3
+ %w(reset_password_at current_sign_in_at current_sign_in_ip remember_created_at sign_in_count encrypted_password reset_password_sent_at reset_password_token password authentication_token unlock_token failed_attempts last_sign_in_at locked_at last_sign_in_ip)
4
+ end
5
+
6
+ register User do
7
+ end
8
+
9
+ end
File without changes
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+ end
File without changes
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
File without changes
File without changes
@@ -0,0 +1,13 @@
1
+ class AbstractAdapter < ActiveRecord::Base
2
+ self.abstract_class = true
3
+
4
+ class << self
5
+ def human_attribute_name(attr, options = {})
6
+ # The default formatting of validation errors sucks, this helps a little syntatically:
7
+ super.titleize+":"
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,6 @@
1
+ class AdminUser < ActiveRecord::Base
2
+ # Include default devise modules. Others available are:
3
+ # :confirmable, :lockable, :timeoutable and :omniauthable
4
+ devise :database_authenticatable,
5
+ :recoverable, :rememberable, :trackable, :validatable
6
+ end
@@ -0,0 +1,18 @@
1
+ class Chat < AbstractAdapter
2
+ belongs_to :creator, foreign_key: :creator_id, :class_name => "User", inverse_of: :own_chats
3
+
4
+ has_many :chat_users, dependent: :destroy
5
+ has_many :users, through: :chat_users
6
+ has_many :chat_messages, dependent: :destroy
7
+ has_many :messages, class_name: 'ChatMessage', dependent: :destroy
8
+
9
+ def active_users
10
+ chat_users.includes(:user).select {|cu| cu.departed_at.nil? }.map(&:user)
11
+ end
12
+
13
+ before_create :add_creator_to_conversation
14
+ def add_creator_to_conversation
15
+ users.push creator
16
+ end
17
+
18
+ end
@@ -0,0 +1,34 @@
1
+ class ChatMessage < AbstractAdapter
2
+ belongs_to :chat
3
+ belongs_to :author, class_name: 'User'
4
+
5
+ has_many :chat_users, through: :chat
6
+ has_many :recipients, lambda {|message| where(':created_at >= chat_users.created_at and (chat_users.departed_at IS NULL OR :created_at <= chat_users.departed_at)', created_at: message.created_at ) }, through: :chat_users, source: :user, class_name: 'User'
7
+
8
+ # Create ChatUserMessage records for each recipient to track read status
9
+ has_many :chat_message_users, dependent: :destroy
10
+
11
+ validate :author_in_chat
12
+
13
+ def author_in_chat
14
+ errors[:base] << 'User not in chat session.' unless chat.active_users.include? author
15
+ end
16
+
17
+ before_save :create_message_users, if: :new_record?
18
+ def create_message_users
19
+ chat_users.merge(ChatUser.current).each do |cu|
20
+ chat_message_users.build(user: cu.user)
21
+ end
22
+ end
23
+
24
+ def read_by?(user)
25
+ chat_message_users.merge(ChatMessageUser.read).map(&:user_id).include?(user.id)
26
+ end
27
+
28
+ def self.find_chat_for_users(users)
29
+ # presumably much more efficient ways to run an intersecton, we want to find the last
30
+ # exact match with the users being messaged to append to the existing chat.
31
+ Chat.eager_load(:chat_users).where("chat_users.departed_at IS NULL").order('chats.created_at desc').detect {|c| c.chat_users.map(&:user_id).uniq.sort == users.map(&:id).sort }
32
+ end
33
+
34
+ end
@@ -0,0 +1,17 @@
1
+ class ChatMessageUser < AbstractAdapter
2
+ belongs_to :chat_message
3
+ belongs_to :user
4
+ has_one :chat, through: :chat_message
5
+
6
+ scope :read, ->{ where('read_at IS NOT NULL' ) }
7
+ scope :unread, ->{ where('read_at IS NULL' ) }
8
+
9
+ before_save :author_reads_message
10
+ def author_reads_message
11
+ self.read_at = Time.now if user == chat_message.author
12
+ end
13
+
14
+ def read?
15
+ read_at.present?
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ class ChatUser < AbstractAdapter
2
+ belongs_to :chat
3
+ belongs_to :user
4
+
5
+ alias_attribute :joined_at, :created_at
6
+ alias_attribute :left_at, :departed_at
7
+
8
+ scope :current, ->{ where(departed_at: nil) }
9
+
10
+ validate :user_not_already_active, on: :create
11
+
12
+ def user_not_already_active
13
+ errors[:base] << "#{user.name} is already present in this chat." if chat.chat_users.where(user_id: user.id, departed_at: nil).count > 0 if user.persisted?
14
+ end
15
+
16
+ end
@@ -0,0 +1,12 @@
1
+ class Company < AbstractAdapter
2
+ has_many :roles, as: :ownable
3
+ has_many :admins, through: :roles, source: :user
4
+ accepts_nested_attributes_for :roles, allow_destroy: true
5
+
6
+ has_many :beacons, class_name: 'LocationBeacon', dependent: :destroy
7
+ has_many :locatables
8
+ has_many :locations, through: :locatables, source: :locatable, source_type: 'Company'
9
+
10
+ has_many :projects, foreign_key: :owner_id, dependent: :destroy, inverse_of: :owner
11
+
12
+ end
File without changes
@@ -0,0 +1,10 @@
1
+ class Job < AbstractAdapter
2
+
3
+ has_many :project_jobs, dependent: :destroy
4
+ accepts_nested_attributes_for :project_jobs, allow_destroy: true
5
+
6
+ has_many :user_project_jobs, dependent: :destroy
7
+ has_many :users, through: :user_project_jobs
8
+ has_many :projects, through: :user_project_jobs
9
+
10
+ end
@@ -0,0 +1,6 @@
1
+ class Locatable < AbstractAdapter
2
+ belongs_to :location
3
+ belongs_to :locatable, polymorphic: true
4
+
5
+ validates_inclusion_of :locatable_type, in: %w(Company)
6
+ end
@@ -0,0 +1,26 @@
1
+ class Location < AbstractAdapter
2
+ has_many :locatables, dependent: :destroy
3
+ has_many :companies, through: :locatables, source: :locatable, source_type: 'Company'
4
+
5
+ has_many :beacons, class_name: 'LocationBeacon', dependent: :destroy
6
+ has_one :gps, class_name: 'LocationGps', dependent: :destroy
7
+ delegate :lat,:lng,:alt, to: :gps
8
+
9
+ belongs_to :parent_location, foreign_key: :parent_location_id, class_name: 'Location', inverse_of: :child_locations
10
+ has_many :child_locations, foreign_key: :parent_location_id, class_name: 'Location', dependent: :destroy, inverse_of: :parent_location
11
+
12
+ has_many :user_locations, dependent: :destroy
13
+
14
+ # isn't this list going to be kinda long? are there any reasonable constraints to put
15
+ # on this random bit of metadata?
16
+ validates_inclusion_of :kind, in: %w(airport terminal gate plane)
17
+
18
+ accepts_nested_attributes_for :child_locations, allow_destroy: true
19
+ accepts_nested_attributes_for :gps, allow_destroy: true
20
+ accepts_nested_attributes_for :beacons, allow_destroy: true
21
+
22
+ def coords
23
+ [gps.lat, gps.lng, gps.alt]
24
+ end
25
+
26
+ end
@@ -0,0 +1,16 @@
1
+ class LocationBeacon < AbstractAdapter
2
+ belongs_to :location
3
+ has_many :gps, through: :location
4
+ belongs_to :company
5
+
6
+ # B9407F30-F5F8-466E-AFF9-25556B57FE6D
7
+ validates_format_of :uuid, with: /[0-9a-fA-F]{32}/ # 32 digit hexadecimal UUID
8
+ validates_format_of :mac_address, with: /[0-9a-fA-F]{12}/ # 16 digit hexadecimal bluetooth MAC address
9
+
10
+ before_validation :massage_ids
11
+ def massage_ids
12
+ self.uuid = (uuid||'').gsub(/[^0-9a-fA-F]+/,'').upcase
13
+ self.mac_address = (mac_address||'').gsub(/[^0-9a-fA-F]+/,'').upcase
14
+ end
15
+
16
+ end
@@ -0,0 +1,64 @@
1
+ class LocationGps < AbstractAdapter
2
+ belongs_to :location
3
+ has_many :beacons, through: :location
4
+
5
+ # lat and lng in degrees altitude in meters
6
+ validates_numericality_of :lat, greater_than_or_equal_to: -90.0, less_than_or_equal_to: 90.0
7
+ validates_numericality_of :lng, greater_than_or_equal_to: -180.0, less_than_or_equal_to: 180.0
8
+ validates_numericality_of :alt
9
+
10
+ def distance_from(lat,lng) # calc distance between this location and the passed coords
11
+ Haversine.distance(self.lat,self.lng, lat,lng).to_meters
12
+ end
13
+
14
+ class << self
15
+ def bounding_box(lat,lng,alt)
16
+ # box constrain the nearest neighbor search to something reasonble
17
+ box = 0.1447 # 10 miles radius: 111132 meters/deg, 1609m a mile, 0.01447 deg to a mile
18
+ alt_box = 8 # we're trying to constrain the box to a floor of a building here...
19
+ ["lat BETWEEN :lat-#{box} AND :lat+#{box} AND
20
+ lng BETWEEN :lng-#{box} AND :lng+#{box} AND
21
+ alt BETWEEN :alt-#{alt_box} AND :alt+#{alt_box}",
22
+ { lat: lat, lng: lng, alt: alt } ]
23
+ end
24
+
25
+ def nearest_beacon(lat, lng, alt=0, companies)
26
+ company_ids = companies.kind_of?(Array) ? companies : [companies]
27
+
28
+ gps = nearest_approximation(lat,lng,alt).joins(:beacons).where("location_beacons.company_id"=>company_ids).first || ( raise ActiveRecord::RecordNotFound.new("Couldn't find a Beacon for that GPS Location.") )
29
+ # It's not clear, if they're doing this across multiple companies, how which beacon
30
+ # comes back is not arbitrary, so maybe we should require it be specific.
31
+ gps.beacons.where(company_id: company_ids).first
32
+ end
33
+
34
+ def nearest(lat,lng,alt=0)
35
+ nearest_approximation(lat,lng,alt).first || ( raise ActiveRecord::RecordNotFound.new("Couldn't find GPS Location.") )
36
+ end
37
+
38
+ def nearest_approximation(lat,lng,alt=0) # approximate as a flat-ish plane
39
+ dOrder= sanitize_sql_array(["(lat - ?)^2 + (lng - ?)^2 * COS(RADIANS(lat))", lat, lng])
40
+
41
+ self.select("location_gps.id, location_gps.location_id, #{dOrder} as dOrder").
42
+ where( bounding_box(lat,lng,alt) ).order("dOrder ASC")
43
+ end
44
+
45
+ def nearest_great_circle(lat,lng,alt=0)
46
+ dOrder = sanitize_sql_array(["acos( sin(radians(lat))*sin(radians(:lat)) + cos(radians(lat))*cos(radians(:lat))*cos(radians(lng - :lng)) )", { lat: lat, lng: lng } ] )
47
+
48
+ self.select("location_gps.id, location_gps.location_id, #{dOrder} as dOrder").
49
+ where( bounding_box(lat,lng,alt) ).order("dOrder ASC")
50
+ end
51
+
52
+ def nearest_haversine(lat,lng,alt=0)
53
+ dOrder = sanitize_sql_array(["asin( sqrt( (sin(radians(:lat - lat))/2)^2 + cos(radians(lat))*cos(radians(:lat))*(sin(radians(:lng - lng )/2))^2 ))", { lat: lat, lng: lng }])
54
+
55
+ self.select("location_gps.id, location_gps.location_id, #{dOrder} as dOrder").
56
+ where( bounding_box(lat,lng,alt) ).order("dOrder ASC")
57
+ end
58
+
59
+ #def nearest_wgs84(lat,lng,alt=0)
60
+ # # vincenty's ellipsoid calculation is an iterative estimate available in postGIS
61
+ #end
62
+ end
63
+
64
+ end