Skip to content

Commit cfd9dbc

Browse files
committed
+ Spec for Net::LDAP::Entry
Pretty much rewrote the code. It now also responds to respond_to? correctly. Most of that symbol manipulation is now in just one place.
1 parent c01dc9e commit cfd9dbc

File tree

2 files changed

+114
-55
lines changed

2 files changed

+114
-55
lines changed

lib/net/ldap/entry.rb

Lines changed: 63 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,9 @@ class LDAP
7171
#
7272
class Entry
7373
# This constructor is not generally called by user code.
74-
#--
75-
# Originally, myhash took a block so we wouldn't have to
76-
# make sure its elements returned empty arrays when necessary.
77-
# Got rid of that to enable marshalling of Entry objects,
78-
# but that doesn't work anyway, because Entry objects have
79-
# singleton methods. So we define a custom dump and load.
74+
#
8075
def initialize dn = nil # :nodoc:
81-
@myhash = {} # originally: Hash.new {|k,v| k[v] = [] }
76+
@myhash = {}
8277
@myhash[:dn] = [dn]
8378
end
8479

@@ -95,20 +90,18 @@ def _load entry
9590
#--
9691
# Discovered bug, 26Aug06: I noticed that we're not converting the
9792
# incoming value to an array if it isn't already one.
98-
def []= name, value # :nodoc:
99-
sym = name.to_s.downcase.intern
93+
def []=(name, value) # :nodoc:
94+
sym = attribute_name(name)
10095
value = [value] unless value.is_a?(Array)
10196
@myhash[sym] = value
10297
end
10398

104-
10599
#--
106-
# We have to deal with this one as we do with []=
107-
# because this one and not the other one gets called
108-
# in formulations like entry["CN"] << cn.
100+
# We have to deal with this one as we do with []= because this one and not
101+
# the other one gets called in formulations like entry["CN"] << cn.
109102
#
110-
def [] name # :nodoc:
111-
name = name.to_s.downcase.intern unless name.is_a?(Symbol)
103+
def [](name) # :nodoc:
104+
name = attribute_name(name) unless name.is_a?(Symbol)
112105
@myhash[name] || []
113106
end
114107

@@ -139,8 +132,6 @@ def each
139132

140133
alias_method :each_attribute, :each
141134

142-
143-
144135
# Converts the Entry to a String, representing the
145136
# Entry's attributes in LDIF format.
146137
#--
@@ -166,24 +157,15 @@ def to_ldif
166157

167158
#--
168159
# TODO, doesn't support broken lines.
169-
# It generates a SINGLE Entry object from an incoming LDIF stream
170-
# which is of course useless for big LDIF streams that encode
171-
# many objects.
160+
# It generates a SINGLE Entry object from an incoming LDIF stream which is
161+
# of course useless for big LDIF streams that encode many objects.
162+
#
172163
# DO NOT DOCUMENT THIS METHOD UNTIL THESE RESTRICTIONS ARE LIFTED.
173-
# As it is, it's useful for unmarshalling objects that we create,
174-
# but not for reading arbitrary LDIF files.
175-
# Eventually, we should have a class method that parses large LDIF
176-
# streams into individual LDIF blocks (delimited by blank lines)
177-
# and passes them here.
178164
#
179-
# There is one oddity, noticed by Matthias Tarasiewicz: as originally
180-
# written, this code would return an Entry object in which the DN
181-
# attribute consisted of a two-element array, and the first element was
182-
# nil. That's because Entry#initialize doesn't like to create an object
183-
# without a DN attribute so it adds one: nil. The workaround here is
184-
# to wipe out the nil DN after creating the Entry object, and trust the
185-
# LDIF string to fill it in. If it doesn't we return a nil at the end.
186-
# (30Sep06, FCianfrocca)
165+
# As it is, it's useful for unmarshalling objects that we create, but not
166+
# for reading arbitrary LDIF files. Eventually, we should have a class
167+
# method that parses large LDIF streams into individual LDIF blocks
168+
# (delimited by blank lines) and passes them here.
187169
#
188170
class << self
189171
def from_single_ldif_string ldif
@@ -202,35 +184,42 @@ def from_single_ldif_string ldif
202184
entry.dn ? entry : nil
203185
end
204186
end
187+
188+
#--
189+
# Part of the support for getter and setter style access to attributes.
190+
#
191+
def respond_to?(sym)
192+
name = attribute_name(sym)
193+
return true if valid_attribute?(name)
194+
return super
195+
end
205196

206197
#--
207-
# Convenience method to convert unknown method names
208-
# to attribute references. Of course the method name
209-
# comes to us as a symbol, so let's save a little time
210-
# and not bother with the to_s.downcase two-step.
211-
# Of course that means that a method name like mAIL
212-
# won't work, but we shouldn't be encouraging that
213-
# kind of bad behavior in the first place.
214-
# Maybe we should thow something if the caller sends
215-
# arguments or a block...
198+
# Supports getter and setter style access for all the attributes that this
199+
# entry holds.
216200
#
217-
def method_missing *args, &block # :nodoc:
218-
s = args[0].to_s.downcase.intern
219-
if attribute_names.include?(s)
220-
self[s]
221-
elsif s.to_s[-1] == 61 and s.to_s.length > 1
222-
value = args[1] or raise RuntimeError.new( "unable to set value" )
223-
value = [value] unless value.is_a?(Array)
224-
name = s.to_s[0..-2].intern
225-
self[name] = value
226-
else
227-
raise NoMethodError.new( "undefined method '#{s}'" )
201+
def method_missing sym, *args, &block # :nodoc:
202+
name = attribute_name(sym)
203+
204+
if valid_attribute? name
205+
if setter?(sym) && args.size == 1
206+
value = args.first
207+
value = [value] unless value.instance_of?(Array)
208+
self[name]= value
209+
210+
return value
211+
elsif args.empty?
212+
return self[name]
213+
end
228214
end
215+
216+
super
229217
end
230218

231219
def write
232220
end
233221

222+
private
234223

235224
#--
236225
# Internal convenience method. It seems like the standard
@@ -249,8 +238,27 @@ def is_attribute_value_binary? value
249238
end
250239
false
251240
end
252-
private :is_attribute_value_binary?
253-
241+
242+
# Returns the symbol that can be used to access the attribute that
243+
# sym_or_str designates.
244+
#
245+
def attribute_name(sym_or_str)
246+
str = sym_or_str.to_s.downcase
247+
248+
# Does str match 'something='? Still only returns :something
249+
return str[0...-1].to_sym if str.size>1 && str[-1] == ?=
250+
return str.to_sym
251+
end
252+
253+
# Given a valid attribute symbol, returns true.
254+
#
255+
def valid_attribute?(attr_name)
256+
attribute_names.include?(attr_name)
257+
end
258+
259+
def setter?(sym)
260+
sym.to_s[-1] == ?=
261+
end
254262
end # class Entry
255263

256264

spec/unit/ldap/entry_spec.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require 'spec_helper'
2+
3+
describe Net::LDAP::Entry do
4+
attr_reader :entry
5+
before(:each) do
6+
@entry = Net::LDAP::Entry.from_single_ldif_string(
7+
%Q{dn: something
8+
foo: foo
9+
barAttribute: bar
10+
}
11+
)
12+
end
13+
14+
describe "entry access" do
15+
it "should always respond to #dn" do
16+
entry.should respond_to(:dn)
17+
end
18+
19+
context "<- #foo" do
20+
it "should respond_to?" do
21+
entry.should respond_to(:foo)
22+
end
23+
it "should return 'foo'" do
24+
entry.foo.should == ['foo']
25+
end
26+
end
27+
context "<- #Foo" do
28+
it "should respond_to?" do
29+
entry.should respond_to(:Foo)
30+
end
31+
it "should return 'foo'" do
32+
entry.foo.should == ['foo']
33+
end
34+
end
35+
context "<- #foo=" do
36+
it "should respond_to?" do
37+
entry.should respond_to(:foo=)
38+
end
39+
it "should set 'foo'" do
40+
entry.foo= 'bar'
41+
entry.foo.should == ['bar']
42+
end
43+
end
44+
context "<- #fOo=" do
45+
it "should return 'foo'" do
46+
entry.fOo= 'bar'
47+
entry.fOo.should == ['bar']
48+
end
49+
end
50+
end
51+
end

0 commit comments

Comments
 (0)