serious_business 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: '02748be03ce33b0d73d118a6182ad52120cf8be9'
4
+ data.tar.gz: a0af8e6f7633262856ba169dd2bccc335073945e
5
+ SHA512:
6
+ metadata.gz: f3b317bd1cc78843df8f10b1405af12e311d033d3d0d8cb6805c044141dcf75b6970f83ef270dc35b02e47e81ee1b211b25dd82286a56a97d5f2ec6cbc38107b
7
+ data.tar.gz: 50f0e42a28a2690eab2c8fdd59b31ece8a82be2e568edb803920c68715caed8bf19e5aa3103acd3fca822a48eacbf9df3fa77ddb6d4ac47254ff94abb556bc38
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Axel Tetzlaff
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # SeriousBusiness: Best application pratices in a handsome DSL
2
+
3
+ A small library helping app developers to comply with basic usability and security pattern.
4
+ By encapsulating business actions in separate classes good pratices are enforced and controllers and models are less likely to turn into [God objects](https://en.wikipedia.org/wiki/God_object)
5
+
6
+ Using it you will get:
7
+
8
+ * a declarative DSL to specify authorization
9
+ * a simple way to check permissions object based in views
10
+ * a guided mechanism to a concise user experience
11
+ * a simpler way to test logic that before went into controllers
12
+ * transactional business actions per default
13
+ * automagic tracking on which users and actions affecting you models
14
+
15
+
16
+ ## Installation
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'serious_business'
21
+ ```
22
+
23
+ And then execute:
24
+ ```bash
25
+ $ bundle
26
+ ```
27
+
28
+ Or install it yourself as:
29
+ ```bash
30
+ $ gem install serious_business
31
+ ```
32
+ ## Usage
33
+
34
+ The gem uses polymorphic associations to track the actions you specify. For that it creates some tables and a initializer you may need to modify:
35
+
36
+ rails g serious_business:install
37
+
38
+ The gem expects one model class to act as *actor* for your actions. The default for this is `User`.
39
+
40
+ You will need to make the following changes to you model class to be aware of the actions:
41
+
42
+ ```ruby
43
+ # app/models/user.rb
44
+ class User < ApplicationRecord
45
+ include SeriousBusiness::Actor
46
+ end
47
+
48
+ # this is needed for hot code replacement in dev mode to work
49
+ # assuming you put your actions in app/models/actions
50
+ Dir[Rails.root.join('app','models', 'actions', '*.rb')].each {|file| require_dependency file }
51
+ ```
52
+
53
+ To create a 'business action' you inherit from `SeriousBusiness::Action`
54
+ ```ruby
55
+ # app/models/actions/update_user.rb
56
+ module Actions
57
+ class UpdateUser < SeriousBusiness::Action
58
+ # this action needs to be initialized with a user that should be updated
59
+ needs :user
60
+
61
+ # this actions reads the email from a hash most likely set from params
62
+ att :email, presence: true
63
+
64
+
65
+ # this action should be able to change only the user
66
+ # that is executing the action
67
+ forbid_if :not_self { |action| action.actor != action.user }
68
+
69
+ # unless it's an admin - they are allowed to do everything, right?
70
+ allow_if {|action| action.actor.admin? }
71
+
72
+
73
+ # implement this method to specify the actual logic of the action.
74
+ # return an array of all objects that are logically affected by the
75
+ # change (the actor is persisted automatically)
76
+ def execute
77
+ user.update_attributes(email: form_model.email)
78
+ [user]
79
+ end
80
+ end
81
+ end
82
+
83
+ ```
84
+
85
+ By creating action classes you actor class (the class you included `SeriousBusiness::Actor` in) gets a method for every action.
86
+ For the above example that means you can now call:
87
+
88
+ action = user.update_user
89
+
90
+ Since we declared that this action applies to another user we can pass other user like this:
91
+
92
+ ```ruby
93
+ other_user = User.find(params[:user_id])
94
+ action = user.update_user.for_user(other_user)
95
+ ```
96
+
97
+ Since we now specified everything thats 'needed' for the action, we can ask if the action can be executed. And since we created it from an user as actor it can anwser itself:
98
+
99
+
100
+ ```ruby
101
+ action.can? # will return if user.admin? or user == other_user
102
+ ```
103
+ And since it's an action that takes further data probably generated from a form submit we have to pass that in to actually execute the action
104
+
105
+
106
+ ```ruby
107
+ action = user.update_user.for_user(other_user).with_params(email: 'foo@example.org')
108
+
109
+ # Note the exclamation mark at the end of 'execute!'
110
+ action.execute! # true if the action was executed successfully
111
+ # this is transactional - no changes are made to the db
112
+ # if anything prevents the action from succeeding (ie.
113
+ # failing guards/permissions or validations)
114
+ ```
115
+ Actions themselves are fully fleged ActiveRecord Models that are persisted when successfully executed. With them references to the affected models you return from you implementation of the `execute` are stored.
116
+
117
+ To access the list of actions that affected an object you have to include the include `SeriousBusiness::AffectedByActions` concern. Since in this model we modify the User:
118
+
119
+ ```ruby
120
+ # app/models/user.rb
121
+ class User < ApplicationRecord
122
+ include SeriousBusiness::Actor
123
+ include SeriousBusiness::AffectedByActions
124
+ end
125
+ ```
126
+
127
+ Afterwards you can retrieve all actions that affected this model.
128
+
129
+ ```ruby
130
+ actions = user.modifiers # [SeriousBusiness::Action]
131
+ actions.last.actor # The User that executed the action
132
+ actions.last.description # A textual description of the action
133
+ ```
134
+
135
+ SeriousBusiness encurages to maintain human consumable descriptions for a better
136
+ user experience
137
+
138
+ To start create the following structure in your i18n yaml file:
139
+
140
+ ```
141
+ serious_action:
142
+ description_with_failed_guards: '%{description} not possible because %{reasons}'
143
+ failed_guard_join_with: ' and '
144
+ update_user
145
+ description: Updating user data
146
+ cta: update
147
+ guards:
148
+ not_self: you are not this user
149
+ ```
150
+
151
+ This enables you to generate descriptive labels for those actions independent of their location of use:
152
+
153
+ ```
154
+ action = user.update_user.for_user(other_user)
155
+ action.cta_label # 'update' - ie for buttons
156
+
157
+ action.description # 'Updating the user' - i.e. for history
158
+
159
+ # the description is extended automatically if the action can not be executed
160
+ action.description # 'Updating the user not possible because you are not this user'
161
+ ```
162
+
163
+ The latter is especially useful to give the user meaningful feedback to an unavailble action:
164
+
165
+ ```
166
+ <% if update_action.can? %>
167
+ <%= link_to 'Edit', edit_account_path(account) %>
168
+ <% else %>
169
+ <span title= "<%= update_action.description %>" >Edit</span>
170
+ <% end %>
171
+ ```
172
+
173
+
174
+ ## Contributing
175
+
176
+ Pull-requests are welcome - with tests loved!
177
+
178
+ ## License
179
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'SeriousBusiness'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ require 'bundler/gem_tasks'
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/serious_business .js
2
+ //= link_directory ../stylesheets/serious_business .css
@@ -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. JavaScript code in this file should be added after the last require_* statement.
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 other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ module SeriousBusiness
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module SeriousBusiness
2
+ module ApplicationHelper
3
+ def form_for_action action, path, custom_opts = {}, &blk
4
+ options = {
5
+ as: action.form_model.to_param,
6
+ url: path,
7
+ method: :post
8
+ }
9
+ form_for action.form_model, options.merge(custom_opts), &blk
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ module SeriousBusiness
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module SeriousBusiness
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ module SeriousBusiness::Actor
2
+ # this module will be dynamically filled with action methods
3
+ # depending on your declarations
4
+ end
@@ -0,0 +1,249 @@
1
+ module SeriousBusiness
2
+ class Action < ApplicationRecord
3
+ self.table_name= 'serious_actions'
4
+ belongs_to :actor, class_name: SeriousBusiness.actor_class_name
5
+ has_many :affecteds, foreign_key: :serious_action_id
6
+ has_many :affectables, through: :affecteds, source: :affected
7
+ attr_accessor :transient_affected_models
8
+
9
+ def self.att(name, options = {})
10
+ name = name.to_sym
11
+ self.form_model_class.send(:attr_accessor, name)
12
+ if options.any?
13
+ self.form_model_class.validates name, options
14
+ end
15
+ self.needed_attributes << name
16
+ end
17
+
18
+ def self.required_attributes
19
+ @_required_attributes ||= []
20
+ end
21
+
22
+ class MissingModelException < StandardError
23
+ def initialize(model_names)
24
+ method_names = model_names.map{|n| "for_#{n}"}.join(', ')
25
+ super("You have to call the following methods before setting params #{method_names}")
26
+ end
27
+ end
28
+
29
+ def with_params(params = {})
30
+ if params.respond_to? :require
31
+ params = params
32
+ .require(self.class.param_name)
33
+ .permit!
34
+ params.to_h.slice!(*self.class.needed_attributes.map(&:to_s))
35
+ end
36
+
37
+ # make sure 'needed' models were set before trying to apply params
38
+ missing_values = self.class.required_attributes.select do |needed_name|
39
+ self.send(needed_name).nil?
40
+ end
41
+ raise MissingModelException.new(missing_values) if missing_values.any?
42
+
43
+ reset_form_model
44
+ form_model.assign_attributes(params) unless params.empty?
45
+ self
46
+ end
47
+
48
+ def self.needs(name)
49
+ required_attributes << name.to_sym
50
+ self.send(:attr_reader, name)
51
+ self.send(:define_method, "for_#{name}") do |needed|
52
+ reset_form_model
53
+ self.instance_variable_set("@#{name}", needed)
54
+ self
55
+ end
56
+ end
57
+
58
+ def self.guards
59
+ @_guards ||= []
60
+ end
61
+
62
+ IfGuard = Struct.new(:prc) do
63
+ def pass?(action)
64
+ action.actor.instance_exec(action, &prc)
65
+ end
66
+ end
67
+
68
+ UnlessGuard = Struct.new(:reason, :prc) do
69
+ def pass?(action)
70
+ !action.actor.instance_exec(action, &prc)
71
+ end
72
+ end
73
+
74
+ def self.forbid_if reason, &blk
75
+ guards << UnlessGuard.new(reason, blk)
76
+ end
77
+
78
+ def self.allow_if &blk
79
+ guards << IfGuard.new(blk)
80
+ end
81
+
82
+ def failed_guards
83
+ if_guards, unless_guards = self.class.guards
84
+ .partition{ |guard| guard.is_a?(IfGuard) }
85
+ return [] if Array.wrap(if_guards).any?{|g| g.pass?(self)}
86
+
87
+ Array.wrap(unless_guards).select{ |guard| !guard.pass?(self) }
88
+ end
89
+
90
+ def full_guard_messages
91
+ failed_guards
92
+ .map(&:reason)
93
+ .map do |reason|
94
+ i18n = I18n.t("serious_action.#{self.class.param_name}.guards.#{reason}")
95
+ if i18n.starts_with? 'translation missing:'
96
+ global_i18n = I18n.t("serious_action.global_guards.#{reason}")
97
+ i18n = global_i18n unless global_i18n.starts_with? 'translation missing:'
98
+ end
99
+ i18n
100
+ end.join(I18n.t('serious_action.failed_guard_join_with'))
101
+ end
102
+
103
+ def can?
104
+ failed_guards.empty?
105
+ end
106
+
107
+ def self.form_model_class
108
+ @model_class ||= begin
109
+ clazz = Class.new(BaseFormModel)
110
+ self.const_set(:FormModel, clazz)
111
+ clazz
112
+ end
113
+ end
114
+
115
+ def self.needed_attributes
116
+ @_attribs ||= []
117
+ end
118
+
119
+ def self.param_name
120
+ name.demodulize.underscore
121
+ end
122
+
123
+ def self.build(actor_id: , for_model: [], params: {} )
124
+ action = self.new(actor_id: actor_id)
125
+ if params.respond_to? :require
126
+ params = params
127
+ .require(param_name)
128
+ .permit self.needed_attributes
129
+ end
130
+ action
131
+ end
132
+
133
+ def init_from_needed
134
+ {}
135
+ end
136
+
137
+ def all_attributes_from(other_model)
138
+ other_model.attributes.slice(*self.class.needed_attributes.map(&:to_s))
139
+ end
140
+
141
+ def form_model(params={})
142
+ @_form_model ||= begin
143
+ attributes = init_from_needed.merge(params)
144
+ model_instance = self.class.form_model_class.new
145
+ model_instance.instance_variable_set(:@_action, self)
146
+ model_instance.assign_attributes(attributes)
147
+ model_instance
148
+ end
149
+ end
150
+
151
+ def params
152
+ form_model
153
+ end
154
+
155
+ def actor_class
156
+ @_actor_class ||= begin
157
+ raise "No actor_class specified! #TODO link to config" unless SeriousBusinessConfig.actor_class_name.present?
158
+ Kernel.const_get(SeriousBusinessConfig.actor_class_name)
159
+ end
160
+ end
161
+
162
+ def self.inherited(child_class)
163
+ super
164
+
165
+ method_name = child_class.name.demodulize.underscore
166
+ actor_class = Kernel.const_get SeriousBusiness.actor_class_name
167
+
168
+ if actor_class.respond_to? method_name
169
+ raise "Action with the same name already registered #{child_class.name}"
170
+ end
171
+
172
+ SeriousBusiness::Actor.send(:define_method, method_name) do |params: {}, for_model: nil|
173
+ child_class.build(actor_id: self.id, for_model: for_model, params: params)
174
+ end
175
+ end
176
+
177
+ def cta_label
178
+ I18n.t("serious_action.#{self.class.param_name}.cta")
179
+ end
180
+
181
+ def description
182
+ i18n_params = self.class.required_attributes.inject({}) do |sum, attr_name|
183
+ sum[attr_name] = self.instance_variable_get("@#{attr_name}")
184
+ sum
185
+ end
186
+ content = I18n.t("serious_action.#{self.class.param_name}.description", plan: i18n_params[:plan] )
187
+ if persisted? || can?
188
+ content
189
+ else
190
+ I18n.t("serious_action.description_with_failed_guards", description: content, reasons: full_guard_messages)
191
+ end
192
+ end
193
+
194
+ def success_description
195
+ custom_msg = I18n.t("serious_action.#{self.class.param_name}.success_description")
196
+ return custom_msg unless custom_msg.starts_with? 'translation missing'
197
+ content = I18n.t("serious_action.#{self.class.param_name}.description")
198
+ I18n.t("serious_action.success_description", description: content)
199
+ end
200
+
201
+ def execute!
202
+ unless can?
203
+ form_model.errors.add(:base, description)
204
+ return false
205
+ end
206
+ self.class.transaction do
207
+ begin
208
+ if self.class.needed_attributes.any? && !form_model.valid?
209
+ return false
210
+ end
211
+ self.transient_affected_models = self.execute
212
+ self.transient_affected_models.each do |model|
213
+ model.errors.each do |key, error|
214
+ error_key = if self.class.needed_attributes.include? key
215
+ key
216
+ else
217
+ :base
218
+ end
219
+ form_model.errors.add error_key, error
220
+ end
221
+ end
222
+ if form_model.errors.any?
223
+ return false
224
+ end
225
+ self.save!
226
+ self.transient_affected_models.each do |model|
227
+ SeriousBusiness::Affected.create!(action: self, affected: model)
228
+ end
229
+ return true
230
+ rescue Exception => e
231
+ raise e
232
+ end
233
+ end
234
+ end
235
+
236
+ protected
237
+
238
+ def execute
239
+ raise "execute should be overwritten in subclass"
240
+ end
241
+
242
+ private
243
+
244
+ def reset_form_model
245
+ @_form_model = nil
246
+ end
247
+ end
248
+ end
249
+
@@ -0,0 +1,4 @@
1
+ module SeriousBusiness::Actor
2
+
3
+ end
4
+
@@ -0,0 +1,8 @@
1
+ # stores the relation between a model and an action affecting it
2
+ class SeriousBusiness::Affected < ApplicationRecord
3
+ self.table_name = 'serious_affecteds'
4
+ belongs_to :action, class_name: 'SeriousBusiness::Action', foreign_key: 'serious_action_id'
5
+ belongs_to :affected, polymorphic: true
6
+ end
7
+
8
+
@@ -0,0 +1,9 @@
1
+ module SeriousBusiness::AffectedByActions
2
+ extend ActiveSupport::Concern
3
+
4
+ included do |clazz|
5
+ clazz.has_many :serious_affecteds, as: :affected, class_name: 'SeriousBusiness::Affected'
6
+ clazz.has_many :modifiers, through: :serious_affecteds, source: :action
7
+
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module SeriousBusiness
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ module SeriousBusiness
2
+ class BaseFormModel
3
+ include ActiveModel::AttributeAssignment
4
+ include ActiveModel::Validations
5
+
6
+ def take_attributes_from(model, fields = nil)
7
+ fields ||= @_action.class.needed_attributes.map(&:to_s)
8
+ self.assign_attributes(model.attributes.slice(*fields))
9
+ end
10
+
11
+ def persisted?
12
+ # is set on instantiation
13
+ @_action.persisted?
14
+ end
15
+
16
+ def to_key
17
+ nil
18
+ end
19
+
20
+ def to_param
21
+ @_action.class.param_name
22
+ end
23
+
24
+ def attributes
25
+ @_action.class.needed_attributes.inject({}) do |sum, attr_name|
26
+ sum[attr_name] = self.instance_variable_get("@#{attr_name}")
27
+ sum
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Serious business</title>
5
+ <%= stylesheet_link_tag "serious_business/application", media: "all" %>
6
+ <%= javascript_include_tag "serious_business/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ SeriousBusiness::Engine.routes.draw do
2
+ end
@@ -0,0 +1,53 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module SeriousBusiness
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ source_root File.expand_path('../templates', __FILE__)
10
+
11
+ class_option :use_uuid, :optional => true, :type => :string, :banner => "use_uuid",
12
+ :desc => "Set to true if you're using uuid as primary key type"
13
+
14
+ # Copy the initializer file to config/initializers folder.
15
+ def copy_initializer_file
16
+ template "initializer.rb", "config/initializers/serious_business.rb"
17
+ end
18
+
19
+ def use_uuid
20
+ options.key? :use_uuid
21
+ end
22
+
23
+ # Copy the migrations files to db/migrate folder
24
+ def copy_migration_files
25
+ # Copy core migration file in all cases except when you pass --only-submodules.
26
+ return unless defined?(SeriousBusiness::Generators::InstallGenerator::ActiveRecord)
27
+ migration_template "migration/actions.rb", "db/migrate/create_serious_business.rb", migration_class_name: migration_class_name
28
+
29
+ end
30
+
31
+ # Define the next_migration_number method (necessary for the migration_template method to work)
32
+ def self.next_migration_number(dirname)
33
+ if ActiveRecord::Base.timestamped_migrations
34
+ sleep 1 # make sure each time we get a different timestamp
35
+ Time.new.utc.strftime("%Y%m%d%H%M%S")
36
+ else
37
+ "%.3d" % (current_migration_number(dirname) + 1)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def migration_class_name
44
+ if Rails::VERSION::MAJOR >= 5
45
+ "ActiveRecord::Migration[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
46
+ else
47
+ "ActiveRecord::Migration"
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,6 @@
1
+ require 'serious_business'
2
+
3
+ SeriousBusiness.config do |config|
4
+ config.actor_class_name = :User
5
+ end
6
+
@@ -0,0 +1,24 @@
1
+ class CreateSeriousBusiness < <%= migration_class_name %>
2
+ def change
3
+ create_table :serious_actions<%= ', id: :uuid' if use_uuid %> do |t|
4
+ t.string :type, null: false
5
+ <% if use_uuid %>
6
+ t.uuid :actor_id
7
+ <% else %>
8
+ t.integer :actor_id
9
+ <% end %>
10
+ t.timestamps null: false
11
+ end
12
+
13
+ create_table :serious_affecteds do |t|
14
+ t.references :serious_action, foreign_key: true, null: false
15
+ <% if use_uuid %>
16
+ t.uuid :affected_id, null: false
17
+ <% else %>
18
+ t.integer :affected_id, null: false
19
+ <% end %>
20
+ t.string :affected_type
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module SeriousBusiness
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace SeriousBusiness
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module SeriousBusiness
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,18 @@
1
+ require "serious_business/engine"
2
+ require "generators/serious_business/install_generator"
3
+ #require "i18n-dot_lookup"
4
+
5
+ module SeriousBusiness
6
+
7
+ SeriousBusinessConfig = Struct.new(:actor_class_name)
8
+
9
+ def self.config &blk
10
+ @config ||= SeriousBusinessConfig.new
11
+ blk.call(@config)
12
+ end
13
+
14
+ def self.actor_class_name
15
+ @config.actor_class_name
16
+ end
17
+
18
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :serious_business do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: serious_business
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Axel Tetzlaff
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Secure your app by using our structured data model to specify the data
56
+ flow between views and semnatic actions
57
+ email:
58
+ - axel.tetzlaff@gmx.de
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - MIT-LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - app/assets/config/serious_business_manifest.js
67
+ - app/assets/javascripts/serious_business/application.js
68
+ - app/assets/stylesheets/serious_business/application.css
69
+ - app/controllers/serious_business/application_controller.rb
70
+ - app/helpers/serious_business/application_helper.rb
71
+ - app/jobs/serious_business/application_job.rb
72
+ - app/mailers/serious_business/application_mailer.rb
73
+ - app/models/concerns/serious_business/actor.rb
74
+ - app/models/serious_business/action.rb
75
+ - app/models/serious_business/actor.rb
76
+ - app/models/serious_business/affected.rb
77
+ - app/models/serious_business/affected_by_actions.rb
78
+ - app/models/serious_business/application_record.rb
79
+ - app/models/serious_business/base_form_model.rb
80
+ - app/views/layouts/serious_business/application.html.erb
81
+ - config/routes.rb
82
+ - lib/generators/serious_business/install_generator.rb
83
+ - lib/generators/serious_business/templates/initializer.rb
84
+ - lib/generators/serious_business/templates/migration/actions.rb
85
+ - lib/serious_business.rb
86
+ - lib/serious_business/engine.rb
87
+ - lib/serious_business/version.rb
88
+ - lib/tasks/serious_business_tasks.rake
89
+ homepage: https://github.com/axelerator
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.6.8
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: A gem to formalize application flow of views and actions
113
+ test_files: []