mince_data_model 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,5 @@
1
+ *.gem
2
+ .idea
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@mince_data_model --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in mince_data_model.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # What is this?
2
+
3
+ Provides a very lightweight interface to store and retrieve data in MongoDB in Ruby.
4
+
5
+ # Why would you want this?
6
+
7
+ - To build a non-active-record implementation application using MongoDB.
8
+ - Allows interchanging data persistence with HashyDb
9
+
10
+ # Todos
11
+
12
+ - Remove rails dependency
13
+ - Add a better readme
14
+
15
+ # Contribute
16
+
17
+ - fork into a topic branch, write specs, make a pull request.
18
+
19
+ # Owners
20
+
21
+ Matt Simpson - [@railsgrammer](https://twitter.com/railsgrammer)
22
+ <br />
23
+ Jason Mayer - [@farkerhaiku](https://twitter.com/farkerhaiku)
24
+
25
+ # Contributors
26
+ - Your name here!
27
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,16 @@
1
+ require_relative 'guitar_data_model'
2
+
3
+ class Guitar
4
+ attr_reader :brand, :price, :type, :color, :id
5
+
6
+ def initialize(attrs)
7
+ @brand = attrs[:brand]
8
+ @price = attrs[:price]
9
+ @type = attrs[:type]
10
+ @color = attrs[:color]
11
+ end
12
+
13
+ def store
14
+ @id = GuitarDataModel.store(self)
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../lib/mince_data_model'
2
+
3
+ class GuitarDataModel
4
+ include MinceDataModel
5
+
6
+ data_collection :guitars
7
+ data_fields :brand, :price, :type, :color
8
+ end
@@ -0,0 +1,7 @@
1
+ require 'rails/application'
2
+
3
+ module DataStoreConfig
4
+ def self.data_store
5
+ Rails.application.config.data_store.to_s.camelize.constantize
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module MinceDataModel
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,166 @@
1
+ require "mince_data_model/version"
2
+ # SRP: Interface to read and write to and from the data store class for a specific collection
3
+
4
+ require 'active_support/hash_with_indifferent_access'
5
+ require 'active_support/core_ext/hash/slice'
6
+ require 'active_support/concern'
7
+
8
+ require_relative 'data_store_config'
9
+
10
+ module MinceDataModel
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ attr_reader :id, :model
15
+ end
16
+
17
+ module ClassMethods
18
+ def data_store
19
+ DataStoreConfig.data_store
20
+ end
21
+
22
+ def data_fields(*fields)
23
+ create_data_fields(*fields) if fields.any?
24
+ @data_fields
25
+ end
26
+
27
+ def data_collection(collection_name = nil)
28
+ set_data_collection(collection_name) if collection_name
29
+ @data_collection
30
+ end
31
+
32
+ def store(model)
33
+ new(model).tap do |p|
34
+ p.add_to_data_store
35
+ end.id
36
+ end
37
+
38
+ def update(model)
39
+ new(model).tap do |p|
40
+ p.replace_in_data_store
41
+ end
42
+ end
43
+
44
+ def remove_from_array(id, field, value)
45
+ data_store.instance.remove_from_array(data_collection, data_store.primary_key_identifier, id, field, value)
46
+ end
47
+
48
+ def push_to_array(id, field, value)
49
+ data_store.instance.push_to_array(data_collection, data_store.primary_key_identifier, id, field, value)
50
+ end
51
+
52
+ def find(id)
53
+ translate_from_data_store data_store.instance.find(data_collection, data_store.primary_key_identifier, id)
54
+ end
55
+
56
+ def all
57
+ translate_each_from_data_store data_store.instance.find_all(data_collection)
58
+ end
59
+
60
+ def all_by_field(field, value)
61
+ translate_each_from_data_store data_store.instance.get_all_for_key_with_value(data_collection, field, value)
62
+ end
63
+
64
+ def all_by_fields(hash)
65
+ translate_each_from_data_store data_store.instance.get_by_params(data_collection, hash)
66
+ end
67
+
68
+ def find_by_fields(hash)
69
+ translate_from_data_store all_by_fields(hash).first
70
+ end
71
+
72
+ def containing_any(field, values)
73
+ translate_each_from_data_store data_store.instance.containing_any(data_collection, field, values)
74
+ end
75
+
76
+ def array_contains(field, value)
77
+ translate_each_from_data_store data_store.instance.array_contains(data_collection, field, value)
78
+ end
79
+
80
+ def translate_from_data_store(hash)
81
+ if hash
82
+ hash["id"] = hash[data_store.primary_key_identifier] if hash[data_store.primary_key_identifier]
83
+ HashWithIndifferentAccess.new hash
84
+ end
85
+ end
86
+
87
+ def translate_each_from_data_store(data)
88
+ data.collect {|d| translate_from_data_store(d) }
89
+ end
90
+
91
+ private
92
+
93
+ def set_data_collection(collection_name)
94
+ @data_collection = collection_name
95
+ end
96
+
97
+ def create_data_fields(*fields)
98
+ attr_accessor *fields
99
+ @data_fields = fields
100
+ end
101
+ end
102
+
103
+ def initialize(model)
104
+ @model = model
105
+ set_data_field_values
106
+ set_id
107
+ end
108
+
109
+ def data_store
110
+ self.class.data_store
111
+ end
112
+
113
+ def data_fields
114
+ self.class.data_fields
115
+ end
116
+
117
+ def data_collection
118
+ self.class.data_collection
119
+ end
120
+
121
+ def add_to_data_store
122
+ data_store.instance.add(data_collection, attributes)
123
+ end
124
+
125
+ def replace_in_data_store
126
+ data_store.instance.replace(data_collection, attributes)
127
+ end
128
+
129
+ private
130
+
131
+ def attributes
132
+ model_instance_values.merge(primary_key_identifier => id)
133
+ end
134
+
135
+ def model_instance_values
136
+ HashWithIndifferentAccess.new(model.instance_values).slice(*data_fields)
137
+ end
138
+
139
+ def set_id
140
+ @id = model_has_id? ? model.id : generated_id
141
+ end
142
+
143
+ def generated_id
144
+ data_store.generate_unique_id(model)
145
+ end
146
+
147
+ def model_has_id?
148
+ model.respond_to?(:id) && model.id
149
+ end
150
+
151
+ def set_data_field_values
152
+ data_fields.each { |field| set_data_field_value(field) }
153
+ end
154
+
155
+ def primary_key_identifier
156
+ data_store.primary_key_identifier
157
+ end
158
+
159
+ def set_data_field_value(field)
160
+ self.send("#{field}=", model.send(field)) if field_exists?(field)
161
+ end
162
+
163
+ def field_exists?(field)
164
+ model.respond_to?(field) && !model.send(field).nil?
165
+ end
166
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "mince_data_model/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "mince_data_model"
7
+ s.version = MinceDataModel::VERSION
8
+ s.authors = ["Jason Mayer", "Matt Simpson", "Asynchrony Solutions"]
9
+ s.email = ["matt@railsgrammer.com", "jason.mayer@gmail.com"]
10
+ s.homepage = "https://github.com/asynchrony/mince_data_model"
11
+ s.summary = %q{Interface for interchanging which type of data store to persist data to}
12
+ s.description = %q{Interface for interchanging which type of data store to persist data to}
13
+
14
+ s.rubyforge_project = "mince_data_model"
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
+ s.add_development_dependency "rspec"
22
+ s.add_development_dependency "rake"
23
+ s.add_dependency("rails", "~> 3.0")
24
+ end
@@ -0,0 +1,17 @@
1
+ require_relative '../examples/guitar_data_model'
2
+
3
+ require_relative 'support/shared_examples/mince_data_model_examples'
4
+
5
+ describe GuitarDataModel do
6
+ let(:collection_name) { :guitars }
7
+ let(:data_field_attributes) do
8
+ {
9
+ brand: 'a brand everyone knows',
10
+ price: 'a price you save up for',
11
+ type: 'the kind you want',
12
+ color: 'should be your favorite'
13
+ }
14
+ end
15
+
16
+ it_behaves_like 'a data model'
17
+ end
@@ -0,0 +1,32 @@
1
+ require_relative '../examples/guitar'
2
+
3
+ describe Guitar do
4
+ let(:attrs) { { brand: 'Gibson', price: 3399.99, type: 'Les Paul', color: 'Mahogany' } }
5
+
6
+ subject { described_class.new(attrs)}
7
+
8
+ its(:brand){ should == attrs[:brand] }
9
+ its(:price){ should == attrs[:price] }
10
+ its(:type){ should == attrs[:type] }
11
+ its(:color){ should == attrs[:color] }
12
+
13
+ describe "storing the guitar" do
14
+ let(:mock_id) { mock 'unique id for the guitar' }
15
+
16
+ before do
17
+ GuitarDataModel.stub(store: mock_id)
18
+ end
19
+
20
+ it 'uses the guitar document to store it' do
21
+ GuitarDataModel.should_receive(:store).with(subject).and_return(mock_id)
22
+
23
+ subject.store
24
+ end
25
+
26
+ it 'sets the id received from the document to the guitar so we can find it later' do
27
+ subject.store
28
+
29
+ subject.id.should == mock_id
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,155 @@
1
+ require 'digest'
2
+
3
+ shared_examples_for 'a data model' do
4
+ let(:mock_data_store) { mock 'data store data model' }
5
+ let(:mock_data_store_class) { mock 'data store data model', :instance => mock_data_store, :generate_unique_id => unique_id, :primary_key_identifier => primary_key }
6
+ let(:unique_id) { mock 'id' }
7
+ let(:primary_key) { "custom_id" }
8
+
9
+ before do
10
+ DataStoreConfig.stub(:data_store => mock_data_store_class)
11
+ end
12
+
13
+ describe "storing a data model" do
14
+ let(:mock_model) { mock 'a model', instance_values: data_field_attributes }
15
+
16
+ before do
17
+ mock_data_store.stub(:add)
18
+ end
19
+
20
+ it 'generates a unique id using the model as a salt' do
21
+ mock_data_store_class.should_receive(:generate_unique_id).with(mock_model).and_return(unique_id)
22
+
23
+ described_class.store(mock_model)
24
+ end
25
+
26
+ it 'adds the data model to the db store' do
27
+ mock_data_store.should_receive(:add).with(collection_name, HashWithIndifferentAccess.new({primary_key => unique_id}).merge(data_field_attributes))
28
+
29
+ described_class.store(mock_model)
30
+ end
31
+ end
32
+
33
+ describe "updating a data model" do
34
+ let(:data_model_id) { '1234567' }
35
+ let(:mock_model) { mock 'a model', id: data_model_id, instance_values: data_field_attributes }
36
+
37
+ before do
38
+ mock_data_store.stub(:replace)
39
+ end
40
+
41
+ it 'replaces the data model in the db store' do
42
+ mock_data_store.should_receive(:replace).with(collection_name, HashWithIndifferentAccess.new({primary_key => data_model_id}).merge(data_field_attributes))
43
+
44
+ described_class.update(mock_model)
45
+ end
46
+ end
47
+
48
+ describe "pushing a value to an array for a data model" do
49
+ let(:data_model_id) { '1234567' }
50
+
51
+ it 'replaces the data model in the db store' do
52
+ mock_data_store.should_receive(:push_to_array).with(collection_name, primary_key, data_model_id, :array_field, 'some value')
53
+
54
+ described_class.push_to_array(data_model_id, :array_field, 'some value')
55
+ end
56
+ end
57
+
58
+ describe "getting all data models with a specific value for a field" do
59
+ let(:mock_data_model) { {primary_key => 'some id'} }
60
+ let(:expected_data_models) { [HashWithIndifferentAccess.new({:id => 'some id', primary_key => 'some id'})] }
61
+ let(:mock_data_models) { [mock_data_model] }
62
+ subject { described_class.array_contains(:some_field, 'some value') }
63
+
64
+ it 'returns the stored data models with the requested field / value' do
65
+ mock_data_store.should_receive(:array_contains).with(collection_name, :some_field, 'some value').and_return(mock_data_models)
66
+
67
+ subject.should == expected_data_models
68
+ end
69
+ end
70
+
71
+ describe "removing a value from an array for a data model" do
72
+ let(:data_model_id) { '1234567' }
73
+
74
+ it 'removes the value from the array' do
75
+ mock_data_store.should_receive(:remove_from_array).with(collection_name, primary_key, data_model_id, :array_field, 'some value')
76
+
77
+ described_class.remove_from_array(data_model_id, :array_field, 'some value')
78
+ end
79
+ end
80
+
81
+ describe 'getting all of the data models' do
82
+ let(:mock_data_model) { {primary_key => 'some id'} }
83
+ let(:expected_data_models) { [HashWithIndifferentAccess.new({:id => 'some id', primary_key => 'some id'})] }
84
+ let(:mock_data_models) { [mock_data_model] }
85
+
86
+ it 'returns the stored data models' do
87
+ mock_data_store.should_receive(:find_all).with(collection_name).and_return(mock_data_models)
88
+
89
+ described_class.all.should == expected_data_models
90
+ end
91
+ end
92
+
93
+ describe "getting all the data fields by a parameter hash" do
94
+ let(:mock_data_model) { {primary_key => 'some id'} }
95
+ let(:expected_data_models) { [{:id => 'some id', primary_key => 'some id'}] }
96
+ let(:mock_data_models) { [mock_data_model] }
97
+ let(:expected_data_models) { [HashWithIndifferentAccess.new(mock_data_model)] }
98
+ let(:sample_hash) { {field1: nil, field2: 'not nil'} }
99
+
100
+ it 'passes the hash to the mock_data_store_class' do
101
+ mock_data_store.should_receive(:get_by_params).with(collection_name, sample_hash).and_return(mock_data_models)
102
+
103
+ described_class.all_by_fields(sample_hash).should == expected_data_models
104
+ end
105
+ end
106
+
107
+ describe "getting a record by a set of key values" do
108
+ let(:mock_data_model) { {primary_key => 'some id'} }
109
+ let(:mock_data_models) { [mock_data_model] }
110
+ let(:expected_data_models) { [{:id => 'some id', primary_key => 'some id'}] }
111
+
112
+ let(:sample_hash) { {field1: nil, field2: 'not nil'} }
113
+
114
+ it 'passes the hash to the mock_data_store_class' do
115
+ mock_data_store.should_receive(:get_by_params).with(collection_name, sample_hash).and_return(mock_data_models)
116
+
117
+ described_class.find_by_fields(sample_hash).should == HashWithIndifferentAccess.new(expected_data_models.first)
118
+ end
119
+ end
120
+
121
+ describe "getting all of the data models for a where a field contains any value of a given array of values" do
122
+ let(:mock_data_models) { [{primary_key => 'some id'}, {primary_key => 'some id 2'}] }
123
+ let(:expected_data_models) { [{"id" => 'some id', primary_key => 'some id'}, {"id" => 'some id 2', primary_key => 'some id 2'}] }
124
+
125
+ subject { described_class.containing_any(:some_field, ['value 1', 'value 2']) }
126
+
127
+ it 'returns the stored data models' do
128
+ mock_data_store.should_receive(:containing_any).with(collection_name, :some_field, ['value 1', 'value 2']).and_return(mock_data_models)
129
+
130
+ subject.should == expected_data_models
131
+ end
132
+ end
133
+
134
+ describe "getting all data models with a specific value for a field" do
135
+ let(:mock_data_models) { [{primary_key => 'some id'}, {primary_key => 'some id 2'}] }
136
+ let(:expected_data_models) { [HashWithIndifferentAccess.new({:id => 'some id', primary_key => 'some id'}), HashWithIndifferentAccess.new({id: 'some id 2', primary_key => 'some id 2'})] }
137
+ subject { described_class.all_by_field(:some_field, 'some value') }
138
+
139
+ it 'returns the stored data models with the requested field / value' do
140
+ mock_data_store.should_receive(:get_all_for_key_with_value).with(collection_name, :some_field, 'some value').and_return(mock_data_models)
141
+
142
+ subject.should == expected_data_models
143
+ end
144
+ end
145
+
146
+ describe 'getting a specific data model' do
147
+ let(:mock_data_model) { {primary_key => 'id', :id => 'id' } }
148
+
149
+ it 'returns the data model from the data store' do
150
+ mock_data_store.should_receive(:find).with(collection_name, primary_key, 'id').and_return(mock_data_model)
151
+
152
+ described_class.find(mock_data_model[:id]).should == HashWithIndifferentAccess.new(mock_data_model)
153
+ end
154
+ end
155
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mince_data_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Mayer
9
+ - Matt Simpson
10
+ - Asynchrony Solutions
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2012-03-02 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ requirement: &16308420 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
23
+ version: '0'
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: *16308420
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: &16307660 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: *16307660
38
+ - !ruby/object:Gem::Dependency
39
+ name: rails
40
+ requirement: &16305880 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.0'
46
+ type: :runtime
47
+ prerelease: false
48
+ version_requirements: *16305880
49
+ description: Interface for interchanging which type of data store to persist data
50
+ to
51
+ email:
52
+ - matt@railsgrammer.com
53
+ - jason.mayer@gmail.com
54
+ executables: []
55
+ extensions: []
56
+ extra_rdoc_files: []
57
+ files:
58
+ - .gitignore
59
+ - .rspec
60
+ - .rvmrc
61
+ - Gemfile
62
+ - README.md
63
+ - Rakefile
64
+ - examples/guitar.rb
65
+ - examples/guitar_data_model.rb
66
+ - lib/data_store_config.rb
67
+ - lib/mince_data_model.rb
68
+ - lib/mince_data_model/version.rb
69
+ - mince_data_model.gemspec
70
+ - spec/guitar_data_model_spec.rb
71
+ - spec/guitar_spec.rb
72
+ - spec/support/shared_examples/mince_data_model_examples.rb
73
+ homepage: https://github.com/asynchrony/mince_data_model
74
+ licenses: []
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project: mince_data_model
93
+ rubygems_version: 1.8.10
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Interface for interchanging which type of data store to persist data to
97
+ test_files: []