Skip to content
This repository has been archived by the owner on Apr 17, 2018. It is now read-only.

Commit

Permalink
Add ability to specify :default value for relationships
Browse files Browse the repository at this point in the history
* Like with Property, :default may be a value or an object that
  responds to #call

[#1099 state:resolved]
  • Loading branch information
dkubb committed Mar 28, 2010
1 parent 547627d commit 540e0de
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 42 deletions.
11 changes: 10 additions & 1 deletion dm-core.gemspec
Expand Up @@ -9,7 +9,7 @@ Gem::Specification.new do |s|

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Dan Kubb"]
s.date = %q{2010-03-18}
s.date = %q{2010-03-27}
s.description = %q{Faster, Better, Simpler.}
s.email = %q{dan.kubb@gmail.com}
s.extra_rdoc_files = [
Expand Down Expand Up @@ -82,6 +82,7 @@ Gem::Specification.new do |s|
"lib/dm-core/support/lazy_array.rb",
"lib/dm-core/support/logger.rb",
"lib/dm-core/support/naming_conventions.rb",
"lib/dm-core/support/subject.rb",
"lib/dm-core/transaction.rb",
"lib/dm-core/type.rb",
"lib/dm-core/types/boolean.rb",
Expand Down Expand Up @@ -137,7 +138,10 @@ Gem::Specification.new do |s|
"spec/semipublic/adapters/sqlite3_adapter_spec.rb",
"spec/semipublic/adapters/sqlserver_adapter_spec.rb",
"spec/semipublic/adapters/yaml_adapter_spec.rb",
"spec/semipublic/associations/many_to_many_spec.rb",
"spec/semipublic/associations/many_to_one_spec.rb",
"spec/semipublic/associations/one_to_many_spec.rb",
"spec/semipublic/associations/one_to_one_spec.rb",
"spec/semipublic/associations/relationship_spec.rb",
"spec/semipublic/associations_spec.rb",
"spec/semipublic/collection_spec.rb",
Expand All @@ -150,6 +154,7 @@ Gem::Specification.new do |s|
"spec/semipublic/resource_spec.rb",
"spec/semipublic/shared/condition_shared_spec.rb",
"spec/semipublic/shared/resource_shared_spec.rb",
"spec/semipublic/shared/subject_shared_spec.rb",
"spec/spec.opts",
"spec/spec_helper.rb",
"spec/unit/array_spec.rb",
Expand Down Expand Up @@ -213,7 +218,10 @@ Gem::Specification.new do |s|
"spec/semipublic/adapters/sqlite3_adapter_spec.rb",
"spec/semipublic/adapters/sqlserver_adapter_spec.rb",
"spec/semipublic/adapters/yaml_adapter_spec.rb",
"spec/semipublic/associations/many_to_many_spec.rb",
"spec/semipublic/associations/many_to_one_spec.rb",
"spec/semipublic/associations/one_to_many_spec.rb",
"spec/semipublic/associations/one_to_one_spec.rb",
"spec/semipublic/associations/relationship_spec.rb",
"spec/semipublic/associations_spec.rb",
"spec/semipublic/collection_spec.rb",
Expand All @@ -226,6 +234,7 @@ Gem::Specification.new do |s|
"spec/semipublic/resource_spec.rb",
"spec/semipublic/shared/condition_shared_spec.rb",
"spec/semipublic/shared/resource_shared_spec.rb",
"spec/semipublic/shared/subject_shared_spec.rb",
"spec/spec_helper.rb",
"spec/unit/array_spec.rb",
"spec/unit/hash_spec.rb",
Expand Down
2 changes: 1 addition & 1 deletion lib/dm-core.rb
Expand Up @@ -66,7 +66,7 @@ module ActiveSupport
require 'dm-core/support/assertions'
require 'dm-core/support/lazy_array'
require 'dm-core/support/hook'

require 'dm-core/support/subject'

require 'dm-core/model'
require 'dm-core/model/descendant_set'
Expand Down
3 changes: 3 additions & 0 deletions lib/dm-core/associations/many_to_one.rb
Expand Up @@ -114,6 +114,9 @@ def resource_for(source, other_query = nil)
def get(source, other_query = nil)
lazy_load(source) unless loaded?(source)

# set the default if it is not already set
set(source, default_for(source)) unless loaded?(source)

resource = get!(source)
if other_query.nil? || query_for(source, other_query).conditions.matches?(resource)
resource
Expand Down
7 changes: 6 additions & 1 deletion lib/dm-core/associations/one_to_many.rb
Expand Up @@ -44,7 +44,7 @@ def collection_for(source, other_query = nil)
collection.source = source

# make the collection empty if the source is not saved
collection.replace([]) unless source.saved?
collection.replace(default_for(source)) unless source.saved?

collection
end
Expand All @@ -70,6 +70,11 @@ def set(source, targets)
get!(source).replace(targets)
end

# @api semipublic
def default_for(source)
Array(super)
end

private

# @api semipublic
Expand Down
12 changes: 11 additions & 1 deletion lib/dm-core/associations/one_to_one.rb
Expand Up @@ -6,14 +6,19 @@ class Relationship < Associations::Relationship
superclass.send("#{visibility}_instance_methods", false).each do |method|
undef_method method unless method.to_s == 'initialize'
end

# remove mixed in methods
undef_method *DataMapper::Subject.send("#{visibility}_instance_methods", false)
end

# Loads (if necessary) and returns association target
# for given source
#
# @api semipublic
def get(source, other_query = nil)
return unless loaded?(source) || valid_source?(source)
unless loaded?(source) || valid_source?(source)
set(source, default_for(source))
end

relationship.get(source, other_query).first
end
Expand All @@ -26,6 +31,11 @@ def set(source, target)
relationship.set(source, [ target ].compact).first
end

# @api semipublic
def default_for(source)
relationship.default_for(source).first
end

# @api public
def kind_of?(klass)
super || relationship.kind_of?(klass)
Expand Down
4 changes: 3 additions & 1 deletion lib/dm-core/associations/relationship.rb
Expand Up @@ -7,8 +7,9 @@ module Associations
# with methods like get and set overridden.
class Relationship
include DataMapper::Assertions
include Subject

OPTIONS = [ :child_repository_name, :parent_repository_name, :child_key, :parent_key, :min, :max, :inverse, :reader_visibility, :writer_visibility ].to_set
OPTIONS = [ :child_repository_name, :parent_repository_name, :child_key, :parent_key, :min, :max, :inverse, :reader_visibility, :writer_visibility, :default ].to_set

# Relationship name
#
Expand Down Expand Up @@ -447,6 +448,7 @@ def initialize(name, child_model, parent_model, options = {})
@max = @options[:max]
@reader_visibility = @options.fetch(:reader_visibility, :public)
@writer_visibility = @options.fetch(:writer_visibility, :public)
@default = @options.fetch(:default, nil)

# TODO: normalize the @query to become :conditions => AndOperation
# - Property/Relationship/Path should be left alone
Expand Down
35 changes: 2 additions & 33 deletions lib/dm-core/property.rb
Expand Up @@ -290,6 +290,7 @@ module DataMapper
# see SingleTableInheritance for more on how to use <tt>Class</tt> columns.
class Property
include DataMapper::Assertions
include Subject
extend Deprecate
extend Equalizer

Expand Down Expand Up @@ -514,7 +515,7 @@ def get(resource)
if loaded?(resource)
get!(resource)
else
set(resource, default? ? default_for(resource) : nil)
set(resource, default_for(resource))
end
end

Expand Down Expand Up @@ -687,38 +688,6 @@ def load(value)
typecast(value)
end

# Returns a default value of the
# property for given resource.
#
# When default value is a callable object,
# it is called with resource and property passed
# as arguments.
#
# @param [Resource] resource
# the model instance for which the default is to be set
#
# @return [Object]
# the default value of this property for +resource+
#
# @api semipublic
def default_for(resource)
if @default.respond_to?(:call)
@default.call(resource, self)
else
@default.try_dup
end
end

# Returns true if the property has a default value
#
# @return [Boolean]
# true if the property has a default value
#
# @api semipublic
def default?
@options.key?(:default)
end

# Returns given value unchanged for core types and
# uses +dump+ method of the property type for custom types.
#
Expand Down
10 changes: 6 additions & 4 deletions lib/dm-core/resource.rb
Expand Up @@ -894,10 +894,12 @@ def child_collections
# @api private
def _create
# set defaults for new resource
properties.each do |property|
unless property.serial? || property.loaded?(self)
property.set(self, property.default_for(self))
end
(properties | relationships.values).each do |subject|
next if subject.respond_to?(:serial?) && subject.serial? ||
subject.loaded?(self) ||
!subject.default?

subject.set(self, subject.default_for(self))
end

@_repository = repository
Expand Down
33 changes: 33 additions & 0 deletions lib/dm-core/support/subject.rb
@@ -0,0 +1,33 @@
module DataMapper
module Subject
# Returns a default value of the subject for given resource
#
# When default value is a callable object, it is called with resource
# and subject passed as arguments.
#
# @param [Resource] resource
# the model instance for which the default is to be set
#
# @return [Object]
# the default value of this subject for +resource+
#
# @api semipublic
def default_for(resource)
if @default.respond_to?(:call)
@default.call(resource, self)
else
@default.try_dup
end
end

# Returns true if the subject has a default value
#
# @return [Boolean]
# true if the subject has a default value
#
# @api semipublic
def default?
@options.key?(:default)
end
end
end
40 changes: 40 additions & 0 deletions spec/semipublic/associations/many_to_many_spec.rb
@@ -0,0 +1,40 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))

describe 'One to Many Associations' do
before :all do
module ::Blog
class Article
include DataMapper::Resource

property :title, String, :key => true
property :body, Text, :required => true
end
end

@article_model = Blog::Article
end

supported_by :all do
before :all do
@article = @article_model.new(:title => 'DataMapper Rocks!', :body => 'TSIA')
end

describe 'acts like a subject' do
before do
n = @article_model.n

@subject_without_default = @article_model.has(n, :without_default, @article_model, :through => DataMapper::Resource)
@subject_with_default = @article_model.has(n, :with_default, @article_model, :through => DataMapper::Resource, :default => [ @article ])
@subject_with_default_callable = @article_model.has(n, :with_default_callable, @article_model, :through => DataMapper::Resource, :default => lambda { |resource, relationship| [ @article ] })

@subject_without_default_value = []
@subject_with_default_value = [ @article ]
@subject_with_default_callable_value = [ @article ]

@resource = @article_model.new
end

it_should_behave_like 'A semipublic Subject'
end
end
end
16 changes: 16 additions & 0 deletions spec/semipublic/associations/many_to_one_spec.rb
Expand Up @@ -32,5 +32,21 @@ class ::Comment
end

it_should_behave_like 'A semipublic Resource'

describe 'acts like a subject' do
before do
@subject_without_default = @user_model.belongs_to(:without_default, @user_model)
@subject_with_default = @user_model.belongs_to(:with_default, @user_model, :default => @user)
@subject_with_default_callable = @user_model.belongs_to(:with_default_callable, @user_model, :default => lambda { |resource, relationship| @user })

@subject_without_default_value = nil
@subject_with_default_value = @user
@subject_with_default_callable_value = @user

@resource = @user_model.new
end

it_should_behave_like 'A semipublic Subject'
end
end
end
40 changes: 40 additions & 0 deletions spec/semipublic/associations/one_to_many_spec.rb
@@ -0,0 +1,40 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))

describe 'One to Many Associations' do
before :all do
module ::Blog
class Article
include DataMapper::Resource

property :title, String, :key => true
property :body, Text, :required => true
end
end

@article_model = Blog::Article
end

supported_by :all do
before :all do
@article = @article_model.new(:title => 'DataMapper Rocks!', :body => 'TSIA')
end

describe 'acts like a subject' do
before do
n = @article_model.n

@subject_without_default = @article_model.has(n, :without_default, @article_model)
@subject_with_default = @article_model.has(n, :with_default, @article_model, :default => [ @article ])
@subject_with_default_callable = @article_model.has(n, :with_default_callable, @article_model, :default => lambda { |resource, relationship| [ @article ] })

@subject_without_default_value = []
@subject_with_default_value = [ @article ]
@subject_with_default_callable_value = [ @article ]

@resource = @article_model.new
end

it_should_behave_like 'A semipublic Subject'
end
end
end
38 changes: 38 additions & 0 deletions spec/semipublic/associations/one_to_one_spec.rb
@@ -0,0 +1,38 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))

describe 'One to One Associations' do
before :all do
module ::Blog
class Article
include DataMapper::Resource

property :title, String, :key => true
property :body, Text, :required => true
end
end

@article_model = Blog::Article
end

supported_by :all do
before :all do
@article = @article_model.new(:title => 'DataMapper Rocks!', :body => 'TSIA')
end

describe 'acts like a subject' do
before do
@subject_without_default = @article_model.has(1, :without_default, @article_model)
@subject_with_default = @article_model.has(1, :with_default, @article_model, :default => @article)
@subject_with_default_callable = @article_model.has(1, :with_default_callable, @article_model, :default => lambda { |resource, relationship| @article })

@subject_without_default_value = nil
@subject_with_default_value = @article
@subject_with_default_callable_value = @article

@resource = @article_model.new
end

it_should_behave_like 'A semipublic Subject'
end
end
end

0 comments on commit 540e0de

Please sign in to comment.