active-fedora 3.0.1 → 3.0.3

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.
@@ -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