delta_attributes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in delta_attributes.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 izbor
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # DeltaAttributes
2
+
3
+ This gem makes updating specified number fields by ActiveRecord in unusual way.
4
+
5
+ Instead to generate sql script to update value in usual way like this:
6
+
7
+ UPDATE users
8
+ SET money = 10
9
+ WHERE id = 1;
10
+
11
+ It replaces it with
12
+
13
+ UPDATE users
14
+ SET money = money + d
15
+ WHERE id = 1;
16
+
17
+ where d is difference between old value and new value of that field.
18
+
19
+ This solves problem with simultaneous updating of same field by different threads
20
+ without locking record.
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ gem 'delta_attributes'
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself as:
33
+
34
+ $ gem install delta_attributes
35
+
36
+ ## Usage
37
+
38
+ To mark numeric field to be updated by "field = field + d" way you just need to add field
39
+ name to delta_attributes like this:
40
+
41
+ class User < ActiveRecord::Base
42
+
43
+ delta_attributes :money
44
+
45
+ end
46
+
47
+ ## Contributing
48
+
49
+ 1. Fork it
50
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
51
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
52
+ 4. Push to the branch (`git push origin my-new-feature`)
53
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'delta_attributes/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "delta_attributes"
8
+ gem.version = DeltaAttributes::VERSION
9
+ gem.authors = ["izbor"]
10
+ gem.email = ["oleh.novosad@gmail.com"]
11
+ gem.description = %q{
12
+ This gem makes updating specified number fields by ActiveRecord in unusual way.
13
+
14
+ Instead to generate sql script to update value in usual way like this:
15
+
16
+ UPDATE users
17
+ SET money = 10
18
+ WHERE id = 1;
19
+
20
+ It replaces it with
21
+
22
+ UPDATE users
23
+ SET money = money + d
24
+ WHERE id = 1;
25
+
26
+ where d is difference between old value and new value of that field.
27
+
28
+ This solves problem with simultaneous updating of same field.
29
+ }
30
+ gem.summary = %q{ delta attributes }
31
+ gem.homepage = ""
32
+
33
+ gem.files = `git ls-files`.split($/)
34
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
35
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
36
+ gem.require_paths = ["lib"]
37
+ gem.add_dependency "rails", "~> 3.2.8"
38
+ end
@@ -0,0 +1,11 @@
1
+ module Arel
2
+ module Crud
3
+
4
+ def compile_full_update values, changed_values
5
+ um = compile_update values
6
+ um.set_changes changed_values unless changed_values.empty?
7
+ um
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,64 @@
1
+ require 'set'
2
+ require "delta_attributes/update_statement"
3
+ require "delta_attributes/update_manager"
4
+ require "delta_attributes/to_sql"
5
+ require "delta_attributes/mysql"
6
+ require "delta_attributes/crud"
7
+ require "delta_attributes/visitor"
8
+
9
+ module ActiveRecord
10
+ # = Active Record Persistence
11
+ module Persistence
12
+
13
+ def update(attribute_names = @attributes.keys)
14
+ return super(attribute_names) if self.new_record?
15
+
16
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
17
+ return 0 if attributes_with_values.empty?
18
+
19
+ attributes = { }
20
+ @changed_attributes.each do |attribute_name, value|
21
+ if self.class.delta_attributes.include?(attribute_name.intern) && !self.excluded_deltas.include?(attribute_name.intern)
22
+ attributes[attribute_name] = value
23
+ end
24
+ end
25
+
26
+ klass = self.class
27
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_full_update(attributes_with_values, attributes)
28
+ klass.connection.update stmt
29
+ end
30
+
31
+ def excluded_deltas
32
+ @_excluded_deltas ||= Set.new
33
+ end
34
+
35
+ def force_clobber(attr_name, value)
36
+ self.excluded_deltas.add(attr_name.to_s.intern)
37
+ send("#{attr_name}=", value)
38
+ end
39
+
40
+ class InvalidDeltaColumn < StandardError;
41
+ end
42
+
43
+ def self.included(base) #:nodoc:
44
+ base.extend ClassMethods
45
+ end
46
+
47
+ module ClassMethods
48
+
49
+ def delta_attributes(*args)
50
+ @_delta_attributes ||= Set.new
51
+
52
+ return @_delta_attributes if args.empty?
53
+
54
+ args.each do |attribute|
55
+ if self.columns_hash[attribute.to_s] && !self.columns_hash[attribute.to_s].number?
56
+ raise InvalidDeltaColumn.new("Delta attributes only work with number attributes, column `#{attribute}` is not a number.")
57
+ end
58
+ @_delta_attributes.add(attribute)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,30 @@
1
+ module Arel
2
+ module Visitors
3
+ class MySQL < Arel::Visitors::ToSql
4
+
5
+ private
6
+
7
+ def visit_Arel_Nodes_UpdateStatement o
8
+ [
9
+ "UPDATE #{visit o.relation}",
10
+ ("SET #{o.values_changed.map { |value| visit value }.join ', '}" unless o.values.empty?),
11
+ ("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?),
12
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
13
+ (visit(o.limit) if o.limit),
14
+ ].compact.join ' '
15
+ end
16
+
17
+ # original
18
+ #def visit_Arel_Nodes_UpdateStatement o
19
+ # [
20
+ # "UPDATE #{visit o.relation}",
21
+ # ("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
22
+ # ("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?),
23
+ # ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
24
+ # (visit(o.limit) if o.limit),
25
+ # ].compact.join ' '
26
+ #end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,8 @@
1
+
2
+ class DeltaAttributesRailtie < Rails::Railtie
3
+
4
+ initializer "delta_attributes.configure_rails_initialization", {:after => 1} do
5
+ load "delta_attributes/main.rb"
6
+ end
7
+
8
+ end
@@ -0,0 +1,25 @@
1
+ module Arel
2
+ module Visitors
3
+ class ToSql < Arel::Visitors::Visitor
4
+
5
+ def visit_Arel_Nodes_Assignment_with_deltas o
6
+ return visit_Arel_Nodes_Assignment_without_deltas o unless o.is_a?(Arel::Nodes::DeltaAttribute)
7
+ return visit_Arel_Nodes_Assignment_without_deltas o.arel_node unless o.valid?
8
+
9
+ old_value = o.old_value
10
+ new_value = o.new_value
11
+
12
+ delta_string = "#{visit o.arel_node.left} "
13
+ delta_string << (old_value > new_value ? "-" : "+")
14
+ delta_string << " #{(old_value - new_value).abs}"
15
+
16
+ "#{visit o.arel_node.left} = #{delta_string}"
17
+ end
18
+
19
+ #alias_method_chain :visit_Arel_Nodes_Assignment, :deltas
20
+
21
+ alias_method :visit_Arel_Nodes_Assignment_without_deltas, :visit_Arel_Nodes_Assignment
22
+ alias_method :visit_Arel_Nodes_Assignment, :visit_Arel_Nodes_Assignment_with_deltas
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module Arel
2
+
3
+ module UpdateManagerExt
4
+
5
+ def set_changes values
6
+ @ast.values_changed = values
7
+ end
8
+ end
9
+
10
+ class UpdateManager
11
+ include Arel::UpdateManagerExt
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ module Arel
2
+ module Nodes
3
+
4
+ class DeltaAttribute
5
+
6
+ attr_accessor :arel_node, :new_value, :old_value
7
+
8
+ def initialize(arel_node, new_value = nil, old_value = nil)
9
+ @arel_node = arel_node
10
+ @new_value = new_value
11
+ @old_value = old_value
12
+ end
13
+
14
+ def valid?
15
+ @arel_node && !new_value.nil? && !old_value.nil?
16
+ end
17
+
18
+ end
19
+
20
+ module UpdateStatementExt
21
+
22
+ def values_changed=(values)
23
+ instance_variable_set("@values_changed", values)
24
+ end
25
+
26
+ def values_changed
27
+ changed = instance_variable_get("@values_changed")
28
+ return @values unless changed
29
+
30
+ @values.map {|m|
31
+ attr_name = m.left.expr.name
32
+ new_value = m.right
33
+ if changed[attr_name]
34
+ Arel::Nodes::DeltaAttribute.new(m, new_value, changed[attr_name])
35
+ else
36
+ m
37
+ end
38
+ }
39
+ end
40
+
41
+ end
42
+
43
+ class UpdateStatement
44
+ include Arel::Nodes::UpdateStatementExt
45
+ end
46
+
47
+ end
48
+
49
+
50
+ end
@@ -0,0 +1,3 @@
1
+ module DeltaAttributes
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ module Arel
2
+ module Visitors
3
+ class Visitor
4
+
5
+ def visit_with_delta_attribute obj
6
+ return visit_without_delta_attribute obj unless obj.is_a?(Arel::Nodes::DeltaAttribute)
7
+ return visit_without_delta_attribute obj.arel_node unless obj.valid?
8
+
9
+ object = obj.arel_node
10
+ send dispatch[object.class], obj
11
+ rescue NoMethodError => e
12
+ raise e if respond_to?(dispatch[object.class], true)
13
+ superklass = object.class.ancestors.find { |klass|
14
+ respond_to?(dispatch[klass], true)
15
+ }
16
+ raise(TypeError, "Cannot visit #{object.class}") unless superklass
17
+ dispatch[object.class] = dispatch[superklass]
18
+ retry
19
+ end
20
+
21
+ #alias_method_chain :visit, :delta_attribute
22
+
23
+ alias_method :visit_without_delta_attribute, :visit
24
+ alias_method :visit, :visit_with_delta_attribute
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,2 @@
1
+ require "delta_attributes/version"
2
+ require 'delta_attributes/railtie' if defined?(Rails)
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delta_attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - izbor
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.8
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.2.8
30
+ description: ! "\n This gem makes updating specified number fields by ActiveRecord
31
+ in unusual way.\n\n Instead to generate sql script to update value in usual way
32
+ like this:\n\n UPDATE users\n SET money = 10\n WHERE id = 1;\n\n
33
+ \ It replaces it with\n\n UPDATE users\n SET money = money + d\n WHERE
34
+ id = 1;\n\n where d is difference between old value and new value of that field.\n\n
35
+ \ This solves problem with simultaneous updating of same field.\n "
36
+ email:
37
+ - oleh.novosad@gmail.com
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - .gitignore
43
+ - Gemfile
44
+ - LICENSE.txt
45
+ - README.md
46
+ - Rakefile
47
+ - delta_attributes.gemspec
48
+ - lib/delta_attributes.rb
49
+ - lib/delta_attributes/crud.rb
50
+ - lib/delta_attributes/main.rb
51
+ - lib/delta_attributes/mysql.rb
52
+ - lib/delta_attributes/railtie.rb
53
+ - lib/delta_attributes/to_sql.rb
54
+ - lib/delta_attributes/update_manager.rb
55
+ - lib/delta_attributes/update_statement.rb
56
+ - lib/delta_attributes/version.rb
57
+ - lib/delta_attributes/visitor.rb
58
+ homepage: ''
59
+ licenses: []
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 1.8.24
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: delta attributes
82
+ test_files: []