-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
391 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
Description: | ||
Generates the necessary migration to enable field tracking for logux updates | ||
|
||
Examples: | ||
rails generate logux:model User | ||
|
||
This will generate the migration to add a column with update time data. | ||
|
||
rails generate logux:model User --nullable | ||
|
||
This will generate the migration to add a column with update time data and add `null: false` constraint. Be careful, adding the constraint to the table with a big number of rows can cause lots of locks. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails/generators' | ||
require 'rails/generators/active_record/migration/migration_generator' | ||
|
||
module Logux | ||
module Generators | ||
class ModelGenerator < ::ActiveRecord::Generators::Base # :nodoc: | ||
source_root File.expand_path('templates', __dir__) | ||
|
||
class_option :nullable, | ||
type: :boolean, | ||
optional: true, | ||
desc: 'Define whether field should have not-null constraint' | ||
|
||
def generate_migration | ||
migration_template( | ||
'migration.rb.erb', | ||
"db/migrate/add_logux_fields_updated_at_to_#{plural_table_name}.rb" | ||
) | ||
end | ||
|
||
def nullable? | ||
options.fetch(:nullable, false) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
class <%= @migration_class_name %> < ActiveRecord::Migration<%= ActiveRecord::VERSION::MAJOR < 5 ? '' : '[5.0]' %> | ||
def up | ||
<% if nullable? %> | ||
add_column :<%= plural_table_name %>, :logux_fields_updated_at, :jsonb, null: false, default: {} | ||
<% else %> | ||
add_column :<%= plural_table_name %>, :logux_fields_updated_at, :jsonb, null: true | ||
change_column_default :<%= plural_table_name %>, :logux_fields_updated_at, {} | ||
<% end %> | ||
end | ||
|
||
def down | ||
remove_column :<%= plural_table_name %>, :logux_fields_updated_at | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative 'model/updater' | ||
require_relative 'model/proxy' | ||
require_relative 'model/dsl' | ||
|
||
module Logux | ||
module Model | ||
class InsecureUpdateError < StandardError; end | ||
|
||
def self.included(base) | ||
base.extend(DSL) | ||
|
||
base.before_save :update_logux_id | ||
end | ||
|
||
def logux | ||
Proxy.new(self) | ||
end | ||
|
||
private | ||
|
||
def update_logux_id | ||
return if changes.key?('logux_fields_updated_at') | ||
|
||
attributes = changed.each_with_object({}) do |attr, res| | ||
res[attr] = send(attr) | ||
end | ||
|
||
updater = Updater.new(model: self, attributes: attributes) | ||
self.logux_fields_updated_at = updater.updated_attributes | ||
|
||
trigger_insecure_update_notification | ||
end | ||
|
||
def trigger_insecure_update_notification | ||
ActiveSupport::Notifications.instrument( | ||
'logux.insecure_update', | ||
model_class: self.class, | ||
attributes: (changed - ['logux_fields_updated_at']).map(&:to_sym) | ||
) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# frozen_string_literal: true | ||
|
||
module Logux | ||
module Model | ||
module DSL | ||
def logux_crdt_map_attributes(*attributes) | ||
@logux_crdt_mapped_attributes = attributes | ||
end | ||
|
||
def logux_crdt_mapped_attributes | ||
@logux_crdt_mapped_attributes ||= [] | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# frozen_string_literal: true | ||
|
||
module Logux | ||
module Model | ||
class InsecureUpdateSubscriber | ||
# rubocop:disable Naming/UncommunicativeMethodParamName | ||
def call(_, _, _, _, args) | ||
model_class = args[:model_class] | ||
attributes = args[:attributes] | ||
|
||
logux_attributes = | ||
attributes & model_class.logux_crdt_mapped_attributes | ||
return if logux_attributes.empty? | ||
|
||
pluralized_attributes = 'attribute'.pluralize(logux_attributes.count) | ||
|
||
raise InsecureUpdateError, <<~TEXT | ||
Logux tracked #{pluralized_attributes} (#{logux_attributes.join(', ')}) should be updated using model.logux.update(...) | ||
TEXT | ||
end | ||
# rubocop:enable Naming/UncommunicativeMethodParamName | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# frozen_string_literal: true | ||
|
||
module Logux | ||
module Model | ||
class Proxy | ||
def initialize(model) | ||
@model = model | ||
end | ||
|
||
def update(meta, attributes) | ||
updater = Updater.new( | ||
model: @model, | ||
logux_id: meta.logux_id, | ||
attributes: attributes | ||
) | ||
@model.update_attributes(updater.updated_attributes) | ||
end | ||
|
||
def updated_at(field) | ||
@model.logux_fields_updated_at[field.to_s] | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# frozen_string_literal: true | ||
|
||
module Logux | ||
module Model | ||
class Updater | ||
def initialize(model:, attributes:, logux_id: Logux.generate_action_id) | ||
@model = model | ||
@logux_id = logux_id | ||
@attributes = attributes | ||
end | ||
|
||
def updated_attributes | ||
newer_updates.merge(logux_fields_updated_at: fields_updated_at) | ||
end | ||
|
||
private | ||
|
||
def fields_updated_at | ||
@fields_updated_at ||= | ||
newer_updates.slice(*tracked_fields) | ||
.keys | ||
.reduce(@model.logux_fields_updated_at) do |acc, attr| | ||
acc.merge(attr => @logux_id) | ||
end | ||
end | ||
|
||
def newer_updates | ||
@newer_updates ||= @attributes.reject do |attr, _| | ||
field_updated_at = @model.logux.updated_at(attr) | ||
field_updated_at && field_updated_at > @logux_id | ||
end | ||
end | ||
|
||
def tracked_fields | ||
@model.class.logux_crdt_mapped_attributes | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# frozen_string_literal: true | ||
|
||
class Post < ActiveRecord::Base | ||
include Logux::Model | ||
|
||
logux_crdt_map_attributes :title, :content | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
class CreatePosts < ActiveRecord::Migration[5.2] | ||
def change | ||
create_table :posts do |t| | ||
t.string :title | ||
t.text :content | ||
|
||
t.timestamps | ||
end | ||
end | ||
end |
10 changes: 10 additions & 0 deletions
10
spec/dummy/db/migrate/20181101194822_add_logux_fields_updated_at_to_posts.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
class AddLoguxFieldsUpdatedAtToPosts < ActiveRecord::Migration[5.2] | ||
def up | ||
add_column :posts, :logux_fields_updated_at, :jsonb, null: true | ||
change_column_default :posts, :logux_fields_updated_at, {} | ||
end | ||
|
||
def down | ||
remove_column :posts, :logux_fields_updated_at | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# frozen_string_literal: true | ||
|
||
FactoryBot.define do | ||
factory :post, class: Post do | ||
title { 'initial' } | ||
content { 'initial' } | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.