active-fedora 3.0.1 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,12 @@
1
+ 3.0.3
2
+
3
+ Added HasAndBelongsToManyAssociation
4
+
5
+ 3.0.2
6
+
7
+ YANKED
8
+
9
+
1
10
  3.0.1
2
11
 
3
12
  HYDRA-654 -- Fixed reification using has_model as the class instead of active_fedora_model
@@ -38,6 +38,7 @@
38
38
  :is_metadata_for: isMetadataFor
39
39
  :is_part_of: isPartOf
40
40
  :is_subset_of: isSubsetOf
41
+ :is_topic_of: isTopicOf
41
42
  info:fedora/fedora-system:def/model#:
42
43
  :has_model: hasModel
43
44
  # http://www.openarchives.org/OAI/2.0/:
@@ -7,6 +7,8 @@ module ActiveFedora
7
7
 
8
8
  autoload :HasManyAssociation, 'active_fedora/associations/has_many_association'
9
9
  autoload :BelongsToAssociation, 'active_fedora/associations/belongs_to_association'
10
+ autoload :HasAndBelongsToManyAssociation, 'active_fedora/associations/has_and_belongs_to_many_association'
11
+
10
12
 
11
13
  autoload :AssociationCollection, 'active_fedora/associations/association_collection'
12
14
  autoload :AssociationProxy, 'active_fedora/associations/association_proxy'
@@ -50,6 +52,73 @@ module ActiveFedora
50
52
  end
51
53
 
52
54
 
55
+ # Specifies a many-to-many relationship with another class. The relatioship is written to both classes simultaneously.
56
+ #
57
+ # Adds the following methods for retrieval and query:
58
+ #
59
+ # [collection(force_reload = false)]
60
+ # Returns an array of all the associated objects.
61
+ # An empty array is returned if none are found.
62
+ # [collection<<(object, ...)]
63
+ # Adds one or more objects to the collection by creating associations in the join table
64
+ # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
65
+ # Note that this operation instantly fires update sql without waiting for the save or update call on the
66
+ # parent object.
67
+ # [collection.delete(object, ...)]
68
+ # Removes one or more objects from the collection by removing their associations from the join table.
69
+ # This does not destroy the objects.
70
+ # [collection=objects]
71
+ # Replaces the collection's content by deleting and adding objects as appropriate.
72
+ # [collection_singular_ids]
73
+ # Returns an array of the associated objects' ids.
74
+ # [collection_singular_ids=ids]
75
+ # Replace the collection by the objects identified by the primary keys in +ids+.
76
+ # [collection.clear]
77
+ # Removes every object from the collection. This does not destroy the objects.
78
+ # [collection.empty?]
79
+ # Returns +true+ if there are no associated objects.
80
+ # [collection.size]
81
+ # Returns the number of associated objects.
82
+ #
83
+ # (+collection+ is replaced with the symbol passed as the first argument, so
84
+ # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.)
85
+ #
86
+ # === Example
87
+ #
88
+ # A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
89
+ # * <tt>Developer#projects</tt>
90
+ # * <tt>Developer#projects<<</tt>
91
+ # * <tt>Developer#projects.delete</tt>
92
+ # * <tt>Developer#projects=</tt>
93
+ # * <tt>Developer#project_ids</tt>
94
+ # * <tt>Developer#project_ids=</tt>
95
+ # * <tt>Developer#projects.clear</tt>
96
+ # * <tt>Developer#projects.empty?</tt>
97
+ # * <tt>Developer#projects.size</tt>
98
+ # * <tt>Developer#projects.find(id)</tt>
99
+ # * <tt>Developer#projects.exists?(...)</tt>
100
+ # The declaration may include an options hash to specialize the behavior of the association.
101
+ #
102
+ # === Options
103
+ #
104
+ # [:class_name]
105
+ # Specify the class name of the association. Use it only if that name can't be inferred
106
+ # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
107
+ # Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
108
+ # [:property]
109
+ # <b>REQUIRED</b> Specify the predicate to use when storing the relationship.
110
+ #
111
+ # Option examples:
112
+ # has_and_belongs_to_many :projects, :property=>:works_on
113
+ # has_and_belongs_to_many :nations, :class_name => "Country", :property=>:is_citizen_of
114
+ def has_and_belongs_to_many(association_id, options = {}, &extension)
115
+ reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
116
+ collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
117
+ #configure_after_destroy_method_for_has_and_belongs_to_many(reflection)
118
+ #add_association_callbacks(reflection.name, options)
119
+ end
120
+
121
+
53
122
  private
54
123
 
55
124
  def create_has_many_reflection(association_id, options)
@@ -60,6 +129,10 @@ module ActiveFedora
60
129
  create_reflection(:belongs_to, association_id, options, self)
61
130
  end
62
131
 
132
+ def create_has_and_belongs_to_many_reflection(association_id, options)
133
+ create_reflection(:has_and_belongs_to_many, association_id, options, self)
134
+ end
135
+
63
136
  def association_accessor_methods(reflection, association_proxy_class)
64
137
  redefine_method(reflection.name) do |*params|
65
138
  force_reload = params.first unless params.empty?
@@ -69,7 +69,7 @@ module ActiveFedora
69
69
  # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
70
70
  def <<(*records)
71
71
  result = true
72
- load_target if @owner.new_record?
72
+ load_target unless loaded?
73
73
 
74
74
  flatten_deeper(records).each do |record|
75
75
  raise_on_type_mismatch(record)
@@ -0,0 +1,117 @@
1
+ module ActiveFedora
2
+ # = Active Fedora Has And Belongs To Many Association
3
+ module Associations
4
+ class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
5
+ def initialize(owner, reflection)
6
+ super
7
+ end
8
+
9
+ def find_target
10
+ @owner.load_outbound_relationship(@reflection.name.to_s, @reflection.options[:property])
11
+ end
12
+
13
+
14
+ # def create(attributes = {})
15
+ # create_record(attributes) { |record| insert_record(record) }
16
+ # end
17
+
18
+ # def create!(attributes = {})
19
+ # create_record(attributes) { |record| insert_record(record, true) }
20
+ # end
21
+
22
+ def columns
23
+ @reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
24
+ end
25
+
26
+ # def reset_column_information
27
+ # @reflection.reset_column_information
28
+ # end
29
+
30
+ # def has_primary_key?
31
+ # @has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table])
32
+ # end
33
+
34
+ protected
35
+ # def construct_find_options!(options)
36
+ # options[:joins] = Arel::SqlLiteral.new @join_sql
37
+ # options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
38
+ # options[:select] ||= (@reflection.options[:select] || Arel::SqlLiteral.new('*'))
39
+ # end
40
+
41
+ def count_records
42
+ load_target.size
43
+ end
44
+
45
+ def insert_record(record, force = true, validate = true)
46
+ if record.new_record?
47
+ if force
48
+ record.save!
49
+ else
50
+ return false unless record.save(:validate => validate)
51
+ end
52
+ end
53
+
54
+ ### TODO save relationship
55
+ @owner.add_relationship(@reflection.options[:property], record)
56
+ record.add_relationship(@reflection.options[:property], @owner)
57
+ record.save
58
+ return true
59
+ end
60
+
61
+ def delete_records(records)
62
+ records.each do |r|
63
+ r.remove_relationship(@reflection.options[:property], @owner)
64
+ end
65
+ end
66
+
67
+ # def construct_sql
68
+ # if @reflection.options[:finder_sql]
69
+ # @finder_sql = interpolate_and_sanitize_sql(@reflection.options[:finder_sql])
70
+ # else
71
+ # @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
72
+ # @finder_sql << " AND (#{conditions})" if conditions
73
+ # end
74
+
75
+ # @join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
76
+
77
+ # construct_counter_sql
78
+ # end
79
+
80
+ def construct_scope
81
+ { :find => { :conditions => @finder_sql,
82
+ :joins => @join_sql,
83
+ :readonly => false,
84
+ :order => @reflection.options[:order],
85
+ :include => @reflection.options[:include],
86
+ :limit => @reflection.options[:limit] } }
87
+ end
88
+
89
+ # Join tables with additional columns on top of the two foreign keys must be considered
90
+ # ambiguous unless a select clause has been explicitly defined. Otherwise you can get
91
+ # broken records back, if, for example, the join column also has an id column. This will
92
+ # then overwrite the id column of the records coming back.
93
+ def finding_with_ambiguous_select?(select_clause)
94
+ !select_clause && columns.size != 2
95
+ end
96
+
97
+ private
98
+ # def create_record(attributes, &block)
99
+ # # Can't use Base.create because the foreign key may be a protected attribute.
100
+ # ensure_owner_is_not_new
101
+ # if attributes.is_a?(Array)
102
+ # attributes.collect { |attr| create(attr) }
103
+ # else
104
+ # build_record(attributes, &block)
105
+ # end
106
+ # end
107
+
108
+ def record_timestamp_columns(record)
109
+ if record.record_timestamps
110
+ record.send(:all_timestamp_attributes).map { |x| x.to_s }
111
+ else
112
+ []
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -139,6 +139,10 @@ module ActiveFedora
139
139
  @metadata_is_dirty == false
140
140
  return result
141
141
  end
142
+
143
+ def save!
144
+ save
145
+ end
142
146
 
143
147
  # Refreshes the object's info from Fedora
144
148
  # Note: Currently just registers any new datastreams that have appeared in fedora
@@ -5,7 +5,7 @@ module ActiveFedora
5
5
  module ClassMethods
6
6
  def create_reflection(macro, name, options, active_fedora)
7
7
  case macro
8
- when :has_many, :belongs_to
8
+ when :has_many, :belongs_to, :has_and_belongs_to_many
9
9
  klass = AssociationReflection
10
10
  reflection = klass.new(macro, name, options, active_fedora)
11
11
  end
@@ -552,7 +552,7 @@ module ActiveFedora
552
552
  return predicates[predicate], namespace
553
553
  end
554
554
  end
555
- raise ActiveFedora::UnregisteredPredicateError
555
+ raise ActiveFedora::UnregisteredPredicateError, "Unregistered predicate: #{predicate.inspect}"
556
556
  end
557
557
 
558
558
 
@@ -1,3 +1,3 @@
1
1
  module ActiveFedora
2
- VERSION = "3.0.1"
2
+ VERSION = "3.0.3"
3
3
  end
@@ -6,80 +6,136 @@ end
6
6
 
7
7
  class Book < ActiveFedora::Base
8
8
  belongs_to :library, :property=>:has_constituent
9
+ has_and_belongs_to_many :topics, :property=>:is_topic_of
10
+ end
11
+
12
+ class Topic < ActiveFedora::Base
13
+ has_and_belongs_to_many :books, :property=>:is_topic_of
9
14
  end
10
15
 
11
16
  describe ActiveFedora::Base do
12
17
  describe "an unsaved instance" do
13
- before do
14
- @library = Library.new()
15
- @book = Book.new
16
- @book.save
17
- @book2 = Book.new
18
- @book2.save
19
- end
20
-
21
- it "should let you shift onto the association" do
22
- @library.new_record?.should be_true
23
- @library.books.size == 0
24
- @library.books.to_ary.should == []
25
- @library.book_ids.should ==[]
26
- @library.books << @book
27
- @library.books.map(&:pid).should == [@book.pid]
28
- @library.book_ids.should ==[@book.pid]
29
- end
30
-
31
- it "should let you set an array of objects" do
32
- @library.books = [@book, @book2]
33
- @library.books.map(&:pid).should == [@book.pid, @book2.pid]
34
- @library.save
35
-
36
- @library.books = [@book]
37
- @library.books.map(&:pid).should == [@book.pid]
38
-
39
- end
40
- it "should let you set an array of object ids" do
41
- @library.book_ids = [@book.pid, @book2.pid]
42
- @library.books.map(&:pid).should == [@book.pid, @book2.pid]
43
- end
44
-
45
- it "setter should wipe out previously saved relations" do
46
- @library.book_ids = [@book.pid, @book2.pid]
47
- @library.book_ids = [@book2.pid]
48
- @library.books.map(&:pid).should == [@book2.pid]
18
+ describe "of belongs_to" do
19
+ before do
20
+ @library = Library.new()
21
+ @book = Book.new
22
+ @book.save
23
+ @book2 = Book.new
24
+ @book2.save
25
+ end
26
+
27
+ it "should let you shift onto the association" do
28
+ @library.new_record?.should be_true
29
+ @library.books.size == 0
30
+ @library.books.to_ary.should == []
31
+ @library.book_ids.should ==[]
32
+ @library.books << @book
33
+ @library.books.map(&:pid).should == [@book.pid]
34
+ @library.book_ids.should ==[@book.pid]
35
+ end
36
+
37
+ it "should let you set an array of objects" do
38
+ @library.books = [@book, @book2]
39
+ @library.books.map(&:pid).should == [@book.pid, @book2.pid]
40
+ @library.save
41
+
42
+ @library.books = [@book]
43
+ @library.books.map(&:pid).should == [@book.pid]
49
44
 
45
+ end
46
+ it "should let you set an array of object ids" do
47
+ @library.book_ids = [@book.pid, @book2.pid]
48
+ @library.books.map(&:pid).should == [@book.pid, @book2.pid]
49
+ end
50
+
51
+ it "setter should wipe out previously saved relations" do
52
+ @library.book_ids = [@book.pid, @book2.pid]
53
+ @library.book_ids = [@book2.pid]
54
+ @library.books.map(&:pid).should == [@book2.pid]
55
+
56
+ end
57
+
58
+
59
+ after do
60
+ @book.delete
61
+ @book2.delete
62
+ end
50
63
  end
51
-
52
- after do
53
- @book.delete
54
- @book2.delete
64
+ describe "of has_many_and_belongs_to" do
65
+ before do
66
+ @topic1 = Topic.new
67
+ @topic1.save
68
+ @topic2 = Topic.new
69
+ @topic2.save
70
+ end
71
+ it "habtm should set relationships bidirectionally" do
72
+ @book = Book.new
73
+ @book.topics << @topic1
74
+ @book.topics.map(&:pid).should == [@topic1.pid]
75
+ Topic.find(@topic1.pid).books.map(&:pid).should == [] #Can't have saved it because @book isn't saved yet.
76
+ end
77
+ after do
78
+ @topic1.delete
79
+ @topic2.delete
80
+ end
55
81
  end
56
82
  end
57
83
 
84
+
58
85
 
59
- describe "a saved instance" do
60
- before do
61
- @library = Library.new()
62
- @library.save()
63
- @book = Book.new
64
- @book.save
65
- end
66
- it "should have many books once it has been saved" do
67
- @library.save
68
- @library.books << @book
69
-
70
- @book.library.pid.should == @library.pid
71
- @library.books.reload
72
- @library.books.map(&:pid).should == [@book.pid]
73
-
74
-
75
- @library2 = Library.find(@library.pid)
76
- @library2.books.map(&:pid).should == [@book.pid]
77
86
 
78
-
87
+ describe "a saved instance" do
88
+ describe "of belongs_to" do
89
+ before do
90
+ @library = Library.new()
91
+ @library.save()
92
+ @book = Book.new
93
+ @book.save
94
+ end
95
+ it "should have many books once it has been saved" do
96
+ @library.books << @book
97
+
98
+ @book.library.pid.should == @library.pid
99
+ @library.books.reload
100
+ @library.books.map(&:pid).should == [@book.pid]
101
+
102
+
103
+ @library2 = Library.find(@library.pid)
104
+ @library2.books.map(&:pid).should == [@book.pid]
105
+ end
106
+ after do
107
+ @library.delete
108
+ @book.delete
109
+ end
79
110
  end
80
- after do
81
- @library.delete
82
- @book.delete
111
+ describe "of has_many_and_belongs_to" do
112
+ before do
113
+ @topic1 = Topic.new
114
+ @topic1.save
115
+ @topic2 = Topic.new
116
+ @topic2.save
117
+ @book = Book.new
118
+ @book.save
119
+ end
120
+ it "habtm should set relationships bidirectionally" do
121
+ @book.topics << @topic1
122
+ @book.topics.map(&:pid).should == [@topic1.pid]
123
+ Topic.find(@topic1.pid).books.map(&:pid).should == [@book.pid] #Can't have saved it because @book isn't saved yet.
124
+ end
125
+ it "should save new child objects" do
126
+ @book.topics << Topic.new
127
+ @book.topics.first.pid.should_not be_nil
128
+ end
129
+ it "should clear out the old associtions" do
130
+ @book.topics = [@topic1]
131
+ @book.topics = [@topic2]
132
+ @book.topic_ids.should == [@topic2.pid]
133
+ end
134
+ after do
135
+ @book.delete
136
+ @topic1.delete
137
+ @topic2.delete
138
+ end
83
139
  end
84
140
  end
85
141
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active-fedora
3
3
  version: !ruby/object:Gem::Version
4
- hash: 5
4
+ hash: 1
5
5
  prerelease:
6
6
  segments:
7
7
  - 3
8
8
  - 0
9
- - 1
10
- version: 3.0.1
9
+ - 3
10
+ version: 3.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matt Zumwalt
@@ -520,6 +520,7 @@ files:
520
520
  - lib/active_fedora/associations/association_collection.rb
521
521
  - lib/active_fedora/associations/association_proxy.rb
522
522
  - lib/active_fedora/associations/belongs_to_association.rb
523
+ - lib/active_fedora/associations/has_and_belongs_to_many_association.rb
523
524
  - lib/active_fedora/associations/has_many_association.rb
524
525
  - lib/active_fedora/attribute_methods.rb
525
526
  - lib/active_fedora/base.rb