milia 0.3.38 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (172) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +94 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -16
  6. data/README.md +890 -141
  7. data/Rakefile +1 -55
  8. data/app/controllers/confirmations_controller.rb +101 -0
  9. data/app/controllers/passwords_controller.rb +8 -0
  10. data/app/controllers/registrations_controller.rb +95 -25
  11. data/app/controllers/sessions_controller.rb +13 -0
  12. data/app/views/members/new.html.haml +33 -0
  13. data/doc/gemfile_addition.txt +28 -0
  14. data/doc/manual_sample.sh +816 -0
  15. data/doc/sample.sh +229 -0
  16. data/lib/generators/milia/install_generator.rb +546 -0
  17. data/lib/generators/milia/temp_generator.rb +93 -0
  18. data/lib/generators/milia/templates/initializer.rb +51 -0
  19. data/lib/milia.rb +89 -1
  20. data/lib/milia/base.rb +29 -4
  21. data/lib/milia/control.rb +161 -9
  22. data/lib/milia/invite_member.rb +92 -0
  23. data/lib/milia/password_generator.rb +171 -0
  24. data/lib/milia/railtie.rb +4 -1
  25. data/lib/milia/version.rb +3 -0
  26. data/milia.gemspec +24 -159
  27. data/test/.ruby-gemset +1 -0
  28. data/test/.ruby-version +1 -0
  29. data/test/Gemfile +81 -0
  30. data/test/Gemfile.lock +200 -0
  31. data/test/README.md +83 -0
  32. data/test/{rails_app/Rakefile → Rakefile} +1 -2
  33. data/test/app/assets/javascripts/application.js +16 -0
  34. data/test/app/assets/stylesheets/application.css +13 -0
  35. data/test/app/controllers/application_controller.rb +13 -0
  36. data/test/app/controllers/home_controller.rb +10 -0
  37. data/test/{rails_app/app → app}/helpers/application_helper.rb +0 -0
  38. data/test/app/models/member.rb +34 -0
  39. data/test/app/models/post.rb +14 -0
  40. data/test/{rails_app/app → app}/models/team.rb +3 -2
  41. data/test/{rails_app/app → app}/models/team_asset.rb +1 -1
  42. data/test/app/models/tenant.rb +54 -0
  43. data/test/app/models/user.rb +14 -0
  44. data/test/app/models/zine.rb +8 -0
  45. data/test/{rails_app/app → app}/views/home/index.html.erb +0 -0
  46. data/test/app/views/home/show.html.erb +2 -0
  47. data/test/app/views/layouts/application.html.erb +14 -0
  48. data/test/bin/bundle +3 -0
  49. data/test/bin/rails +4 -0
  50. data/test/bin/rake +4 -0
  51. data/test/config/application.rb +36 -0
  52. data/test/{rails_app/config → config}/boot.rb +0 -2
  53. data/test/config/database.yml +25 -0
  54. data/test/config/environment.rb +5 -0
  55. data/test/config/environments/development.rb +48 -0
  56. data/test/config/environments/production.rb +95 -0
  57. data/test/config/environments/test.rb +42 -0
  58. data/test/{rails_app/config → config}/initializers/backtrace_silencers.rb +0 -0
  59. data/test/{rails_app/config → config}/initializers/devise.rb +84 -36
  60. data/test/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/test/config/initializers/inflections.rb +16 -0
  62. data/test/config/initializers/milia.rb +51 -0
  63. data/test/{rails_app/config → config}/initializers/mime_types.rb +0 -0
  64. data/test/config/initializers/secret_token.rb +12 -0
  65. data/test/config/initializers/session_store.rb +3 -0
  66. data/test/{rails_app/config → config}/initializers/wrap_parameters.rb +6 -6
  67. data/test/config/locales/en.yml +23 -0
  68. data/test/config/routes.rb +77 -0
  69. data/test/{rails_app/db/migrate/20111012060818_add_sessions_table.rb → db/migrate/20111012050200_add_sessions_table.rb} +2 -6
  70. data/test/db/migrate/20111012050340_devise_create_users.rb +48 -0
  71. data/test/{rails_app/db → db}/migrate/20111012050532_create_tenants.rb +3 -1
  72. data/test/db/migrate/20111012050600_create_tenants_users_join_table.rb +8 -0
  73. data/test/db/migrate/20111012050650_create_members.rb +12 -0
  74. data/test/db/migrate/20111012231923_create_posts.rb +12 -0
  75. data/test/{rails_app/db → db}/migrate/20111013050657_create_zines.rb +2 -4
  76. data/test/{rails_app/db → db}/migrate/20111013050753_create_teams.rb +1 -2
  77. data/test/db/migrate/20111013050837_create_team_assets.rb +11 -0
  78. data/test/db/schema.rb +126 -0
  79. data/test/{rails_app/db → db}/seeds.rb +0 -0
  80. data/test/test/controllers/home_controller_test.rb +133 -0
  81. data/test/{rails_app/test → test}/ctlr_test_helper.rb +0 -0
  82. data/test/test/fixtures/members.yml +35 -0
  83. data/test/test/fixtures/posts.yml +96 -0
  84. data/test/test/fixtures/team_assets.yml +30 -0
  85. data/test/test/fixtures/teams.yml +17 -0
  86. data/test/test/fixtures/tenants.yml +12 -0
  87. data/test/test/fixtures/tenants_users.yml +15 -0
  88. data/test/test/fixtures/users.yml +33 -0
  89. data/test/test/fixtures/zines.yml +25 -0
  90. data/test/test/models/member_test.rb +75 -0
  91. data/test/test/models/post_test.rb +66 -0
  92. data/test/test/models/team_test.rb +49 -0
  93. data/test/test/models/tenant_test.rb +228 -0
  94. data/test/test/models/user_test.rb +182 -0
  95. data/test/test/models/zine_test.rb +40 -0
  96. data/test/test/test_helper.rb +31 -0
  97. metadata +199 -154
  98. data/.rvmrc +0 -1
  99. data/Gemfile.lock +0 -115
  100. data/VERSION +0 -1
  101. data/test/helper.rb +0 -18
  102. data/test/rails_app/.gitignore +0 -5
  103. data/test/rails_app/Gemfile +0 -48
  104. data/test/rails_app/Gemfile.lock +0 -168
  105. data/test/rails_app/Gemfile.lock.backup +0 -167
  106. data/test/rails_app/Procfile +0 -1
  107. data/test/rails_app/README +0 -261
  108. data/test/rails_app/app/assets/images/rails.png +0 -0
  109. data/test/rails_app/app/assets/javascripts/application.js +0 -9
  110. data/test/rails_app/app/assets/javascripts/home.js.coffee +0 -3
  111. data/test/rails_app/app/assets/stylesheets/application.css +0 -7
  112. data/test/rails_app/app/assets/stylesheets/home.css.scss +0 -3
  113. data/test/rails_app/app/controllers/application_controller.rb +0 -50
  114. data/test/rails_app/app/controllers/home_controller.rb +0 -7
  115. data/test/rails_app/app/helpers/home_helper.rb +0 -2
  116. data/test/rails_app/app/mailers/.gitkeep +0 -0
  117. data/test/rails_app/app/models/.gitkeep +0 -0
  118. data/test/rails_app/app/models/author.rb +0 -9
  119. data/test/rails_app/app/models/calendar.rb +0 -6
  120. data/test/rails_app/app/models/post.rb +0 -15
  121. data/test/rails_app/app/models/tenant.rb +0 -8
  122. data/test/rails_app/app/models/user.rb +0 -14
  123. data/test/rails_app/app/models/zine.rb +0 -6
  124. data/test/rails_app/app/views/layouts/application.html.erb +0 -12
  125. data/test/rails_app/config.ru +0 -4
  126. data/test/rails_app/config/application.rb +0 -56
  127. data/test/rails_app/config/database.yml +0 -55
  128. data/test/rails_app/config/environment.rb +0 -5
  129. data/test/rails_app/config/environments/development.rb +0 -41
  130. data/test/rails_app/config/environments/production.rb +0 -60
  131. data/test/rails_app/config/environments/test.rb +0 -56
  132. data/test/rails_app/config/initializers/inflections.rb +0 -10
  133. data/test/rails_app/config/initializers/secret_token.rb +0 -7
  134. data/test/rails_app/config/initializers/session_store.rb +0 -8
  135. data/test/rails_app/config/locales/devise.en.yml +0 -58
  136. data/test/rails_app/config/locales/en.yml +0 -5
  137. data/test/rails_app/config/routes.rb +0 -63
  138. data/test/rails_app/db/migrate/20111012050340_devise_create_users.rb +0 -39
  139. data/test/rails_app/db/migrate/20111012050600_create_tenants_users.rb +0 -10
  140. data/test/rails_app/db/migrate/20111012231923_create_posts.rb +0 -15
  141. data/test/rails_app/db/migrate/20111013050558_create_calendars.rb +0 -14
  142. data/test/rails_app/db/migrate/20111013050837_create_team_assets.rb +0 -14
  143. data/test/rails_app/db/migrate/20111013053403_create_authors.rb +0 -13
  144. data/test/rails_app/db/schema.rb +0 -133
  145. data/test/rails_app/lib/assets/.gitkeep +0 -0
  146. data/test/rails_app/lib/tasks/.gitkeep +0 -0
  147. data/test/rails_app/log/.gitkeep +0 -0
  148. data/test/rails_app/public/404.html +0 -26
  149. data/test/rails_app/public/422.html +0 -26
  150. data/test/rails_app/public/500.html +0 -26
  151. data/test/rails_app/public/favicon.ico +0 -0
  152. data/test/rails_app/script/rails +0 -6
  153. data/test/rails_app/test/factories/units_factory.rb +0 -84
  154. data/test/rails_app/test/fixtures/.gitkeep +0 -0
  155. data/test/rails_app/test/functional/.gitkeep +0 -0
  156. data/test/rails_app/test/functional/home_controller_test.rb +0 -10
  157. data/test/rails_app/test/integration/.gitkeep +0 -0
  158. data/test/rails_app/test/performance/browsing_test.rb +0 -12
  159. data/test/rails_app/test/test_helper.rb +0 -119
  160. data/test/rails_app/test/unit/.gitkeep +0 -0
  161. data/test/rails_app/test/unit/author_test.rb +0 -30
  162. data/test/rails_app/test/unit/calendar_test.rb +0 -28
  163. data/test/rails_app/test/unit/helpers/home_helper_test.rb +0 -7
  164. data/test/rails_app/test/unit/post_test.rb +0 -81
  165. data/test/rails_app/test/unit/team_test.rb +0 -30
  166. data/test/rails_app/test/unit/tenant_test.rb +0 -28
  167. data/test/rails_app/test/unit/user_test.rb +0 -26
  168. data/test/rails_app/test/unit/zine_test.rb +0 -28
  169. data/test/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
  170. data/test/rails_app/vendor/plugins/.gitkeep +0 -0
  171. data/test/rails_app/vendor/plugins/rails_log_stdout/init.rb +0 -43
  172. data/test/test_milia.rb +0 -7
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bbee764da49ec653dec3714f567a518a160d5e42
4
+ data.tar.gz: e4835d8ffd9cf753e2cff72fa09798f1af1011e7
5
+ SHA512:
6
+ metadata.gz: b324efa804d1bd08fd7e49818019fd7bbe8fd924106b22cee933073a8346a7e35a6e5f0d48809db9d393708a13c923708f68aaffc23ba79ce853e70bbfe55bdc
7
+ data.tar.gz: 04cffd9a266f822467ee296a72a7cf319eecbeb5e61e19cc3d2f1d257c45e313fb50fdea4494461c24f6a48f061eaf11032c54e271e1bd739a2b4e5585a0f11d
@@ -0,0 +1,94 @@
1
+ #####################################################################################
2
+ # Rails
3
+ .bundle
4
+ test/db/*.sqlite3
5
+ test/db/*.sqlite3-journal
6
+ log/
7
+ tmp/
8
+ *.log
9
+ tmp/**/*
10
+ .tmp*
11
+
12
+ # Documentation
13
+ doc/api
14
+ doc/app
15
+ .yardoc
16
+ .yardopts
17
+ rdoc/
18
+
19
+ # Public Uploads
20
+ public/system/*
21
+ public/themes/*
22
+
23
+ # Public Cache
24
+ public/javascripts/cache
25
+ public/stylesheets/cache
26
+
27
+ # Vendor Cache
28
+ vendor/cache
29
+
30
+ # Acts as Indexed
31
+ index/**/*
32
+
33
+ # Refinery Specific
34
+ *.tmproj
35
+ *.autobackupbyrefinery.*
36
+ /refinerycms-*.gem
37
+ .autotest
38
+
39
+ # Mac
40
+ .DS_Store
41
+
42
+ # Windows
43
+ Thumbs.db
44
+
45
+ # NetBeans
46
+ nbproject
47
+
48
+ # Eclipse
49
+ .project
50
+
51
+ # Redcar
52
+ .redcar
53
+
54
+ # Rubinius
55
+ *.rbc
56
+
57
+ # Vim
58
+ *.swp
59
+ *.swo
60
+ *~
61
+
62
+ # RubyMine
63
+ .idea
64
+
65
+ # Backup
66
+ *~
67
+
68
+ # Capybara Bug
69
+ capybara-*html
70
+
71
+ # sass
72
+ .sass-cache
73
+ .sass-cache/*
74
+
75
+ #rvm
76
+ .rvmrc
77
+ .rvmrc.*
78
+
79
+ #gem
80
+ pkg/
81
+ *.gem
82
+
83
+ # other
84
+ .loadpath
85
+ all.js
86
+ all.css
87
+ temp*.txt
88
+ config/database.yml
89
+ *.dump
90
+ .bashrc
91
+
92
+ #git
93
+ *.keep
94
+ *.gitkeep
@@ -0,0 +1 @@
1
+ milia2
@@ -0,0 +1 @@
1
+ ruby-2.0.0
data/Gemfile CHANGED
@@ -1,17 +1,4 @@
1
- source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
1
+ source 'https://rubygems.org'
5
2
 
6
- gem 'rails', '3.2.13'
7
- gem 'devise', "2.1.2"
8
-
9
- # Add dependencies to develop your gem here.
10
- # Include everything needed to run rake, tests, features, etc.
11
- group :development, :test do
12
- gem 'pg'
13
- gem "shoulda", "3.5.0"
14
- gem "jeweler", "~> 1.6.4"
15
- gem 'rdoc'
16
- gem 'turn', :require => false
17
- end
3
+ # Specify your gem's dependencies in milia.gemspec
4
+ gemspec
data/README.md CHANGED
@@ -1,15 +1,19 @@
1
1
  # milia
2
2
 
3
- Milia is a multi-tenanting gem for hosted Rails 3.1 applications which uses
4
- the devise gem for user authentication.
3
+ Milia is a multi-tenanting gem for hosted Rails 4.0.x applications which use
4
+ the devise gem for user authentication and registrations. Milia comes with
5
+ tailoring for common use cases needing multi-tenanting with user authentication.
5
6
 
6
- ## Basic concepts
7
+ ## Basic concepts for the milia multi-tenanting gem
8
+
9
+ ### multi-tenanting highlights
7
10
 
8
11
  * should be transparent to the main application code
9
12
  * should be symbiotic with user authentication
10
13
  * should raise exceptions upon attempted illegal access
11
14
  * should force tenanting (not allow sloppy access to all tenant records)
12
- * should allow application flexibility upon new tenant sign-up, usage of eula information, etc
15
+ * should allow application flexibility upon new tenant sign-up,
16
+ usage of eula information, etc
13
17
  * should be as non-invasive (as possible) to Rails code
14
18
  * row-based tenanting is used
15
19
  * default_scope is used to enforce tenanting
@@ -21,6 +25,8 @@ performance hit, was seriously time-consuming to backup and restore, was invasiv
21
25
  into the Rails code structure (monkey patching), was complex to implement, and
22
26
  couldn't use Rails migration tools as-is.
23
27
 
28
+ ### tenants/users vs organizations/members
29
+
24
30
  A tenant == an organization; users == members of the organization.
25
31
  Only organizations sign up for new tenants, not members (users).
26
32
  The very first user of an organization, let's call him the Organizer,
@@ -29,174 +35,518 @@ The Organizer becomes the first member (user) of the organization (tenant).
29
35
  Thereafter, other members only obtain entry to the organization (tenant)
30
36
  by invitation. New tenants are not created for every new user.
31
37
 
38
+ ## Version
39
+
40
+ milia v1.0.0 is the release version for Rails 4.0.x and is now available for usage.
41
+
42
+ The last previous release version for Rails 3.2.x can be found in the git branch 'v0.3', but
43
+ it is essentially obsolete. Go with v1.0.x
44
+
45
+ ## What's changed?
46
+
47
+ * Rails 4.0.x adapted (changes to terms, strong_parameters, default_scope, etc)
48
+ * Devise 3.2.x adapted
49
+ * All the changes which version 0.3.x advised to be inserted in applications_controller.rb are now automatically loaded into ActionController by milia.
50
+ * that includes authenticate_tenant!
51
+ * so if you've been using an older version of milia, you'll need to remove that stuff from applications_controller!
52
+ * generators for easy install of basic rails/milia/devise
53
+ * callback after successful authenticate_tenant!
54
+ * debug & info logging and trace for troubleshooting
55
+ * improved invite_member support
56
+ * revised README instructions
57
+
58
+ ## Sample app and documentation
59
+
60
+ There were numerous requests for me to provide a complete sample web application
61
+ which uses milia and devise. I have done this. This README will have a brief section
62
+ on creating and installing the sample application. Further details about this process
63
+ can be found via the sources listed below:
64
+
65
+ * see doc/sample.sh for easy generator usage for setting up and creating a working app.
66
+ Although all the same information is here in this README, it's perhaps clearer, and
67
+ presented better, as a step-by-step instruction manual. This README must perforce be
68
+ more as a reference manual.
69
+ * the sample app uses web-theme-app to provide some pleasantly formatted views for your testing pleasure.
70
+ * see doc/manual_sample.sh for complete step-by-step instructions for manually setting up and creating a working app.
71
+ * if you want to know exactly everything the generators are doing, see the manual_sample.sh
72
+ - instructions are very detailed and loaded with comments (600 lines!).
73
+ - Stage one: with simple devise and no milia,
74
+ - Stage two: installing milia for complete tenanting,
75
+ - Stage three: adding in invite_member capability
76
+ * the entire sample is also fully available on github, if you wish to check your work. diff can be your friend.
77
+ this sample on github, however, will always be for the latest release or latest beta (whichever is most recent).
78
+ * find it at: https://github.com/dsaronin/sample-milia-app
79
+
80
+ ### Available docmentation resources for milia
81
+
82
+ * doc/sample.sh -- this document will ALWAYS be the most recent
83
+ (for example in the edge branch: "newdev")
84
+ * doc/manual_sample.sh -- non-generator-based instructions for manually editing files.
85
+ (this may no longer be the most recent since further work will focus on the generators)
86
+ * doc/gemfile_addition.txt -- the additions to Gemfile needed for setting up the sample-milia-app
87
+
88
+ * github.com/milia/wiki/sample-milia-app-tutorial
89
+ this should be the same as the manual_sample.sh doc for the current
90
+ stable release (or last beta version); but markdown formatted
91
+ https://github.com/dsaronin/milia/wiki/sample-milia-app-tutorial
92
+ * milia README (this document):
93
+ - this will be the knowledgable programmer's digest of the essentials
94
+ - and thus it won't cover some of the intricacies of actually
95
+ implementing milia: either the tutorial or sample.sh will do that
96
+ - if you're a first time milia implementer, please use both the
97
+ README and either of the two above documents for assistance: it will save you time.
98
+
99
+ ## converting an existing app to multi-tenanted
100
+
101
+ It is doable, but you'll need to first understand how milia basically is installed. I'd still recommend
102
+ bringing up the sample-milia-app, getting it working, and then figuring out how to either graft it onto your app.
103
+ Or (recommended), grafting your app onto it. I prefer to work that way because it's based off of a pure Rails 4.0
104
+ and devise 3.2 install.
105
+
106
+ ## Dependency requirements
107
+
108
+ * Rails 4.0.x
109
+ * Devise 3.2.x
110
+
111
+ ## this readme is for v1.0.0 (fka v1.0.0-beta-7)
112
+ * changes in beta-7: model & controller testing is almost complete;
113
+ minor bug fixed; mixed-in controller methods are now public, not
114
+ private.
115
+
116
+ * changes in beta-6: user_params added to Tenant.create_new_tenant;
117
+ ability to add additional whitelist parameters during config
118
+
119
+ * changes in beta-5: logging, callback, bug fixes
120
+
121
+ * changes in beta-4:
122
+ corrections to README for Gemfile requirements
123
+ generator tests for requirements
124
+
125
+ * changes in beta-3: improved generators getting a new app started
126
+
127
+ * changes in beta-2: invite_member capability
128
+
129
+ ### edge branch: "newdev"
130
+
131
+ If I'm actively developing, this can be in a state of flux. Use at your own risk.
132
+
133
+ ## Authorized Roles
134
+
135
+ Milia doesn't have any requirements re roles for users. But you will probably need
136
+ something in your app to support different roles levels. Devise recommends cancan, but
137
+ I have not used it and do not know how it might affect milia. In my app, I used to use
138
+ ACL9 before it encountered version issues with Rails. Rather than debugging it, I spun
139
+ off my own simplified version which I use now with great success. The gem I wrote is
140
+ open sourced. It is called _kibali_ and is available at github: https://github.com/dsaronin/kibali.
141
+ Kibali is a simple replacement for ACL9, a role-based authentication gem.
142
+ I prefer the non-obstrusive nature of kibali and the clear-cut way it deliniates
143
+ roles for actions at the start of each controller. This simplicity was also in ACL9.
144
+ Kibali is primarily oriented for functioning as a before_action role authentication scheme for Rails controllers.
145
+
32
146
  ## Structure
33
147
 
34
148
  * necessary models: user, tenant
35
149
  * necessary migrations: user, tenant, tenants_users (join table)
36
150
 
37
- ## Dependency requirements
151
+ You must understand which of your apps models will be tenanted ( <i>acts_as_tenant</i> )
152
+ and which will be universal ( <i>cts_as_universal</i>). Universal data NEVER has critical user/company
153
+ information in the table. It is usually only for system-wide constants. For example, if you've put
154
+ too much user information in the users table, you'll need to seperate it out. by definition, the devise
155
+ user table MUST be universal and should only contain email, encrypted password, and devise-required data.
156
+ ALL OTHER USER DATA (name, phone, address, etc) should be broken out into a tenanted table (say called member_data)
157
+ which belongs_to :user, and in the User model, has_one :member_data. Ditto for organization (account or company)
158
+ information.
159
+
160
+ Most of your tables (except for pure join tables, users, and tenants) SHOULD BE tenanted. You should rarely have
161
+ universal tables, even for things you consider to be system settings. At some time in the future, your accounts
162
+ (organizations) will want to tailor/customize this data. So might as well start off correctly by making the
163
+ table tenanted. It costs you nothing to do so now at the beginning. It does mean that you will need to seed
164
+ these tables whenever a new tenant (organizational account) is created.
165
+
166
+ Finally:
167
+
168
+ * tenants = organizational accounts and are created via sign up, a one-time event. this also creates the
169
+ first MEMBER of that account in your app who is usually the organizing admin. This person can then issue
170
+ invitations (below) to bring other members into the account on the app.
171
+ * members = members WITHIN a tenant and are created by invitation only; they do NOT sign up. An invitation is
172
+ sent to them, they click on an activate or confirm link, and then they become a member of a tenanted group.
173
+ * The invitation process involves creating both a new user (within the current_tenant) and its corresponding
174
+ member_data records.
175
+ * ALL models (whether tenanted or universal) are expected to have a field in the table labelled: tenant_id.
176
+ * YOUR CODE SHOULD NEVER EVER TRY TO CHANGE OR SET THE tenant_id OF A RECORD. milia will not allow it, milia
177
+ will check for deviance; milia will raise exceptions if it's wrong; and milia will override it to maintain integrity.
178
+ * Tenanted records will have tenant_id set to the appropriate tenant automagically by milia.
179
+ * Universal records will have tenant_id always set to nil, automagically by milia; and references to any
180
+ universal table will ALWAYS expect this field to be nil.
181
+ * Pure join tables (has_and_belongs_to_many HABTM associations) get neither designation (tenant nor universal).
182
+ The way that rails accesses these ensures that it will validate the tenant of joined member. A pure HABTM join
183
+ table is created with generation such as follows:
38
184
 
39
- * Rails 3.1 or higher
40
- * Devise 1.4.8 or higher
185
+ ```
186
+ rails g migration CreateModel1sModel2sJoinTable model1s model2s
187
+ ```
41
188
 
42
189
  ## Installation
43
190
 
44
- Either install the gem manually:
191
+ This README describes two different ways to install:
192
+ * use of a milia generator to install itself and automate those
193
+ tweaks for most use cases (recommended method)
194
+ * a bare minimum manual setup which requires many minor tweaks;
195
+ those tweaks will be described in a later section.
196
+
197
+ Later sections of the README will enumerate:
198
+ * how to create a simple working sample rails/devise/milia application
199
+ * all the expected tweaks which the generator performed automatically
200
+ * advice on advanced usage of milia (from rake tasks, console)
201
+ * specifics about the milia API
202
+
203
+
204
+ ## Creating and Installing a Rails/Milia/Devise Sample Application
205
+
206
+ ### Getting started for the sample application
207
+
208
+ Multi-tenanting, much like user authentication, exists within the large scheme of
209
+ an application. I recommend first creating and installing the following complete, but simple,
210
+ reference Rails application so that you can validate a working setup on your
211
+ system, and have a reference point for making changes to your own application.
212
+
213
+ ### Environment setup
214
+
215
+ I put these in .bashrc for an Ubuntu system.
45
216
 
46
217
  ```
47
- $ gem install milia
218
+ export PORT=3000
219
+ export RACK_ENV=development
220
+ export SMTP_ENTRY=<my smtp password>
221
+ # OPTIONAL: recaptcha keys
222
+ export RECAPTCHA_PUBLIC_KEY=6LeYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKpT
223
+ export RECAPTCHA_PRIVATE_KEY=6LeBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBgQBv
48
224
  ```
49
225
 
50
- Or in the Gemfile:
226
+ ### Rails setup
227
+
228
+ This example shows getting starting a new rails project in an rvm environment and using
229
+ git (github) as the SCM, and expecting to use heroku (eventually) as the PaaS
230
+ (Platform as a Service) provider. Note: foreman is available when you set up the
231
+ heroku toolbelt (see doc/sample.sh for more details).
232
+
233
+ This example will set everything up for milia AND devise (you won't need to do devise).
234
+ It assumes you won't use airbrake, you will use recaptcha for new account sign-ups,
235
+ and you will use invite_member capability. Skeleton user, tenant, and member models
236
+ will be created. Before you run db:migrate, you can add any additional fields to the
237
+ tenant and member models. User model really is primarily only used by devise, and so
238
+ you shouldn't add anything to this model as it's a universal model. Member is a tenanted
239
+ model and so this is where all the information for a member should be kept.
51
240
 
52
241
  ```ruby
53
- gem 'milia'
242
+ $ cd projectspace # if not there already
243
+
244
+ $ rails new sample-milia-app --skip-bundle
245
+ $ echo "sample-milia-app" > sample-milia-app/.ruby-gemset
246
+ $ echo "2.0.0" > sample-milia-app/.ruby-version
247
+ $ echo "web: bundle exec thin start -R config.ru -p \$PORT -e \$RACK_ENV" > sample-milia-app/Procfile
248
+ $ rvm gemset create sample-milia-app
54
249
  ```
55
-
56
- ## Getting started
57
250
 
58
- ### Rails setup
251
+ Change .gitignore to match your development environment.
252
+ I just copy my standard .gitignore from another project
253
+ but you can copy mine from sample-milia-app on github.
59
254
 
60
- Milia expects a user session, so please set one up
255
+ ```
256
+ $ cd sample-milia-app
257
+ $ cp ../<an existing project>/.gitignore .
258
+
259
+ $ git init
260
+ $ git add --all .
261
+ $ git commit -am 'initial commit'
262
+ $ git remote add origin git@github.com:<git-user>/sample-milia-app.git
263
+ $ git push -u origin master
264
+ ```
265
+
266
+ ### milia and devise setup
267
+
268
+ This sample web application depends on my updates to the web-app-theme, as
269
+ well as several other gems for the application, which need to be added
270
+ to the Gemfile, before running the installer.
271
+ You can directly import these into your Gemfile
272
+ by getting them from <i>doc/gemfile_addition.txt.</i>
273
+ After adding this addition to the Gemfile, please make sure the correct
274
+ milia branch is being designated (it sometimes points to edge branch).
61
275
 
62
276
  ```
63
- $ rails g session_migration
64
- invoke active_record
65
- create db/migrate/20111012060818_add_sessions_table.rb
277
+ $ vim Gemfile
278
+ G
279
+ :r <path to milia gem>/doc/gemfile_addition.txt
280
+ ZZ
281
+ $ bundle install
66
282
  ```
67
-
283
+
284
+ ### WARNING: don't go commando and try to change everything at once! Don't be a perfectionist and try to bring up a fully written app at once!
285
+
286
+ Just follow the instructions for creating the sample, exactly, step-by-step.
287
+ Get the basics working. Then change, adapt, and spice to taste.
288
+ Please?! Because I'm more inclined to help you solve problems if you've started out by
289
+ getting the sample working exactly as described! If you've tried to go off into the jungle on your own, you are, well, on
290
+ your own. And as they say, _"get out the way you got in!"_
291
+
292
+
293
+ #### complete generating the sample application
294
+
295
+ Running the generator below will completely install milia, devise, and a sample app. You will not
296
+ need to run the "milia:install" given above. You'll need to do the following:
297
+
298
+ ```
299
+ $ rails g milia:install --org_email='<your smtp email for dev work>'
300
+ $ rails g web_app_theme:milia
301
+ ```
302
+
303
+ NOTE: The above generator has an option to specify an email address to
304
+ be used for sending emails for confirmation and account activation.
305
+
306
+ The generator set up basic information for
307
+ being able to send the confirmation & activation emails.
308
+ But, you might need to complete entering in your email and smtp
309
+ information in the following places:
310
+
311
+ * _config/environments/development.rb_
312
+ * _config/environments/production.rb_
313
+
314
+ #### create the database and migration
315
+ ```
316
+ $ rake db:create
317
+ $ rake db:migrate
318
+ ```
319
+
320
+ #### test by starting server:
321
+ ```
322
+ $ foreman start
323
+ ```
324
+
325
+ #### open your browser to http://localhost:3000
326
+
327
+ And that's all you have to do!
328
+
329
+ ## Milia Basic Installation (not needed if you used the generator above)
330
+
331
+ ### Getting started for the Bare minimum setup
332
+
333
+ This is the mininum necessary for using milia with a Rails application. If you're new to Rails
334
+ (or Devise and Milia), then I'd recommend you skip this section and instead follow the instructions
335
+ (previous section above) for Creating and Installing a Rails/Milia/Devise Sample Application.
336
+
337
+ <strong>In any case, do NOT do both installations.</strong>
338
+
339
+ If you'll be using the recaptcha option, then milia will generate expecting the following
340
+ environment variables (put them in .bashrc, with the correct keys):
341
+
342
+ ```
343
+ export RECAPTCHA_PUBLIC_KEY=6LeYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKpT
344
+ export RECAPTCHA_PRIVATE_KEY=6LeBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBgQBv
345
+ ```
346
+
347
+ ### Steps for Bare minimum
348
+ Add to your Gemfile:
349
+
350
+ ```ruby
351
+ gem 'devise', '~>3.2'
352
+ gem 'milia', '~>1.0'
353
+ ```
354
+
355
+ If you'll be working with any beta or leading edge version, specify as follows:
356
+
357
+ ```
358
+ gem 'milia', :git => 'git://github.com/dsaronin/milia.git', :branch => 'v1.0.0-beta-7'
359
+ ```
360
+
361
+ Then,
362
+ ```
363
+ $ bundle install
364
+ $ rails g milia:install --org_email='<your smtp email for dev work>'
365
+ ```
366
+
367
+ Note: The milia generator has an option to specify an email address to be used for sending emails for
368
+ confirmation and account activation. Also note that the milia generator runs two
369
+ devise generators.
370
+
371
+ Make any changes required to the generated migrations, then:
372
+ ```
373
+ $ rake db:create
374
+ $ rake db:migrate
375
+ ```
376
+
377
+ ---------------------------------------------------------------------
378
+
379
+ ## Installation Reference Manual
380
+
381
+ This information is for reference only. The two generators automatically perform
382
+ these changes when installing the sample application. Do NOT repeat these steps
383
+ if you followed the automatic installation of the sample application.
384
+
385
+ #### information and expectations
386
+
387
+ **The above generator did everthing that's required. This section
388
+ will explain why the generator did what it did. You won't need
389
+ to do any of these steps unless you decide to customize or adapt.**
390
+
391
+ #### User session required
392
+
393
+ Rails 4 now handles this with a gem:
394
+
395
+ ```
396
+ gem 'activerecord-session_store', github: 'rails/activerecord-session_store'
397
+ ```
398
+
399
+ #### Generate a session migration
400
+
401
+ ```
402
+ $ rails g active_record:session_migration
403
+ ```
404
+
68
405
  ### Devise setup
69
406
 
70
407
  * See https://github.com/plataformatec/devise for how to set up devise.
71
408
  * The current version of milia requires that devise use a *User* model.
72
409
 
73
- ### Milia setup
410
+ ```
411
+ $ rails g devise:install
412
+ $ rails g devise user
413
+ ```
74
414
 
75
- #### migrations
415
+ Add the following in <i>config/routes.rb</i> to the existing devise_for :users :
76
416
 
77
- *ALL* models require a tenanting field, whether they are to be universal or to
78
- be tenanted. So make sure the following is added to each migration
417
+ ```
418
+ as :user do # *MUST* come *BEFORE* devise's definitions (below)
419
+ match '/user/confirmation' => 'milia/confirmations#update', :via => :put, :as => :update_user_confirmation
420
+ end
79
421
 
80
- <i>db/migrate</i>
422
+ devise_for :users, :controllers => {
423
+ :registrations => "milia/registrations",
424
+ :confirmations => "milia/confirmations",
425
+ :sessions => "milia/sessions",
426
+ :passwords => "milia/passwords",
427
+ }
81
428
 
82
- ```ruby
83
- t.references :tenant
84
429
  ```
85
430
 
86
- Tenanted models will also require indexes for the tenant field:
431
+ Add the appropriate line below to <i>config/environments/</i>_
432
+ files <i>development.rb, production.rb, test.rb</i>_ (respectively below, editing hosts as appropriate for your app).
433
+ Make sure you've also correctly set up the ActionMailer::Base.smtp_settings. If you're unclear as to how to
434
+ do that, refer to the sample-milia-app.
87
435
 
88
- ```ruby
89
- add_index :TABLE, :tenant_id
436
+ ```
437
+ config.action_mailer.default_url_options = { :host => 'localhost:3000' }
438
+ config.action_mailer.default_url_options = { :host => 'secure.simple-milia-app.com', :protocol => 'https' }
439
+ config.action_mailer.default_url_options = { :host => "www.example.com" }
90
440
  ```
91
441
 
92
- Also create a tenants_users join table:
442
+ EDIT: <i>db/migrate/xxxxxxx_devise_create_users.rb</i>
443
+ and uncomment the confirmable section, it will then look as follows:
93
444
 
94
- <i>db/migrate/20111008081639_create_tenants_users.rb</i>
445
+ ```
446
+ ## Confirmable
447
+ t.string :confirmation_token
448
+ t.datetime :confirmed_at
449
+ t.datetime :confirmation_sent_at
450
+ t.string :unconfirmed_email # Only if using reconfirmable
451
+ ```
95
452
 
96
- ```ruby
97
- class CreateTenantsUsers < ActiveRecord::Migration
98
- def change
99
- create_table :tenants_users, :id => false do |t|
100
- t.references :tenant
101
- t.references :user
102
- end
103
- add_index :tenants_users, :tenant_id
104
- add_index :tenants_users, :user_id
105
- end
106
- end
453
+ and uncomment the confirmation_token index line to look as follows
454
+
455
+ ```
456
+ add_index :users, :confirmation_token, :unique => true
107
457
  ```
108
458
 
109
- #### application controller
459
+ and add above the t.timestamps line:
110
460
 
111
- add the following line AFTER the devise-required filter for authentications:
461
+ ```
462
+ # milia member_invitable
463
+ t.boolean :skip_confirm_change_password, :default => false
112
464
 
113
- <i>app/controllers/application_controller.rb</i>
465
+ t.references :tenant
466
+ ```
114
467
 
115
- ```ruby
116
- before_filter :authenticate_tenant! # authenticate user and setup tenant
468
+ edit <i>config/initializers/devise.rb</i>
469
+ and change mailer_sender to be your from: email address
117
470
 
118
- # ------------------------------------------------------------------------------
119
- # authenticate_tenant! -- authorization & tenant setup
120
- # -- authenticates user
121
- # -- sets current tenant
122
- # -- sets up app environment for this user
123
- # ------------------------------------------------------------------------------
124
- def authenticate_tenant!()
471
+ ```
472
+ config.mailer_sender = "my-email@simple-milia-app.com"
473
+ ```
125
474
 
126
- unless authenticate_user!
127
- email = ( params.nil? || params[:user].nil? ? "" : " as: " + params[:user][:email] )
475
+ OPTIONAL (not required for milia):
476
+ in the same initializer file, locate and uncomment the following lines:
128
477
 
129
- flash[:notice] = "cannot sign you in#{email}; check email/password and try again"
130
-
131
- return false # abort the before_filter chain
132
- end
478
+ ```
479
+ config.pepper = '46f2....'
480
+ config.confirmation_keys = [ :email ]
481
+ config.email_regexp = /\A[^@]+@[^@]+\z/
482
+ ```
483
+
484
+ ### Milia setup
133
485
 
134
- # user_signed_in? == true also means current_user returns valid user
135
- raise SecurityError,"*** invalid sign-in ***" unless user_signed_in?
486
+ #### migrations
136
487
 
137
- set_current_tenant # relies on current_user being non-nil
138
-
139
- # any application-specific environment set up goes here
140
-
141
- true # allows before filter chain to continue
142
- end
488
+ *ALL* models require a tenanting field, whether they are to be universal or to
489
+ be tenanted. So make sure the following is added to each migration:
490
+
491
+ <i>db/migrate/xxxxxxx_create_modelXYZ.rb</i>
143
492
 
493
+ ```
494
+ t.references :tenant
144
495
  ```
145
496
 
497
+ Tenanted models will also require indexes for the tenant field.
146
498
 
147
- catch any exceptions with the following (be sure to also add the designated methods!)
499
+ ```
500
+ add_index :<tablename>, :tenant_id
501
+ ```
502
+
503
+ BUT: Do not add any <i>belongs_to :tenant</i> statements into any of your
504
+ models. milia will do that for all. I do recommend, however, that you add
505
+ into your <i>app/models/tenant.rb</i> file, one line per tenanted model such
506
+ as the following (replacing <model> with your model's name):
148
507
 
149
- ```ruby
150
- rescue_from ::Milia::Control::MaxTenantExceeded, :with => :max_tenants
151
- rescue_from ::Milia::Control::InvalidTenantAccess, :with => :invalid_tenant
508
+ ```
509
+ has_many :<model>s, :dependency => destroy
152
510
  ```
153
511
 
154
- You'll need to place prep_signup_view method in application_controller.rb; it sets up any attributes required by your signup form. below is the example from my application.
512
+ The reason for this is that if you wish to have a master destroy tenant action,
513
+ it will also remove all related tenanted tables and records.
155
514
 
156
- ```ruby
157
- # ------------------------------------------------------------------------------
158
- # klass_option_obj -- returns a (new?) object of a given klass
159
- # purpose is to handle the variety of ways to prepare for a view
160
- # args:
161
- # klass -- class of object to be returned
162
- # option_obj -- any one of the following
163
- # -- nil -- will return klass.new
164
- # -- object -- will return the object itself
165
- # -- hash -- will return klass.new( hash ) for parameters
166
- # ------------------------------------------------------------------------------
167
- def klass_option_obj(klass, option_obj)
168
- return option_obj if option_obj.instance_of?(klass)
169
- option_obj ||= {} # if nil, makes it empty hash
170
- return klass.send( :new, option_obj )
171
- end
515
+ Generate the tenant migration
172
516
 
173
- # ------------------------------------------------------------------------------
174
- # prep_signup_view -- prepares for the signup view
175
- # args:
176
- # tenant: either existing tenant obj or params for tenant
177
- # user: either existing user obj or params for user
178
- # ------------------------------------------------------------------------------
179
- def prep_signup_view(tenant=nil, user=nil, coupon='')
180
- @user = klass_option_obj( User, user )
181
- @tenant = klass_option_obj( Tenant, tenant )
182
- @coupon = coupon
183
- @eula = Eula.get_latest.first
184
- end
517
+ ```
518
+ $ rails g model tenant tenant:references name:string:index
185
519
  ```
186
520
 
187
- My signup form has fields for user's email, organization's name (tenant model), coupon code, and current EULA version.
521
+ Generate the tenants_users join table migration
188
522
 
523
+ ```
524
+ $ rails g migration CreateTenantsUsersJoinTable tenants users
525
+ ```
189
526
 
190
- #### routes
527
+ EDIT: <i>db/migrate/20131119092046_create_tenants_users_join_table.rb</i>
528
+ then uncomment the first index line as follows:
191
529
 
192
- Add the following line into the devise_for :users block
530
+ ```
531
+ t.index [:tenant_id, :user_id]
532
+ ```
533
+
534
+ #### application controller
535
+
536
+ <i>app/controllers/application_controller.rb</i>
537
+ add the following line IMMEDIATELY AFTER line 4 protect_from_forgery
193
538
 
194
- <i>config/routes.rb</i>
195
539
 
196
- ```ruby
197
- devise_for :users, :controllers => { :registrations => "milia/registrations" }
198
540
  ```
199
-
541
+ before_action :authenticate_tenant! # authenticate user and sets up tenant
542
+
543
+ rescue_from ::Milia::Control::MaxTenantExceeded, :with => :max_tenants
544
+ rescue_from ::Milia::Control::InvalidTenantAccess, :with => :invalid_tenant
545
+
546
+ # milia defines a default max_tenants, invalid_tenant exception handling
547
+ # but you can override if you wish to handle directly
548
+ ```
549
+
200
550
  ### Designate which model determines account
201
551
 
202
552
  Add the following acts_as_... to designate which model will be used as the key
@@ -230,38 +580,24 @@ Only designate one model in this manner.
230
580
  end # class Tenant
231
581
  ```
232
582
 
233
- ### Designate universal models
583
+ ### Clean up any generated belongs_to tenant references in all models
234
584
 
235
- Add the following acts_as_universal to *ALL* models which are to be universal and
236
- remove any superfluous
237
-
238
- ```ruby
239
- belongs_to :tenant
240
- ```
241
-
242
- which the generator might have generated ( acts_as_tenant will specify that ).
585
+ which the generator might have generated
586
+ ( both <i>acts_as_tenant</i> and <i>acts_as_universal</i> will specify these ).
587
+
588
+ ### Designate universal models
243
589
 
244
- <i>app/models/eula.rb</i>
590
+ Add the following acts_as_universal to *ALL* models which are to be universal.
245
591
 
246
592
  ```ruby
247
- class Eula < ActiveRecord::Base
248
-
249
593
  acts_as_universal
250
-
251
- end # class Eula
252
594
  ```
253
595
 
254
596
  ### Designate tenanted models
255
597
 
256
- Add the following acts_as_tenant to *ALL* models which are to be tenanted and
257
- remove any superfluous
598
+ Add the following acts_as_tenant to *ALL* models which are to be tenanted.
599
+ Example for a ficticous Post model:
258
600
 
259
- ```ruby
260
- belongs_to :tenant
261
- ```
262
-
263
- which the generator might have generated ( acts_as_tenant will specify that ).
264
-
265
601
  <i>app/models/post.rb</i>
266
602
 
267
603
  ```ruby
@@ -272,7 +608,6 @@ which the generator might have generated ( acts_as_tenant will specify that ).
272
608
  end # class Post
273
609
  ```
274
610
 
275
-
276
611
  ### Exceptions raised
277
612
 
278
613
  ```ruby
@@ -280,12 +615,29 @@ which the generator might have generated ( acts_as_tenant will specify that ).
280
615
  Milia::Control::MaxTenantExceeded
281
616
  ```
282
617
 
618
+ ### post authenticate_tenant! callback [optional]
619
+
620
+ In some applications, you will want to set up commonly used
621
+ variables used throughout your application, after a user and a
622
+ tenant have been established. This is optional and if the
623
+ callback is missing, nothing will happen.
624
+
625
+ <i>app/controllers/application_controller.rb</i>
626
+
627
+ ```ruby
628
+ def callback_authenticate_tenant
629
+ # set_environment or whatever else you need for each valid session
630
+ end
631
+ ```
632
+
633
+
634
+
283
635
  ### Tenant pre-processing hooks
284
636
 
285
637
  #### Milia expects a tenant pre-processing & setup hook:
286
638
 
287
639
  ```ruby
288
- Tenant.create_new_tenant(params) # see sample code below
640
+ Tenant.create_new_tenant(tenant_params, coupon_params) # see sample code below
289
641
  ```
290
642
 
291
643
  where the sign-up params are passed, the new tenant must be validated, created,
@@ -297,19 +649,28 @@ immediately after the new tenant has been created).
297
649
  <i>app/models/tenant.rb</i>
298
650
 
299
651
  ```ruby
300
- def self.create_new_tenant(params)
301
-
302
- tenant # Tenant.new(:cname => params[:user][:email], :company => params[:tenant][:company])
652
+ def self.create_new_tenant(tenant_params, user_params, coupon_params)
653
+
654
+ tenant = Tenant.new(:name => tenant_params[:name])
655
+
656
+ if new_signups_not_permitted?(coupon_params)
303
657
 
304
- if new_signups_not_permitted?(params)
305
-
306
658
  raise ::Milia::Control::MaxTenantExceeded, "Sorry, new accounts not permitted at this time"
307
-
659
+
308
660
  else
309
661
  tenant.save # create the tenant
310
662
  end
311
663
  return tenant
312
664
  end
665
+
666
+ # ------------------------------------------------------------------------
667
+ # new_signups_not_permitted? -- returns true if no further signups allowed
668
+ # args: params from user input; might contain a special 'coupon' code
669
+ # used to determine whether or not to allow another signup
670
+ # ------------------------------------------------------------------------
671
+ def self.new_signups_not_permitted?(params)
672
+ return false
673
+ end
313
674
  ```
314
675
 
315
676
  #### Milia expects a tenant post-processing hook:
@@ -337,10 +698,77 @@ work in setting things up for a new tenant.
337
698
  # other -- any other parameter string from initial request
338
699
  # ------------------------------------------------------------------------
339
700
  def self.tenant_signup(user, tenant, other = nil)
340
- StartupJob.queue_startup( tenant, user, other )
701
+ # StartupJob.queue_startup( tenant, user, other )
702
+ # any special seeding required for a new organizational tenant
703
+
704
+ Member.create_org_admin(user) # sample if using Member as tenanted member information model
341
705
  end
342
706
  ```
343
707
 
708
+ ### View for Organizer sign ups
709
+
710
+ This example shows how to display a signup form together with recaptcha.
711
+ It also shows usage of an optional coupon field
712
+ for whatever reason you might need. If you're not familiar with haml, leading spaces are significant
713
+ and are used to indicate logical blocks. Otherwise, it's kinda like erb without all the syntactical cruff.
714
+ Leading "." indicate div class; "#" indicates a div ID. The example here is
715
+ taken from sample-milia-app.
716
+
717
+ <i>app/views/devise/registrations/new.html.haml</i>
718
+
719
+ ```ruby
720
+ %h1 Simple Milia App
721
+ .block#block-signup
722
+ %h2 New Organizational Sign up
723
+ .content
724
+ %span.description
725
+ %i
726
+ If you're a member of an existing group in our system,
727
+ click the activate link in the invitation email from your organization's admin.
728
+ You should not sign up for a new organizational account.
729
+ %br
730
+ .flash
731
+ - flash.each do |type, message|
732
+ %div{ :class => "message #{type}" }
733
+ %p= message
734
+ - flash.clear # clear contents so we won't see it again
735
+
736
+ = form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :class => "form" }) do |f|
737
+ .group
738
+ = f.label :email, :class => "label"
739
+ = f.text_field :email, :class => "text_field"
740
+ %span.description Ex. test@example.com
741
+ .group
742
+ = f.label :password, :class => "label"
743
+ = f.password_field :password, :class => "text_field"
744
+ %span.description must be at least 6 characters
745
+ .group
746
+ = f.label :password_confirmation, "Re-enter Password", :class => "label"
747
+ = f.password_field :password_confirmation, :class => "text_field"
748
+ %span.description to confirm your password
749
+
750
+ .group
751
+ = fields_for( :tenant ) do |w|
752
+ = w.label( :name, 'Organization', :class => "label" )
753
+ = w.text_field( :name, :class => "text_field")
754
+ %span.description unique name for your group or organization for the new account
755
+
756
+ - if ::Milia.use_coupon
757
+ .group
758
+ = label_tag( 'coupon', 'Coupon code', :class => "label" )
759
+ = text_field_tag( "coupon[coupon]", @coupon.to_s, :size => 8, :class => "text_field" )
760
+ %span.description optional promotional code, if any
761
+
762
+ - if ::Milia.use_recaptcha
763
+ = recaptcha_tags( :display => { :theme => 'clean', :tabindex => 0 } )
764
+
765
+ .group.navform.wat-cf
766
+ %button.button{ :type => "submit" }
767
+ = image_tag "web-app-theme/icons/tick.png"
768
+ Sign up
769
+ = render :partial => "devise/shared/links"
770
+
771
+ ```
344
772
 
345
773
  ### Alternate use case: user belongs to multiple tenants
346
774
 
@@ -377,9 +805,100 @@ for each of the subordinate models in the join.
377
805
  Comment.joins(stuff).where( where_restrict_tenants(Post, Author) ).all
378
806
  ```
379
807
 
808
+ ## no tenant authorization required controller actions: root_path
809
+
810
+ Any controller actions, such as the root_path page, will need to skip the tenant & user authorizations.
811
+ For example in <i>app/controllers/home_controller.rb </i> place the following near the top of the controller:
812
+
813
+ ```ruby
814
+ skip_before_action :authenticate_tenant!, :only => [ :index ]
815
+ ```
816
+
817
+ ## using tokens for authentication
818
+
819
+ My app has certain actions which require a token for authentication, instead of a user
820
+ sign-in. These use cases include an icalendar feed for a particular user's assignments
821
+ or a generic icalendar feed for all of an organization's events. The tokens are NOT
822
+ a general replacement for user sign-in for all actions, but merely to enable a simple
823
+ restful API for certain specific actions. This section will explain how to incorporate
824
+ token authentication together with milia/devise. Please note that the application
825
+ assigns to each user an authentication token for this use, as well as creates a
826
+ generic "guest" for the organization itself for accessing the organization-wide action.
827
+
828
+ The general scheme is to have a prepend_before_action authenticate_by_token! specified
829
+ only for those actions allowed. This action determines the "user" required to proceed
830
+ with the action, signs in that user via devise, then falls through to the normal
831
+ before_action authenticate_tenant! action which establishes the current_tenant.
832
+
833
+ Below are some examples of this (typically the token is passed as the id parameter):
834
+
835
+ <i>app/controllers/application_controller</i>
836
+ ```ruby
837
+ # ------------------------------------------------------------------------------
838
+ # NOTE: be sure to use prepend_before_action authenticate_by_token!
839
+ # so that this will occur BEFORE authenticate_tenant!
840
+ # ------------------------------------------------------------------------------
841
+ # Notice we are passing store false, so the user is not
842
+ # actually stored in the session and a token is needed for every request.
843
+ # ------------------------------------------------------------------------------
844
+ def authenticate_by_token!
845
+ # special case for designated actions only
846
+ if ( controller_name == "feeder" &&
847
+ ( user = User.find_user_by_user_feed( params ) )
848
+ ) ||
849
+ ( controller_name == "questions" && ['signup_form', 'finish_signup'].include?(action_name) &&
850
+ ( user = User.find_user_by_user_feed( params ) )
851
+ )
852
+
853
+ # create a special session after authorizing a user
854
+ reset_session
855
+ sign_in(user, store: false) # devise's way to signin the user
856
+ # now continue with tenant authorization & set up
857
+ true # ok to continue processing
858
+
859
+ else
860
+ act_path = controller_name.to_s + '/' + action_name.to_s
861
+ logger.info("SECURITY - access denied #{Time.now.to_s(:db)} - auth: #{params[:userfeed] }\tuid:#{(user.nil? ? 'n/f' : user.id.to_s)}\tRequest: " + act_path)
862
+ render( :nothing => true, :status => :forbidden) # redirect_back # go back to where you were
863
+ nil # abort further processing
864
+ end
865
+
866
+ end
867
+
868
+ ```
869
+ <i>app/controllers/feeder_controller</i>
870
+ ```ruby
871
+ prepend_before_action :authenticate_by_token! # special authtentication by html token
872
+ ```
873
+
874
+ <i>app/models/user.rb</i>
875
+ ```ruby
876
+ # ------------------------------------------------------------------------
877
+ # find_user_by_user_feed -- returns a user based on auth code from params
878
+ # ------------------------------------------------------------------------
879
+ def self.find_user_by_user_feed( params )
880
+ # can get auth by either :userfeed or :id
881
+ key = ( params[:userfeed].blank? ? params[:id] : params[:userfeed] )
882
+ return nil if key.blank? # neither key present; invalid
883
+ return User.where( :authentication_token => key ).first # find by the key; nil if invalid
884
+ end
885
+
886
+ def make_authentication_token
887
+ self.authentication_token = generate_unique_authentication_token
888
+ end
889
+
890
+ def generate_unique_authentication_token
891
+ loop do
892
+ token = AuthKey.make_token # this can be anything to generate a random large token
893
+ break token unless User.where(authentication_token: token).first
894
+ end
895
+ end
896
+ ```
897
+
898
+
380
899
  ## console
381
900
 
382
- Note that even when running the console ($ rails console) will be run in
901
+ Note that even when running the console, ($ rails console) it will be run in
383
902
  multi-tenanting mode. You will need to establish a current_user and
384
903
  setup the current_tenant, otherwise most Model DB accesses will fail.
385
904
 
@@ -396,6 +915,229 @@ load when I start the console. This does the following:
396
915
  change_tenant(1,1) # or whatever is an appropriate starting user, tenant
397
916
  ```
398
917
 
918
+ ## Whitelisting additional parameters for tenant/user/coupon
919
+
920
+ During the Tenant.create_new_tenant part of the sign-up process, three
921
+ sets of whitelisted parameters are passed to the method: The parameters
922
+ for tenant, user, and coupon. But some applications might require more or
923
+ other parameters than the ones expected by milia. Sometimes the application
924
+ might need to add some parameters of its own, such a EULA version number,
925
+ additions to an activation message, or a unique name for the tenant itself.
926
+
927
+ Milia has a mechanism to add additional parameters to be whitelisted.
928
+ In <i>config/initializers/milia.rb</i> you can add a list of symbols for
929
+ the additional parameters to each of a config setting for any of the
930
+ three (tenant, user, or coupon). The example below shows how.
931
+
932
+ ```ruby
933
+ # whitelist user params list
934
+ # allows an app to expand the permitted attribute list
935
+ # specify each attribute as a symbol
936
+ # example: [:name]
937
+ config.whitelist_user_params = [:eula_id, :message]
938
+
939
+ # whitelist tenant params list
940
+ # allows an app to expand the permitted attribute list
941
+ # specify each attribute as a symbol
942
+ # example: [:name]
943
+ config.whitelist_tenant_params = [:company, :cname]
944
+
945
+ # whitelist coupon params list
946
+ # allows an app to expand the permitted attribute list
947
+ # specify each attribute as a symbol
948
+ # example: [:coupon]
949
+ config.whitelist_coupon_params = [:vendor]
950
+
951
+ ```
952
+
953
+ ## inviting additional user/members
954
+
955
+ To keep this discussion simple, we'll give the example of using class Member < Activerecord::Base
956
+ which will be a tenanted table for keeping information regarding all the members in a given
957
+ organization. The name "Member" is not a requirement of milia. But this is how you would set up an
958
+ invite_member capability. It is in this event, that you will require the line in the Tenant
959
+ post-processing hook <i>tenant_signup</i> <pre>Member.create_org_admin(user)</pre> which also
960
+ creates the Member record for the initial admin on the account.
961
+
962
+ ```
963
+ $ rails g resource member tenant:references user:references first_name:string last_name:string favorite_color:string
964
+ ```
965
+
966
+ ADD to <i>app/models/tenant.rb</i>
967
+ ```ruby
968
+ has_many :members, dependent: :destroy
969
+ ```
970
+
971
+ ADD to <i>app/models/user.rb</i>
972
+ ```ruby
973
+ has_one :member, :dependent => :destroy
974
+ ```
975
+
976
+
977
+ EDIT <i>app/models/member.rb</i>
978
+ REMOVE belongs_to :tenant
979
+ ADD
980
+ ```ruby
981
+ acts_as_tenant
982
+
983
+ DEFAULT_ADMIN = {
984
+ first_name: "Admin",
985
+ last_name: "Please edit me"
986
+ }
987
+
988
+ def self.create_new_member(user, params)
989
+ # add any other initialization for a new member
990
+ return user.create_member( params )
991
+ end
992
+
993
+ def self.create_org_admin(user)
994
+ new_member = create_new_member(user, DEFAULT_ADMIN)
995
+ unless new_member.errors.empty?
996
+ raise ArgumentError, new_member.errors.full_messages.uniq.join(", ")
997
+ end
998
+
999
+ return new_member
1000
+
1001
+ end
1002
+ ```
1003
+
1004
+ CREATE a form for inputting new member information for an invite
1005
+ (below is a sample only)
1006
+ <i>app/views/members/new.html.haml</i>
1007
+ ```ruby
1008
+ %h1 Simple Milia App
1009
+ .block#block-signup
1010
+ %h2 Invite a new member into #{@org_name}
1011
+ .content.login
1012
+ .flash
1013
+ - flash.each do |type, message|
1014
+ %div{ :class => "message #{type}" }
1015
+ %p= message
1016
+ - flash.clear # clear contents so we won't see it again
1017
+
1018
+ = form_for(@member, :html => { :class => "form login" }) do |f|
1019
+ - unless @member.errors.empty? && @user.errors.empty?
1020
+ #errorExplanation.group
1021
+ %ul
1022
+ = @member.errors.full_messages.uniq.inject(''){|str, msg| (str << "<li> #{msg}") }.html_safe
1023
+ = @user.errors.full_messages.uniq.inject(''){|str, msg| (str << "<li> #{msg}") }.html_safe
1024
+
1025
+ = fields_for( :user ) do |w|
1026
+ .group
1027
+ = w.label :email, :class => "label "
1028
+ = w.text_field :email, :class => "text_field"
1029
+ %span.description Ex. test@example.com; must be unique
1030
+
1031
+ .group
1032
+ = f.label :first_name, :class => "label "
1033
+ = f.text_field :first_name, :class => "text_field"
1034
+
1035
+ .group
1036
+ = f.label :last_name, :class => "label "
1037
+ = f.text_field :last_name, :class => "text_field"
1038
+
1039
+ .group
1040
+ = f.label :favorite_color, :class => "label "
1041
+ = f.text_field :favorite_color, :class => "text_field"
1042
+ %span.description What is your favorite color?
1043
+
1044
+ .group.navform.wat-cf
1045
+ %button.button{ :type => "submit" }
1046
+ = image_tag "web-app-theme/icons/key.png"
1047
+ Create user and invite
1048
+ ```
1049
+
1050
+ ## authorized tenanted user landing page:
1051
+
1052
+ You will need a members-only landing page for after someone successfully signs into your app.
1053
+ Here is what I typically do:
1054
+
1055
+ ```ruby
1056
+ # REPLACE the empty def index ... end with following ADD:
1057
+ # this will give you improved handling for letting user know
1058
+ # what is expected. If you want to have a welcome page for
1059
+ # signed in users, uncomment the redirect_to line, etc.
1060
+ def index
1061
+ if user_signed_in?
1062
+
1063
+ # was there a previous error msg carry over? make sure it shows in flasher
1064
+ flash[:notice] = flash[:error] unless flash[:error].blank?
1065
+ redirect_to( welcome_path() )
1066
+
1067
+ else
1068
+
1069
+ if flash[:notice].blank?
1070
+ flash[:notice] = "sign in if your organization has an account"
1071
+ end
1072
+
1073
+ end # if logged in .. else first time
1074
+
1075
+ end
1076
+
1077
+ def welcome
1078
+ end
1079
+
1080
+ ```
1081
+
1082
+ ## Milia API Reference Manual
1083
+
1084
+ ### From controller-levels:
1085
+
1086
+ ```ruby
1087
+ set_current_tenant( tenant_id )
1088
+ # raise InvalidTenantAccess unless tenant_id is one of the current_user valid tenants
1089
+ ```
1090
+
1091
+ set_current_tenant can be used to change the current_tenanted (for example, if a member
1092
+ can belong to multiple tenants and wants to switch between them). See example else in this
1093
+ README. NOTE: you will normally NEVER use this. Milia does this automatically during
1094
+ authorize_tenant! so you never should at the beginning of a session.
1095
+
1096
+ ### From model-levels:
1097
+ ```ruby
1098
+ Tenant.current_tenant -- returns tenant object for the current tenant; nil if none
1099
+
1100
+ Tenant.current_tenant_id -- returns tenant_id for the current tenant; nil if none
1101
+ ```
1102
+
1103
+ If you need to gain access to tenant object itself (say to get the name of the tenant),
1104
+ then use these accessor methods.
1105
+
1106
+ ### From background, rake, or console-level (CAUTION):
1107
+
1108
+ From background jobs (only at the start of the task);
1109
+ tenant can either be a tenant object or an integer tenant_id; anything else will raise
1110
+ exception. set_current_tenant -- is model-level ability to set the current tenant
1111
+ NOTE: *USE WITH CAUTION* normally this should *NEVER* be done from
1112
+ the models ... it is only useful and safe WHEN performed at the start
1113
+ of a background job (DelayedJob#perform) or at start of rails console, or a rake task.
1114
+
1115
+ ```ruby
1116
+ Tenant.set_current_tenant( tenant )
1117
+ raise ArgumentError, "invalid tenant object or id"
1118
+ ```
1119
+
1120
+
1121
+ ## running tests
1122
+
1123
+ You must cd into the milia/test directory.
1124
+ Then run test:units, test:functionals seperately.
1125
+ For some reason, rake test won't work and yields errors.
1126
+
1127
+ ```ruby
1128
+ $ cd test
1129
+ $ rake db:create
1130
+ $ rake db:migrate
1131
+ $ rake db:test:prepare
1132
+ $ rake test:units
1133
+ $ rake test:functionals
1134
+ ```
1135
+
1136
+ ### test coverage
1137
+ * All models, including milia-added methods, are tested.
1138
+ * Functional testing currently covers all milia-added controller methods.
1139
+ * TBD: milia overrides of devise registration, confirmation controllers
1140
+
399
1141
  ## Cautions
400
1142
 
401
1143
  * Milia designates a default_scope for all models (both universal and tenanted). From Rails 3.2 onwards, the last designated default scope overrides any prior scopes and will invalidate multi-tenanting; so *DO NOT USE default_scope*
@@ -403,11 +1145,18 @@ change_tenant(1,1) # or whatever is an appropriate starting user, tenant
403
1145
  * SQL statements executed outside the context of ActiveRecord pose a potential danger; the current milia implementation does not extend to the DB connection level and so cannot enforce tenanting at this point.
404
1146
  * The tenant_id of a universal model will always be forced to nil.
405
1147
  * The tenant_id of a tenanted model will be set to the current_tenant of the current_user upon creation.
1148
+ * HABTM (has_and_belongs_to_many) associations don't have models; they shouldn't have id fields
1149
+ (setup as below) nor any field other than the joined references; they don't have a tenant_id field;
1150
+ rails will invoke the default_scope of the appropriate joined table which does have a tenant_id field.
1151
+
406
1152
 
407
1153
  ## Further documentation
1154
+
408
1155
  * Check out the three-part blog discussion of _Multi-tenanting Ruby on Rails Applications on Heroku_
409
1156
  at: http://myrailscraft.blogspot.com/2013/05/multi-tenanting-ruby-on-rails.html
410
1157
  * See the Milia tutorial at: http://myrailscraft.blogspot.com/2013/05/multi-tenanting-ruby-on-rails_3982.html
1158
+ * see code & setup sample in test/railsapp, which is also used to run the tests.
1159
+ * see milia wiki on github for a CHANGE HISTORY page.
411
1160
 
412
1161
 
413
1162
  ## Contributing to milia
@@ -422,5 +1171,5 @@ at: http://myrailscraft.blogspot.com/2013/05/multi-tenanting-ruby-on-rails.html
422
1171
 
423
1172
  ## Copyright
424
1173
 
425
- Copyright (c) 2011 Daudi Amani. See LICENSE.txt for further details.
1174
+ Copyright (c) 2014 Daudi Amani. See LICENSE.txt for further details.
426
1175