delay_henka 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: 8976109f2133f416e0e81dbab3c03e101e100dff
4
+ data.tar.gz: b6a2fc83de719b7a333aa4b7fdb4d584af0c5b82
5
+ SHA512:
6
+ metadata.gz: 2ea718d1951b2bed9ae1b852567aa8668a56ad02d2c04c2f7d36670444906b2c52cdfafb959fa32329ac47606065e619bdcaf8eaf82cb3b506caeb95beda2697
7
+ data.tar.gz: 27be8e5efb7253009ad6d4c7e2ebdc6b69b3436547d5e56cbf420c790d86e4d24ee1a47331fff3c807902d2fa1f5e7905983afade71ce01a0182a7f2c6cb4acc
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2018 zino
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,87 @@
1
+ # Delay Henka
2
+
3
+ 'Henka' is Japanese for 'change'.
4
+
5
+ `DelayHenka` is a Rails engine for managing delayed update of attributes. This is built to ensure that important attributes (such as price of a product, for instance) can be updated anytime but will not impact users until a specific time later in the day (maybe 2am in the morning, when it's safe to apply those changes).
6
+
7
+ Granted, there're other strategies with higher level of integrity (such as versioning the resources). This implementation is merely a low-cost solution that solves the problem to a reasonable extent.
8
+
9
+ ## Usage
10
+
11
+ In the including application,
12
+ ```ruby
13
+ # copy over migrations
14
+ `bundle exec rails delay_henka:install:migrations`
15
+ `bundle exec rails db:migrate`
16
+
17
+ # config/routes.rb
18
+ mount DelayHenka::Engine, at: '/delay_henka'
19
+
20
+ # Create an initilazer delay_henka.rb
21
+ DelayHenka.setup do |config|
22
+ # Sets the base controller of the engine views
23
+ config.base_view_controller = 'Backend::BaseController'
24
+ end
25
+
26
+ # In your model that you want to have delayed updates,
27
+ class SomeModel < ApplicationRecord
28
+ # This gives instances of this class `#upcoming_changes` association with staged ScheduledChange
29
+ include DelayHenka::Model
30
+ end
31
+
32
+ # In your controller (or factory service),
33
+ class SomeController
34
+ def update
35
+ product = SomeModel.find(params[:id])
36
+ delayed_changes = some_params.extract!(:discount_pct, :price)
37
+ if product.update some_params.except(:discount_pct, :price)
38
+ DelayHenka::ScheduledChange.schedule(record: product, changes: delayed_changes, by_id: current_user.id)
39
+ redirect_to #..., success
40
+ else
41
+ render :edit
42
+ end
43
+ end
44
+ end
45
+
46
+ # To view scheduled changes associated with a record,
47
+ # some_view.html.haml
48
+ - if @product.upcoming_changes.any?
49
+ = render 'delay_henka/web/admin/scheduled_changes/summary_table', scheduled_changes: @product.upcoming_changes
50
+
51
+ # To view all scheduled changes, use this link:
52
+ delay_henka.web_admin_scheduled_changes_path
53
+
54
+ # To schedule the worker that applies all changes,
55
+ # in your sidekiq config/schedule.yml,
56
+ apply_scheduled_changes:
57
+ cron: "0 1 * * *"
58
+ class: DelayHenka::ApplyChangesWorker
59
+ queue: default
60
+ ```
61
+
62
+ ### States of ScheduledChange
63
+
64
+ * STAGED: initial state
65
+ * REPLACED: replaced by another record updating the same attribute
66
+ * COMPLETED: change is applied successfully
67
+ * ERRORED: change failed to be applied
68
+
69
+ ## Installation
70
+ Add this line to your application's Gemfile:
71
+
72
+ ```ruby
73
+ gem 'delay_henka'
74
+ ```
75
+
76
+ And then execute:
77
+ ```bash
78
+ $ bundle
79
+ ```
80
+
81
+ ## Development
82
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
83
+
84
+ Note that a mock AR class - `Foo` - is created dynamically in rails_helper.rb for testing.
85
+
86
+ ## License
87
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,32 @@
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 = 'DelayHenka'
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", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/delay_henka .js
2
+ //= link_directory ../stylesheets/delay_henka .css
@@ -0,0 +1,15 @@
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 rails-ujs
14
+ //= require activestorage
15
+ //= 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 DelayHenka
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ module DelayHenka
2
+ module Web
3
+ module Admin
4
+ class ScheduledChangesController < DelayHenka.base_view_controller.constantize
5
+
6
+ before_action :set_scheduled_change, only: %i(destroy)
7
+
8
+ def index
9
+ @changes = ScheduledChange.order('created_at DESC')
10
+ end
11
+
12
+ def destroy
13
+ @change.destroy!
14
+ redirect_back fallback_location: web_admin_scheduled_changes_path, flash: {success: 'Destroy succeeded'}
15
+ end
16
+
17
+ private
18
+
19
+ def set_scheduled_change
20
+ @change = ScheduledChange.find(params[:id])
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ module DelayHenka
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module DelayHenka
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,46 @@
1
+ module DelayHenka
2
+ class ScheduledChange < ApplicationRecord
3
+
4
+ STATES = {
5
+ STAGED: 'staged',
6
+ REPLACED: 'replaced',
7
+ COMPLETED: 'completed',
8
+ ERRORED: 'errored'
9
+ }
10
+
11
+ belongs_to :changeable, polymorphic: true
12
+
13
+ validates :submitted_by_id, :attribute_name, presence: true
14
+ validates :state, inclusion: { in: STATES.values }
15
+ after_initialize :set_initial_state, if: :new_record?
16
+
17
+ scope :staged, -> { where(state: STATES[:STAGED]) }
18
+
19
+ def self.schedule(record:, changes:, by_id:)
20
+ changes.each do |attribute, new_val|
21
+ old_val = record.public_send(attribute)
22
+ next if old_val == new_val
23
+ create!(changeable: record, submitted_by_id: by_id, attribute_name: attribute, old_value: old_val, new_value: new_val)
24
+ end
25
+ end
26
+
27
+ def apply_change
28
+ if changeable.update(attribute_name => new_value)
29
+ update(state: STATES[:COMPLETED])
30
+ else
31
+ update(state: STATES[:ERRORED], error_message: changeable.errors.full_messages.join(', '))
32
+ end
33
+ end
34
+
35
+ def replace_change
36
+ update(state: STATES[:REPLACED])
37
+ end
38
+
39
+ private
40
+
41
+ def set_initial_state
42
+ self.state ||= STATES[:STAGED]
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,20 @@
1
+ -# This partial is rendered by including application.
2
+ -# Required local variables:
3
+ -# * scheduled_changes
4
+ -# To render this partial:
5
+ -# = render 'delay_henka/web/admin/scheduled_changes/summary_table', scheduled_changes: @changes
6
+
7
+ %table.table.table-bordered
8
+ %thead
9
+ %tr
10
+ %th Attribute
11
+ %th Old Value
12
+ %th New Value
13
+ %th Action
14
+ %tbody
15
+ - scheduled_changes.each do |change|
16
+ %tr
17
+ %td= change.attribute_name.humanize
18
+ %td= change.old_value
19
+ %td= change.new_value
20
+ %td= link_to 'Delete', delay_henka.web_admin_scheduled_change_path(change), method: :delete
@@ -0,0 +1,21 @@
1
+ %h1 Scheduled Changes
2
+ %table.table
3
+ %thead
4
+ %tr
5
+ %th Type
6
+ %th Id
7
+ %th Attribute
8
+ %th State
9
+ %th Old Value
10
+ %th New Value
11
+ %th Submitted By
12
+ %tbody
13
+ - @changes.each do |change|
14
+ %tr
15
+ %td= change.changeable_type
16
+ %td= change.changeable_id
17
+ %td= change.attribute_name
18
+ %td= change.state
19
+ %td= change.old_value
20
+ %td= change.new_value
21
+ %td= change.submitted_by_id
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Delay henka</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "delay_henka/application", media: "all" %>
9
+ <%= javascript_include_tag "delay_henka/application" %>
10
+ </head>
11
+ <body>
12
+
13
+ <%= yield %>
14
+
15
+ </body>
16
+ </html>
@@ -0,0 +1,19 @@
1
+ module DelayHenka
2
+ class ApplyChangesWorker
3
+
4
+ include Sidekiq::Worker
5
+
6
+ def perform
7
+ ScheduledChange.staged
8
+ .includes(:changeable)
9
+ .group_by{ |change| [change.changeable_type, change.changeable_id, change.attribute_name] }
10
+ .values
11
+ .each do |changes_for_attribute|
12
+ latest_change = changes_for_attribute.sort_by(&:created_at).last
13
+ (changes_for_attribute - [latest_change]).each(&:replace_change)
14
+ latest_change.apply_change
15
+ end
16
+ end
17
+
18
+ end
19
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ DelayHenka::Engine.routes.draw do
2
+ namespace :web do
3
+ namespace :admin do
4
+ resources :scheduled_changes, only: %i(index destroy)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ class CreateDelayHenkaScheduledChanges < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :delay_henka_scheduled_changes do |t|
4
+ t.string :changeable_type, null: false
5
+ t.integer :changeable_id, null: false
6
+ t.string :attribute_name, null: false
7
+ t.integer :submitted_by_id, null: false
8
+ t.string :state, null: false
9
+ t.text :error_message
10
+ t.jsonb :old_value
11
+ t.jsonb :new_value
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module DelayHenka
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DelayHenka
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module DelayHenka
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,24 @@
1
+ require "delay_henka/engine"
2
+ require 'haml'
3
+ require 'pry'
4
+
5
+ module DelayHenka
6
+
7
+ module Model
8
+ extend ActiveSupport::Concern
9
+ included do
10
+ has_many :upcoming_changes,
11
+ ->{ staged.order('created_at DESC') },
12
+ class_name: 'DelayHenka::ScheduledChange',
13
+ as: :changeable
14
+ end
15
+ end
16
+
17
+ mattr_accessor :base_view_controller
18
+ @@base_view_controller = 'DelayHenka::ApplicationController'
19
+
20
+ def self.setup
21
+ yield self
22
+ end
23
+
24
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :delay_henka do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delay_henka
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - zino
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-02 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.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: haml
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 5.0.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: sidekiq
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 5.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 5.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: pg
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 3.7.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 3.7.2
97
+ description: ActiveRecord-based engine for scheduled changes
98
+ email:
99
+ - rhu5@u.rochester.edu
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - MIT-LICENSE
105
+ - README.md
106
+ - Rakefile
107
+ - app/assets/config/delay_henka_manifest.js
108
+ - app/assets/javascripts/delay_henka/application.js
109
+ - app/assets/stylesheets/delay_henka/application.css
110
+ - app/controllers/delay_henka/application_controller.rb
111
+ - app/controllers/delay_henka/web/admin/scheduled_changes_controller.rb
112
+ - app/helpers/delay_henka/application_helper.rb
113
+ - app/models/delay_henka/application_record.rb
114
+ - app/models/delay_henka/scheduled_change.rb
115
+ - app/views/delay_henka/web/admin/scheduled_changes/_summary_table.html.haml
116
+ - app/views/delay_henka/web/admin/scheduled_changes/index.html.haml
117
+ - app/views/layouts/delay_henka/application.html.erb
118
+ - app/workers/delay_henka/apply_changes_worker.rb
119
+ - config/routes.rb
120
+ - db/migrate/20181228205218_create_delay_henka_scheduled_changes.rb
121
+ - lib/delay_henka.rb
122
+ - lib/delay_henka/engine.rb
123
+ - lib/delay_henka/version.rb
124
+ - lib/tasks/delay_henka_tasks.rake
125
+ homepage: https://github.com/zinosama/delay_henka
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.6.14
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Rails engine for scheduled changes
149
+ test_files: []