File: associations.rb

package info (click to toggle)
ruby-activeldap 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 1,588 kB
  • sloc: ruby: 18,143; sh: 12; makefile: 5
file content (205 lines) | stat: -rw-r--r-- 7,361 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
require 'active_ldap/association/belongs_to'
require 'active_ldap/association/belongs_to_many'
require 'active_ldap/association/has_many'
require 'active_ldap/association/has_many_wrap'

module ActiveLdap
  # Associations
  #
  # Associations provides the class methods needed for
  # the extension classes to create methods using
  # belongs_to and has_many
  module Associations
    def self.append_features(base)
      super
      base.extend(ClassMethods)
      base.class_attribute(:associations)
      base.associations ||= []
    end

    module ClassMethods
      def set_associated_class(name, klass)
        @associated_classes ||= {}
        @associated_classes[name.to_s] = klass
      end

      def associated_class(name)
        @associated_classes[name.to_s]
      end

      # belongs_to
      #
      # This defines a method for an extension class map its DN key
      # attribute value on to multiple items which reference it by
      # |:foreign_key| in the other LDAP entry covered by class
      # |:class_name|.
      #
      # Example:
      #  belongs_to :groups, :class_name => "Group",
      #             :many => "memberUid" # Group#memberUid
      #             # :primary_key => "uid" # User#uid
      #             ## deprecated since 1.1.0. Use :primary_key instead.
      #             ## :foreign_key => "uid" # User#uid
      #             # dn attribute value is used by default
      #  belongs_to :primary_group, :class_name => "Group",
      #             :foreign_key => "gidNumber", # User#gidNumber
      #             :primary_key => "gidNumber"  # Group#gidNumber
      #
      def belongs_to(association_id, options={})
        validate_belongs_to_options(options)
        klass = options[:class]
        klass ||= (options[:class_name] || association_id.to_s).classify
        foreign_key = options[:foreign_key]
        primary_key = options[:primary_key]
        many = options[:many]
        set_associated_class(association_id, klass)

        opts = {
          :association_id => association_id,
          :foreign_key_name => foreign_key,
          :primary_key_name => primary_key,
          :many => many,
          :extend => options[:extend],
        }
        if opts[:many]
          association_class = Association::BelongsToMany
          foreign_key_name = opts[:foreign_key_name]
          if foreign_key_name
            message = _(":foreign_key belongs_to(:many) option is " \
                        "deprecated since 1.1.0. Use :primary_key instead.")
            ActiveSupport::Deprecation.warn(message)
            opts[:primary_key_name] ||= foreign_key_name
          end
          opts[:primary_key_name] ||= dn_attribute
        else
          association_class = Association::BelongsTo
          opts[:foreign_key_name] ||= "#{association_id}_id"

          before_save do
            if instance_variable_defined?(:"@#{association_id}")
              association = instance_variable_get(:"@#{association_id}")
              if association and association.updated?
                self[association.__send__(:primary_key)] =
                  association[opts[:foreign_key_name]]
              end
            end
          end
        end

        association_accessor(association_id) do |target|
          association_class.new(target, opts)
        end
      end


      # has_many
      #
      # This defines a method for an extension class expand an
      # existing multi-element attribute into ActiveLdap objects.
      # This discards any calls which result in entries that
      # don't exist in LDAP!
      #
      # Example:
      #   has_many :primary_members, :class_name => "User",
      #            :primary_key => "gidNumber", # Group#gidNumber
      #            :foreign_key => "gidNumber"  # User#gidNumber
      #            ## deprecated since 1.1.0. Those options
      #            ## are inverted.
      #            # :primary_key => "gidNumber", # User#gidNumber
      #            # :foreign_key => "gidNumber"  # Group#gidNumber
      #   has_many :members, :class_name => "User",
      #            :wrap => "memberUid" # Group#memberUid
      def has_many(association_id, options = {})
        validate_has_many_options(options)
        klass = options[:class]
        klass ||= (options[:class_name] || association_id.to_s).classify
        foreign_key = options[:foreign_key]
        primary_key = options[:primary_key]
        set_associated_class(association_id, klass)

        opts = {
          :association_id => association_id,
          :foreign_key_name => foreign_key,
          :primary_key_name => primary_key,
          :wrap => options[:wrap],
          :extend => options[:extend],
        }
        if opts[:wrap]
          association_class = Association::HasManyWrap
        else
          association_class = Association::HasMany
          primary_key_name = opts[:primary_key_name]
          foreign_key_name = opts[:foreign_key_name]
          if primary_key_name != foreign_key_name and
              primary_key_name != "dn" and
              !new.have_attribute?(primary_key_name)
            message = _(":primary_key and :foreign_key has_many options are " \
                        "inverted their mean since 1.1.0. Please invert them.")
            ActiveSupport::Deprecation.warn(message)
            opts[:foreign_key_name] = primary_key_name
            opts[:primary_key_name] = foreign_key_name
          end
        end

        association_accessor(association_id) do |target|
          association_class.new(target, opts)
        end
      end

      private
      def association_accessor(name, &make_association)
        define_method("__make_#{name}") do
          make_association.call(self)
        end
        associations << name
        association_reader(name, &make_association)
        association_writer(name, &make_association)
      end

      def association_reader(name, &make_association)
        class_eval(<<-EOM, __FILE__, __LINE__ + 1)
          def #{name}
            @#{name} ||= __make_#{name}
          end
        EOM
      end

      def association_writer(name, &make_association)
        class_eval(<<-EOM, __FILE__, __LINE__ + 1)
          def #{name}=(new_value)
            association = defined?(@#{name}) ? @#{name} : nil
            association ||= __make_#{name}
            association.replace(new_value)
            @#{name} = new_value.nil? ? nil : association
            @#{name}
          end
        EOM
      end

      VALID_BELONGS_TO_OPTIONS = [:class, :class_name,
                                  :foreign_key, :primary_key, :many,
                                  :extend]
      def validate_belongs_to_options(options)
        options.assert_valid_keys(VALID_BELONGS_TO_OPTIONS)
      end

      VALID_HAS_MANY_OPTIONS = [:class, :class_name,
                                :foreign_key, :primary_key, :wrap,
                                :extend]
      def validate_has_many_options(options)
        options.assert_valid_keys(VALID_HAS_MANY_OPTIONS)
      end
    end

    def clear_association_cache
      return if new_record?
      (self.class.associations || []).each do |association|
        instance_variable_set("@#{association}", nil)
      end
    end
  end

  module Association
    autoload :Children, 'active_ldap/association/children'
  end
end