rd3 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +4 -0
- data/.rvmrc +71 -0
- data/.simplecov +5 -0
- data/Gemfile +22 -0
- data/LICENSE.txt +20 -0
- data/README.md +4 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/rd3.rb +32 -0
- data/lib/rd3/configuration.rb +5 -0
- data/lib/rd3/data_providers/azure_table_storage.rb +67 -0
- data/lib/rd3/data_providers/rdbms.rb +62 -0
- data/lib/rd3/model.rb +108 -0
- data/lib/rd3/railtie.rb +7 -0
- data/lib/rd3/repository.rb +61 -0
- data/rd3.gemspec +102 -0
- data/spec/config/azure_table_storage.yml +3 -0
- data/spec/config/mysql.yml +7 -0
- data/spec/config/postgresql.yml +7 -0
- data/spec/lib/rd3/data_providers/azure_table_storage_spec.rb +30 -0
- data/spec/lib/rd3/data_providers/rdbms_spec.rb +41 -0
- data/spec/lib/rd3/model_spec.rb +158 -0
- data/spec/lib/rd3/repository_spec.rb +65 -0
- data/spec/lib/rd3_spec.rb +18 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/data_provider_shared_examples.rb +131 -0
- data/spec/support/rdbms_shared_context.rb +29 -0
- metadata +286 -0
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
+
environment_id="ruby-1.9.3@rd3"
|
8
|
+
|
9
|
+
#
|
10
|
+
# Uncomment following line if you want options to be set only for given project.
|
11
|
+
#
|
12
|
+
# PROJECT_JRUBY_OPTS=( --1.9 )
|
13
|
+
#
|
14
|
+
# The variable PROJECT_JRUBY_OPTS requires the following to be run in shell:
|
15
|
+
#
|
16
|
+
# chmod +x ${rvm_path}/hooks/after_use_jruby_opts
|
17
|
+
#
|
18
|
+
|
19
|
+
#
|
20
|
+
# First we attempt to load the desired environment directly from the environment
|
21
|
+
# file. This is very fast and efficient compared to running through the entire
|
22
|
+
# CLI and selector. If you want feedback on which environment was used then
|
23
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
24
|
+
#
|
25
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
26
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
27
|
+
then
|
28
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
29
|
+
|
30
|
+
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
31
|
+
then
|
32
|
+
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
33
|
+
fi
|
34
|
+
else
|
35
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
36
|
+
if ! rvm --create "$environment_id"
|
37
|
+
then
|
38
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
39
|
+
return 1
|
40
|
+
fi
|
41
|
+
fi
|
42
|
+
|
43
|
+
#
|
44
|
+
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
45
|
+
# it be automatically loaded. Uncomment the following and adjust the filename if
|
46
|
+
# necessary.
|
47
|
+
#
|
48
|
+
# filename=".gems"
|
49
|
+
# if [[ -s "$filename" ]]
|
50
|
+
# then
|
51
|
+
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
52
|
+
# fi
|
53
|
+
|
54
|
+
# If you use bundler, this might be useful to you:
|
55
|
+
# if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
|
56
|
+
# then
|
57
|
+
# printf "The rubygem 'bundler' is not installed. Installing it now.\n"
|
58
|
+
# gem install bundler
|
59
|
+
# fi
|
60
|
+
# if [[ -s Gemfile ]] && command -v bundle
|
61
|
+
# then
|
62
|
+
# bundle install
|
63
|
+
# fi
|
64
|
+
|
65
|
+
if [[ $- == *i* ]] # check for interactive shells
|
66
|
+
then
|
67
|
+
echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
|
68
|
+
else
|
69
|
+
echo "Using: $GEM_HOME" # don't use colors in interactive shells
|
70
|
+
fi
|
71
|
+
|
data/.simplecov
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
gem 'activesupport'
|
6
|
+
gem 'activemodel'
|
7
|
+
gem 'require_all'
|
8
|
+
|
9
|
+
# Add dependencies to develop your gem here.
|
10
|
+
# Include everything needed to run rake, tests, features, etc.
|
11
|
+
group :development, :test do
|
12
|
+
gem "rdoc", ">= 3.12"
|
13
|
+
gem "bundler", ">= 1.0.0"
|
14
|
+
gem "jeweler", ">= 1.8.4"
|
15
|
+
gem "rspec", ">= 2.12.0"
|
16
|
+
gem "simplecov", ">= 0.7.1"
|
17
|
+
gem "mysql2", ">= 0.3.11"
|
18
|
+
gem "pg", ">= 0.14.1"
|
19
|
+
gem "sequel", ">= 3.41.0"
|
20
|
+
gem "waz-storage", ">= 1.1.4"
|
21
|
+
gem "database_cleaner", ">= 0.9.1"
|
22
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 obie quelland
|
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
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "rd3"
|
18
|
+
gem.homepage = "http://github.com/obieq/rd3"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{RD3 is a ruby gem that adheres to and facilitates domain driven design principles}
|
21
|
+
gem.description = %Q{ enable domain driven design approach via POROs and Repositories vs Rails' ActiveRecord }
|
22
|
+
gem.email = "quelland@gmail.com"
|
23
|
+
gem.authors = ["obie quelland"]
|
24
|
+
end
|
25
|
+
Jeweler::RubygemsDotOrgTasks.new
|
26
|
+
|
27
|
+
require 'rdoc/task'
|
28
|
+
Rake::RDocTask.new do |rdoc|
|
29
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
30
|
+
|
31
|
+
rdoc.rdoc_dir = 'rdoc'
|
32
|
+
rdoc.title = "rd3 #{version}"
|
33
|
+
rdoc.rdoc_files.include('README*')
|
34
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get spec rake tasks working in RSpec 2.0
|
38
|
+
require 'rspec/core/rake_task'
|
39
|
+
|
40
|
+
desc 'Default: run specs.'
|
41
|
+
task :default => :spec
|
42
|
+
|
43
|
+
desc "Run specs"
|
44
|
+
RSpec::Core::RakeTask.new do |t|
|
45
|
+
end
|
46
|
+
|
47
|
+
RSpec::Core::RakeTask.new(:fast) do |t|
|
48
|
+
t.rspec_opts = '--tag ~speed:slow'
|
49
|
+
end
|
50
|
+
|
51
|
+
RSpec::Core::RakeTask.new(:slow) do |t|
|
52
|
+
t.rspec_opts = '--tag speed:slow'
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/rd3.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_model'
|
3
|
+
require 'require_all'
|
4
|
+
|
5
|
+
require 'rd3/model'
|
6
|
+
require 'rd3/repository'
|
7
|
+
require 'rd3/configuration'
|
8
|
+
require_rel '../lib/rd3/data_providers'
|
9
|
+
require 'rd3/railtie' if defined?(Rails)
|
10
|
+
#require 'active_support'
|
11
|
+
#require 'active_model'
|
12
|
+
|
13
|
+
#require 'rd3/railtie' if defined?(Rails)
|
14
|
+
#require_rel '../lib' #.reject {|f| railtie.rb}
|
15
|
+
|
16
|
+
module RD3
|
17
|
+
class << self
|
18
|
+
attr_reader :config
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.configure(args)
|
22
|
+
configuration = nil
|
23
|
+
unless args.nil? || args.empty?
|
24
|
+
configuration = RD3::Configuration.new
|
25
|
+
configuration.environment = args[:environment]
|
26
|
+
configuration.config_file_directory = args[:config_file_directory]
|
27
|
+
configuration.enable_logical_deletes = args[:enable_logical_deletes]
|
28
|
+
end
|
29
|
+
@config = configuration
|
30
|
+
#@config ||= configuration
|
31
|
+
end
|
32
|
+
end # module RD3
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'waz-storage'
|
3
|
+
require 'waz-tables'
|
4
|
+
|
5
|
+
module RD3
|
6
|
+
module DataProviders
|
7
|
+
module AzureTableStorage
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
def self.client
|
12
|
+
return @client if @client
|
13
|
+
|
14
|
+
yml_config = YAML.load(File.read("#{RD3.config.config_file_directory}/#{self.config_file_name}.yml"))[RD3.config.environment.to_s]
|
15
|
+
yml_config = yml_config.symbolize_keys
|
16
|
+
|
17
|
+
WAZ::Storage::Base.establish_connection!(:account_name => yml_config[:storage_account_name],
|
18
|
+
:access_key => yml_config[:primary_access_key])
|
19
|
+
@client = WAZ::Tables::Table.service_instance
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.save(attributes)
|
23
|
+
# convert the key attribute to partition key
|
24
|
+
# NOTE: don't overwrite the partion key if it has already been set
|
25
|
+
attributes[:partition_key] ||= partition_key = attributes.delete(:key)
|
26
|
+
|
27
|
+
unless attributes[:Timestamp]
|
28
|
+
self.client.insert_entity(self.table_name.to_s, attributes)
|
29
|
+
else
|
30
|
+
self.client.update_entity(self.table_name.to_s, attributes)
|
31
|
+
end
|
32
|
+
|
33
|
+
partition_key
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find_by_key(key, opts={})
|
37
|
+
begin
|
38
|
+
if db_instance = @client.get_entity(self.table_name.to_s, key, opts[:row_key])
|
39
|
+
db_instance unless self._logical_only(opts) && !db_instance[:active]
|
40
|
+
end
|
41
|
+
rescue RestClient::ResourceNotFound # returns nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.delete(key, opts={})
|
46
|
+
if self._logical_only(opts)
|
47
|
+
attributes= {:partition_key => key,
|
48
|
+
:row_key => opts[:row_key],
|
49
|
+
:active => false}
|
50
|
+
@client.update_entity(self.table_name.to_s, attributes)
|
51
|
+
else
|
52
|
+
@client.delete_entity(self.table_name.to_s, key, opts[:row_key])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self._logical_only(opts)
|
57
|
+
unless opts[:logical_only].nil?
|
58
|
+
opts[:logical_only]
|
59
|
+
else
|
60
|
+
RD3.config.enable_logical_deletes
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end # included do
|
64
|
+
|
65
|
+
end # module AzureTableStorage
|
66
|
+
end # module DataProviders
|
67
|
+
end # module RD3
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
module RD3
|
5
|
+
module DataProviders
|
6
|
+
module Rdbms
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
def self.client
|
11
|
+
return @client if @client
|
12
|
+
|
13
|
+
yml_config = YAML.load(File.read("#{RD3.config.config_file_directory}/#{self.config_file_name}.yml"))[RD3.config.environment.to_s]
|
14
|
+
yml_config = yml_config.symbolize_keys
|
15
|
+
|
16
|
+
@client = Sequel.connect(yml_config)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.save(attributes)
|
20
|
+
if id = attributes.delete(:id)
|
21
|
+
self._table.where('id = ?', id).update(attributes)
|
22
|
+
else
|
23
|
+
id = self._table.insert(attributes)
|
24
|
+
end
|
25
|
+
|
26
|
+
id
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.find_by_key(key, opts={})
|
30
|
+
# logically deleted instances will return nil
|
31
|
+
# if the key exists, but the active flag is false
|
32
|
+
filter_logical_deletes = unless opts[:logical_only].nil?
|
33
|
+
opts[:logical_only]
|
34
|
+
else
|
35
|
+
RD3.config.enable_logical_deletes
|
36
|
+
end
|
37
|
+
filter = {:id => key}
|
38
|
+
filter.merge!(:active => filter_logical_deletes) if filter_logical_deletes
|
39
|
+
self._table.where(filter).first
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.delete(id, opts)
|
43
|
+
logical_delete = unless opts[:logical_only].nil?
|
44
|
+
opts[:logical_only]
|
45
|
+
else
|
46
|
+
RD3.config.enable_logical_deletes
|
47
|
+
end
|
48
|
+
if logical_delete
|
49
|
+
self.save({:id => id, :active => false})
|
50
|
+
else
|
51
|
+
self._table.where('id = ?', id).delete
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self._table
|
56
|
+
self.client[self.table_name]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/rd3/model.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'active_support/core_ext' # necessary for Hash.to_json and Hash.as_json
|
2
|
+
|
3
|
+
module RD3
|
4
|
+
module Model
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveModel::Validations
|
7
|
+
|
8
|
+
included do
|
9
|
+
def self.class_attributes
|
10
|
+
@class_attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
# NOTE: this method must be declared
|
14
|
+
# before the attribute declarations
|
15
|
+
def self.attribute(*args)
|
16
|
+
@class_attributes ||= []
|
17
|
+
attributes_with_defaults = args.reduce({}) do |hash, arg|
|
18
|
+
arg.instance_of?(Hash) ? hash.merge(arg) : hash.merge(arg => nil)
|
19
|
+
end
|
20
|
+
@class_attributes.concat(attributes_with_defaults.keys)
|
21
|
+
|
22
|
+
attributes_with_defaults.each do |attribute, default|
|
23
|
+
getter = "@#{attribute}"
|
24
|
+
getter.prepend("@#{attribute} = #{default} if @#{attribute}.nil?;") unless default.nil?
|
25
|
+
|
26
|
+
self.module_eval <<-EVAL
|
27
|
+
def #{attribute}=(value)
|
28
|
+
# set dirty flag
|
29
|
+
@dirty = true unless @dirty || @#{attribute} == value
|
30
|
+
@#{attribute} = value
|
31
|
+
end
|
32
|
+
def #{attribute}
|
33
|
+
#{getter}
|
34
|
+
end
|
35
|
+
EVAL
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
attribute :id, :created_at, :updated_at
|
40
|
+
attribute :active=>true
|
41
|
+
end
|
42
|
+
|
43
|
+
# NOTE: 1) we can create a new instance with a hash
|
44
|
+
# in such a case, default values will be set
|
45
|
+
# 2) we can also create a new instance by COPYING
|
46
|
+
# properties from one object to another via
|
47
|
+
# its attributes
|
48
|
+
# in such a case, default values will NOT be set
|
49
|
+
def initialize(*args)
|
50
|
+
# default to an empty hash if Class.new is called, i.e., args = nil
|
51
|
+
obj = args[0] || {}
|
52
|
+
|
53
|
+
# an instance SHOULD be marked as dirty if:
|
54
|
+
# 1) it's initialized w/ a non-empty hash
|
55
|
+
#
|
56
|
+
# it SHOULD NOT be marked as dirty if:
|
57
|
+
# 1) Class.new is called (empty hash)
|
58
|
+
# 2) we're mapping from one object to another
|
59
|
+
is_dirty = obj.instance_of?(Hash) && !obj.blank?
|
60
|
+
|
61
|
+
# grab attributes hash if we're dealing w/ mapping
|
62
|
+
# an object instance
|
63
|
+
obj = obj.attributes unless obj.instance_of? Hash
|
64
|
+
|
65
|
+
# map properties
|
66
|
+
obj.each do |k,v|
|
67
|
+
self.send("#{k}=", v) if self.class_attributes.include? k
|
68
|
+
end
|
69
|
+
|
70
|
+
# reset dirty flag
|
71
|
+
@dirty = is_dirty
|
72
|
+
|
73
|
+
# return self for method chaining
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# tracks whether or not the instance
|
78
|
+
# has been updated/changed post initialization
|
79
|
+
def dirty?
|
80
|
+
return @dirty ||= false
|
81
|
+
end
|
82
|
+
|
83
|
+
def persisted?
|
84
|
+
id.present?
|
85
|
+
end
|
86
|
+
|
87
|
+
def class_attributes
|
88
|
+
return self.class.class_attributes
|
89
|
+
end
|
90
|
+
|
91
|
+
def attributes
|
92
|
+
self.instance_values.select { |key| class_attributes.include? key.to_sym }.symbolize_keys
|
93
|
+
end
|
94
|
+
|
95
|
+
# add errors to errors collection
|
96
|
+
# allows errors to bubble up from a child
|
97
|
+
# model to its parent model when working
|
98
|
+
# with repositories
|
99
|
+
def errors=(value)
|
100
|
+
unless value.blank?
|
101
|
+
value.each do |k, v|
|
102
|
+
self.errors[k] = v
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end # module Model
|
108
|
+
end # module RD3
|