plumber 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in plumber.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "rake"
8
+ end
@@ -0,0 +1,3 @@
1
+ # Plumber
2
+
3
+ A library for connecting external data sources to local ActiveRecord models.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,49 @@
1
+ require "plumber/version"
2
+ require "plumber/context"
3
+
4
+ require "active_record"
5
+
6
+ # Provides functionality for creating and updating ActiveRecord model objects. Requires Rails 3+.
7
+ class Plumber
8
+ # Create a new instance which will act on the given model and log to the given logger.
9
+ def initialize(model, logger)
10
+ @model = model
11
+ @logger = logger
12
+ end
13
+
14
+ def import
15
+ context = Plumber::Context.new(@model.scoped)
16
+
17
+ yield context
18
+
19
+ operation = if context.record.new_record?
20
+ "Create"
21
+ else
22
+ "Update"
23
+ end
24
+
25
+ changed = context.record.changed
26
+
27
+ begin
28
+ ActiveRecord::Base.transaction do
29
+ if context.record.save!
30
+ if changed
31
+ @logger.info("#{operation} #{context.record.class} #{context.record.id}.")
32
+ else
33
+ @logger.debug("#{context.record.class} #{context.record.id} not changed.")
34
+ end
35
+ end
36
+ end
37
+ rescue ActiveRecord::RecordInvalid => e
38
+ prefix = "#{e.record.class} #{e.record.id}".strip
39
+
40
+ @logger.warn("#{prefix}: #{operation} failed - #{e.record.errors.size} validation errors.")
41
+
42
+ context.record.errors.full_messages.each do |message|
43
+ @logger.warn("#{prefix}: #{message}")
44
+ end
45
+ end
46
+
47
+ context.record
48
+ end
49
+ end
@@ -0,0 +1,75 @@
1
+ class Plumber
2
+ class Context
3
+ MAX_ASSOCIATION_COUNT = 50
4
+
5
+ attr_reader :scope, :conditions, :mapping, :associations
6
+
7
+ def initialize(scope, parent = nil)
8
+ @scope = scope
9
+ @parent = parent
10
+ @conditions = {}
11
+ @mapping = {}
12
+ @associations = []
13
+ end
14
+
15
+ def conditions(hash)
16
+ @conditions = hash
17
+ end
18
+
19
+ def mapping(hash)
20
+ @mapping = hash
21
+ end
22
+
23
+ def association(name)
24
+ scope = record.send(name)
25
+ context = self.class.new(scope, self)
26
+
27
+ yield context
28
+
29
+ @associations << context
30
+
31
+ # Return the record defined in the yielded context:
32
+ context.record
33
+ end
34
+
35
+ def record
36
+ if @record.nil?
37
+ @record = if @parent.nil?
38
+ # If this is a top-level context, use a database query to look up an
39
+ # existing record that matches the conditions supplied.
40
+ @scope.where(@conditions).first
41
+ else
42
+ # Because ActiveRecord (currently) doesn't have an "Identity Map" feature,
43
+ # unfortunately for associated records we can't run a DB query to find
44
+ # existing records.
45
+ #
46
+ # Instead we load all records in the scope and search for one matching
47
+ # the conditions supplied. This is why we check the scope size and raise
48
+ # an exception if it's too large - for situations where there are
49
+ # potentially large numbers of associated records, a separate Importer
50
+ # instance should be defined for handling those records.
51
+ if @scope.count > MAX_ASSOCIATION_COUNT
52
+ message = "%s scope contains too many records (%s)" % [
53
+ @scope.new.class, @scope.count
54
+ ]
55
+
56
+ raise RuntimeError, message
57
+ end
58
+
59
+ @scope.detect do |record|
60
+ @conditions.all? { |k, v| record.send(k) == v }
61
+ end
62
+ end
63
+
64
+ # If an existing record hasn't been found in the scope, build a new one:
65
+ @record ||= @scope.build
66
+
67
+ @conditions.merge(@mapping).each do |key, value|
68
+ @record.send("#{key}=", value)
69
+ end
70
+ end
71
+
72
+ @record
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ class Plumber
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "plumber/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "plumber"
7
+ s.version = Plumber::VERSION
8
+ s.authors = ["Nick Dainty"]
9
+ s.email = ["nick@npad.co.uk"]
10
+ s.homepage = "https://github.com/nickpad/plumber"
11
+ s.summary = %q{Connect anything to an ActiveRecord model}
12
+ s.description = %q{Plumber is a library for creating and updating ActiveRecord models from external data}
13
+
14
+ s.rubyforge_project = "plumber"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "activerecord", ">= 3.0.0"
24
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plumber
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nick Dainty
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: &70177976806580 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70177976806580
25
+ description: Plumber is a library for creating and updating ActiveRecord models from
26
+ external data
27
+ email:
28
+ - nick@npad.co.uk
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - .gitignore
34
+ - Gemfile
35
+ - README.markdown
36
+ - Rakefile
37
+ - lib/plumber.rb
38
+ - lib/plumber/context.rb
39
+ - lib/plumber/version.rb
40
+ - plumber.gemspec
41
+ homepage: https://github.com/nickpad/plumber
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project: plumber
61
+ rubygems_version: 1.8.11
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Connect anything to an ActiveRecord model
65
+ test_files: []