couchbase-model-relationship 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.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
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
18
+ spec/*.jar
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in couchbase-model-relationship.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jon Moses
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,29 @@
1
+ # Couchbase::Model::Relationship
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'couchbase-model-relationship'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install couchbase-model-relationship
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ require 'rake/clean'
5
+
6
+ rule 'test/CouchbaseMock.jar' do |task|
7
+ jar_path = "0.5-SNAPSHOT/CouchbaseMock-0.5-20120726.220757-19.jar"
8
+ sh %{wget -q -O spec/CouchbaseMock.jar http://files.couchbase.com/maven2/org/couchbase/mock/CouchbaseMock/#{jar_path}}
9
+ end
10
+
11
+ CLOBBER << 'spec/CouchbaseMock.jar'
12
+
13
+ require 'rspec/core/rake_task'
14
+
15
+ RSpec::Core::RakeTask.new(:spec)
16
+
17
+ task :default => :spec
18
+
19
+ #Rake::Task['spec'].prerequisites.unshift('test/CouchbaseMock.jar')
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'couchbase/model/relationship/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "couchbase-model-relationship"
8
+ spec.version = Couchbase::Model::Relationship::VERSION
9
+ spec.authors = ["Jon Moses"]
10
+ spec.email = ["jon@burningbush.us"]
11
+ spec.description = %q{Closely bound relationships for Couchbase::Model}
12
+ spec.summary = %q{Supports relationships that are fetched and saved with a root model}
13
+ spec.homepage = "https://github.com/jmoses/couchbase-model-relationship"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'activemodel', '~> 3'
22
+ spec.add_dependency 'activesupport', '~> 3'
23
+ spec.add_dependency 'couchbase', '~> 1'
24
+ spec.add_dependency 'couchbase-model', '~> 0.5.3'
25
+ spec.add_dependency 'json', '~> 1.7.7'
26
+ # FIXME Remove when couchbase is > 1.3.1
27
+ spec.add_dependency 'multi_json', '1.7.5'
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.3"
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency 'rspec'
32
+ spec.add_development_dependency 'mocha'
33
+ end
@@ -0,0 +1,36 @@
1
+ module Couchbase
2
+ class Model
3
+ module Attributes
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ include ::ActiveModel::AttributeMethods
8
+
9
+ class << self
10
+ remove_method :attribute
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def attribute(*names)
16
+ options = names.extract_options!
17
+
18
+ names.each do |name|
19
+ name = name.to_s
20
+ attributes[name] = options[:default]
21
+
22
+ define_attribute_methods([name])
23
+
24
+ define_method(name) do
25
+ read_attribute name
26
+ end
27
+
28
+ define_method("#{name}=") do |value|
29
+ write_attribute name, value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ module Couchbase
2
+ class Model
3
+ module ComplexAttributes
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def array_attribute(*names)
8
+ options = names.extract_options!
9
+ class_name = options.delete(:class_name)
10
+
11
+ names.each do |name|
12
+ name = name.to_s
13
+
14
+ (@_array_attributes ||= {})[name] = class_name
15
+
16
+ attribute name, {default: proc { [] }}.merge(options)
17
+
18
+ define_method("#{name}=") do |values|
19
+ actual_values = values.map do |value|
20
+ if value.is_a?(String) && value =~ /json_class/
21
+ JSON.load value
22
+ else
23
+ value
24
+ end
25
+ end
26
+
27
+ write_attribute name, actual_values
28
+ end
29
+ end
30
+ end
31
+
32
+ def array_attribute_class(name)
33
+ @_array_attributes[name.to_s]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ module Couchbase
2
+ class Model
3
+ class DeepCopier
4
+ attr_reader :source
5
+ def initialize(source)
6
+ @source = source
7
+ end
8
+
9
+ def copy
10
+ if cloneable?
11
+ if complex?
12
+ deep_copy
13
+ else
14
+ source.clone
15
+ end
16
+ else
17
+ source
18
+ end
19
+ end
20
+
21
+ private
22
+ def cloneable?
23
+ source.duplicable?
24
+ end
25
+
26
+ def complex?
27
+ [Array, Hash].include? source.class
28
+ end
29
+
30
+ def deep_copy
31
+ shallow = source.clone
32
+
33
+ if source.is_a?(Array)
34
+ shallow.clear
35
+ shallow.concat source.map {|value| DeepCopier.new(value).copy }
36
+ elsif source.is_a?(Hash)
37
+ source.each {|key, value| shallow[key] = DeepCopier.new(value).copy }
38
+ shallow
39
+ else
40
+ raise ArgumentError.new("Deep copying a #{source.class} is not supported.")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,81 @@
1
+ # TODO Deep clone previous changes to support nested complex data (from BB)
2
+ module Couchbase
3
+ class Model
4
+ module Dirty
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::Dirty
7
+
8
+ included do
9
+ remove_method :write_attribute
10
+
11
+ alias_method_chain :save, :dirty
12
+ alias_method_chain :create, :dirty
13
+
14
+ attribute_method_prefix :previous_
15
+
16
+ class << self
17
+ alias_method_chain :_find, :cleaning
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+ # If we're just loaded from the database, we're not dirty
23
+ def _find_with_cleaning(quiet, *ids)
24
+ _find_without_cleaning(quiet, *ids).tap do |results|
25
+ Array(results).each {|instance| instance.send :clean! }
26
+ end
27
+ end
28
+ end
29
+
30
+ def write_attribute(name, value)
31
+ send "#{name}_will_change!" unless send(name) == value
32
+
33
+ @_attributes[name] = value
34
+ end
35
+
36
+ def save_with_dirty(options = {})
37
+ save_without_dirty(options).tap do |value|
38
+ capture_previous_changes if value
39
+ end
40
+ end
41
+
42
+ def create_with_dirty(options = {})
43
+ create_without_dirty(options).tap do |value|
44
+ capture_previous_changes if value
45
+ end
46
+ end
47
+
48
+ # FIXME Return value for "Fail" and "didn't try" is the same
49
+ def save_if_changed(options = {})
50
+ save if changed?
51
+ end
52
+
53
+ private
54
+ def capture_previous_changes
55
+ @previously_changed = changes
56
+ @changed_attributes.clear
57
+ end
58
+
59
+ def clean!
60
+ @changed_attributes.clear if changed?
61
+ end
62
+
63
+ def previous_attribute(attr)
64
+ return unless previous_changes
65
+
66
+ previous_changes[attr.to_s].try :first
67
+ end
68
+
69
+ def attribute_will_change!(attr)
70
+ begin
71
+ value = __send__(attr)
72
+ value = DeepCopier.new(value).copy
73
+ rescue TypeError, NoMethodError
74
+ end
75
+
76
+ changed_attributes[attr] = value
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,53 @@
1
+ module Couchbase
2
+ class Model
3
+ module IdPrefix
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ alias_method_chain :create, :id_prefix
8
+ end
9
+
10
+ def prefixed_id(id)
11
+ self.class.prefixed_id(id)
12
+ end
13
+
14
+ def create_with_id_prefix(options = {})
15
+ ensure_has_id
16
+ create_without_id_prefix(options)
17
+ end
18
+
19
+ def ensure_has_id
20
+ @id ||= model.next_prefixed_id
21
+ end
22
+ private :ensure_has_id
23
+
24
+ module ClassMethods
25
+ # FIXME Need to handle cases where there's no id, or we fail or
26
+ # w/e
27
+ def id_prefix
28
+ name.underscore
29
+ end
30
+
31
+ def next_prefixed_id
32
+ prefixed_id(Couchbase::Model::UUID.generator.next(1, thread_storage[:uuid_algorithm]))
33
+ end
34
+
35
+ def prefixed_id(id)
36
+ "#{id_prefix}:#{unprefixed_id(id)}"
37
+ end
38
+
39
+ def unprefixed_id(id)
40
+ id.to_s.split(':').last
41
+ end
42
+
43
+ def prefix_from_id(id)
44
+ id.to_s.split(':').first
45
+ end
46
+
47
+ def class_from_id(id)
48
+ prefix_from_id(id).classify.constantize
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,34 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+ require 'active_support/core_ext/string/inflections'
4
+ require 'active_support/core_ext/object/try'
5
+
6
+ require 'active_model'
7
+
8
+ require "couchbase/model/relationship/version"
9
+ require 'couchbase/model'
10
+ require 'couchbase/model/deep_copier'
11
+ require 'couchbase/model/attributes'
12
+ require 'couchbase/model/complex_attributes'
13
+ require 'couchbase/model/dirty'
14
+ require 'couchbase/model/id_prefix'
15
+ require 'couchbase/model/relationship/association'
16
+ require 'couchbase/model/relationship/parent'
17
+ require 'couchbase/model/relationship/child'
18
+
19
+ module Couchbase
20
+ class Model
21
+ module Relationship
22
+ end
23
+ end
24
+ end
25
+
26
+ # Setup code
27
+
28
+ Couchbase::Model.send :include, Couchbase::Model::Attributes
29
+ Couchbase::Model.send :include, Couchbase::Model::ComplexAttributes
30
+ Couchbase::Model.send :include, Couchbase::Model::Dirty
31
+ Couchbase::Model.send :include, Couchbase::Model::IdPrefix
32
+ Couchbase::Model.send :include, Couchbase::Model::Relationship::Parent
33
+ Couchbase::Model.send :include, Couchbase::Model::Relationship::Child
34
+
@@ -0,0 +1,40 @@
1
+ module Couchbase
2
+ class Model
3
+ module Relationship
4
+ class Association
5
+ attr_accessor :name
6
+ attr_reader :auto_save, :auto_delete, :auto_load, :class_name
7
+
8
+ def initialize(name, options = {})
9
+ self.name = name.to_s
10
+ @auto_save = options[:auto_save]
11
+ @auto_delete = options[:auto_delete]
12
+ @class_name = options[:class_name]
13
+ @auto_load = options.key?(:auto_load) ? options[:auto_load] : true
14
+ end
15
+
16
+ def fetch(parent)
17
+ parent.send(name)
18
+ end
19
+
20
+ def load(parent)
21
+ child_id = child_class.prefixed_id(parent.id)
22
+
23
+ child_class.find_by_id(child_id)
24
+ end
25
+
26
+ def child_klass
27
+ @class_name || name.classify
28
+ end
29
+
30
+ def child_class
31
+ child_klass.constantize
32
+ end
33
+
34
+ def prefix
35
+ child_class.id_prefix
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end