xcodeproj 0.1.0 → 0.2.0

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.
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Xcodeproj
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/CocoaPods/Xcodeproj.png)](https://secure.travis-ci.org/CocoaPods/Xcodeproj)
3
+ [![Master Build Status](https://secure.travis-ci.org/CocoaPods/Xcodeproj.png?branch=master)](https://secure.travis-ci.org/CocoaPods/Xcodeproj)
4
+ [![Develop Build Status](https://secure.travis-ci.org/CocoaPods/Xcodeproj.png?branch=develop)](https://secure.travis-ci.org/CocoaPods/Xcodeproj)
4
5
 
5
6
  Xcodeproj lets you create and modify Xcode projects from [Ruby][ruby].
6
7
  Script boring management tasks or build Xcode-friendly libraries. Also includes
@@ -15,7 +16,7 @@ static library from scratch, for both iOS and OSX.
15
16
  Xcodeproj itself installs through RubyGems, the Ruby package manager. Install it
16
17
  by performing the following command:
17
18
 
18
- $ sudo gem install xcodeproj
19
+ $ [sudo] gem install xcodeproj --pre
19
20
 
20
21
 
21
22
  ## Colaborate
@@ -31,13 +32,6 @@ If you're really oldschool and you want to discuss Xcodeproj development you
31
32
  can join #cocoapods on irc.freenode.net.
32
33
 
33
34
 
34
- ## Authors
35
-
36
- * [Nolan Waite](https://github.com/nolanw)
37
- * [Luke Redpath](https://github.com/lukeredpath)
38
- * [Eloy Durán](https://github.com/alloy)
39
-
40
-
41
35
  ## LICENSE
42
36
 
43
37
  These works are available under the MIT license. See the [LICENSE][license] file
@@ -37,6 +37,7 @@ end
37
37
  have_header 'CoreFoundation/CoreFoundation.h'
38
38
  have_header 'CoreFoundation/CFStream.h'
39
39
  have_header 'CoreFoundation/CFPropertyList.h'
40
+ have_header 'ruby/st.h'
40
41
 
41
42
  create_header
42
43
  create_makefile 'xcodeproj_ext'
@@ -4,7 +4,10 @@
4
4
  #include "extconf.h"
5
5
 
6
6
  #include "ruby.h"
7
+ #if HAVE_RUBY_ST_H
7
8
  #include "ruby/st.h"
9
+ #endif
10
+
8
11
  #include "CoreFoundation/CoreFoundation.h"
9
12
  #include "CoreFoundation/CFStream.h"
10
13
  #include "CoreFoundation/CFPropertyList.h"
@@ -14,12 +17,22 @@ VALUE Xcodeproj = Qnil;
14
17
 
15
18
  static VALUE
16
19
  cfstr_to_str(const void *cfstr) {
17
- long len = (long)CFStringGetLength(cfstr);
20
+ CFDataRef data = CFStringCreateExternalRepresentation(NULL, cfstr, kCFStringEncodingUTF8, 0);
21
+ assert(data != NULL);
22
+ long len = (long)CFDataGetLength(data);
18
23
  char *buf = (char *)malloc(len+1);
19
24
  assert(buf != NULL);
20
- CFStringGetCString(cfstr, buf, len+1, kCFStringEncodingUTF8);
25
+ CFDataGetBytes(data, CFRangeMake(0, len), buf);
26
+
21
27
  register VALUE str = rb_str_new(buf, len);
22
28
  free(buf);
29
+
30
+ // force UTF-8 encoding in Ruby 1.9+
31
+ ID forceEncodingId = rb_intern("force_encoding");
32
+ if (rb_respond_to(str, forceEncodingId)) {
33
+ rb_funcall(str, forceEncodingId, 1, rb_str_new("UTF-8", 5));
34
+ }
35
+
23
36
  return str;
24
37
  }
25
38
 
@@ -29,7 +42,14 @@ str_to_cfstr(VALUE str) {
29
42
  return CFStringCreateWithCString(NULL, RSTRING_PTR(rb_String(str)), kCFStringEncodingUTF8);
30
43
  }
31
44
 
32
-
45
+ /* Generates a UUID. The original version is truncated, so this is not 100%
46
+ * guaranteed to be unique. However, the `PBXObject#generate_uuid` method
47
+ * checks that the UUID does not exist yet, in the project, before using it.
48
+ *
49
+ * @note Meant for internal use only.
50
+ *
51
+ * @return [String] A 24 characters long UUID.
52
+ */
33
53
  static VALUE
34
54
  generate_uuid(void) {
35
55
  CFUUIDRef uuid = CFUUIDCreate(NULL);
@@ -69,11 +89,22 @@ hash_set(const void *keyRef, const void *valueRef, void *hash) {
69
89
  value = rb_ary_new();
70
90
  CFIndex i, count = CFArrayGetCount(valueRef);
71
91
  for (i = 0; i < count; i++) {
72
- CFStringRef x = CFArrayGetValueAtIndex(valueRef, i);
73
- if (CFGetTypeID(x) == CFStringGetTypeID()) {
74
- rb_ary_push(value, cfstr_to_str(x));
92
+ CFTypeRef elementRef = CFArrayGetValueAtIndex(valueRef, i);
93
+ CFTypeID elementType = CFGetTypeID(elementRef);
94
+ if (elementType == CFStringGetTypeID()) {
95
+ rb_ary_push(value, cfstr_to_str(elementRef));
96
+
97
+ } else if (elementType == CFDictionaryGetTypeID()) {
98
+ VALUE hashElement = rb_hash_new();
99
+ CFDictionaryApplyFunction(elementRef, hash_set, (void *)hashElement);
100
+ rb_ary_push(value, hashElement);
101
+
75
102
  } else {
76
- rb_raise(rb_eTypeError, "Plist array value contains a object type unsupported by Xcodeproj.");
103
+ CFStringRef descriptionRef = CFCopyDescription(elementRef);
104
+ // obviously not optimial, but we're raising here, so it doesn't really matter
105
+ VALUE description = cfstr_to_str(descriptionRef);
106
+ rb_raise(rb_eTypeError, "Plist array value contains a object type unsupported by Xcodeproj. In: `%s'", RSTRING_PTR(description));
107
+ CFRelease(descriptionRef);
77
108
  }
78
109
  }
79
110
 
@@ -94,15 +125,26 @@ dictionary_set(st_data_t key, st_data_t value, CFMutableDictionaryRef dict) {
94
125
  0,
95
126
  &kCFTypeDictionaryKeyCallBacks,
96
127
  &kCFTypeDictionaryValueCallBacks);
97
- st_foreach(RHASH_TBL(value), dictionary_set, (st_data_t)valueRef);
128
+ rb_hash_foreach(value, dictionary_set, (st_data_t)valueRef);
98
129
 
99
130
  } else if (TYPE(value) == T_ARRAY) {
100
131
  long i, count = RARRAY_LEN(value);
101
132
  valueRef = CFArrayCreateMutable(NULL, count, &kCFTypeArrayCallBacks);
102
133
  for (i = 0; i < count; i++) {
103
- CFStringRef x = str_to_cfstr(RARRAY_PTR(value)[i]);
104
- CFArrayAppendValue((CFMutableArrayRef)valueRef, x);
105
- CFRelease(x);
134
+ VALUE element = RARRAY_PTR(value)[i];
135
+ CFTypeRef elementRef = NULL;
136
+ if (TYPE(element) == T_HASH) {
137
+ elementRef = CFDictionaryCreateMutable(NULL,
138
+ 0,
139
+ &kCFTypeDictionaryKeyCallBacks,
140
+ &kCFTypeDictionaryValueCallBacks);
141
+ rb_hash_foreach(element, dictionary_set, (st_data_t)elementRef);
142
+ } else {
143
+ // otherwise coerce to string
144
+ elementRef = str_to_cfstr(element);
145
+ }
146
+ CFArrayAppendValue((CFMutableArrayRef)valueRef, elementRef);
147
+ CFRelease(elementRef);
106
148
  }
107
149
 
108
150
  } else {
@@ -130,7 +172,19 @@ str_to_url(VALUE path) {
130
172
  }
131
173
 
132
174
 
133
- // TODO handle errors
175
+ /* @overload read_plist(path)
176
+ *
177
+ * Reads from the specified path and de-serializes the property list.
178
+ *
179
+ * @note This does not yet support all possible types that can exist in a valid property list.
180
+ *
181
+ * @note This currently only assumes to be given an Xcode project document.
182
+ * This means that it only accepts dictionaries, arrays, and strings in
183
+ * the document.
184
+ *
185
+ * @param [String] path The path to the property list file.
186
+ * @return [Hash] The dictionary contents of the document.
187
+ */
134
188
  static VALUE
135
189
  read_plist(VALUE self, VALUE path) {
136
190
  CFPropertyListRef dict;
@@ -159,6 +213,20 @@ read_plist(VALUE self, VALUE path) {
159
213
  return hash;
160
214
  }
161
215
 
216
+ /* @overload write_plist(hash, path)
217
+ *
218
+ * Writes the serialized contents of a property list to the specified path.
219
+ *
220
+ * @note This does not yet support all possible types that can exist in a valid property list.
221
+ *
222
+ * @note This currently only assumes to be given an Xcode project document.
223
+ * This means that it only accepts dictionaries, arrays, and strings in
224
+ * the document.
225
+ *
226
+ * @param [Hash] hash The property list to serialize.
227
+ * @param [String] path The path to the property list file.
228
+ * @return [true, false] Wether or not saving was successful.
229
+ */
162
230
  static VALUE
163
231
  write_plist(VALUE self, VALUE hash, VALUE path) {
164
232
  VALUE h = rb_check_convert_type(hash, T_HASH, "Hash", "to_hash");
@@ -171,7 +239,7 @@ write_plist(VALUE self, VALUE hash, VALUE path) {
171
239
  &kCFTypeDictionaryKeyCallBacks,
172
240
  &kCFTypeDictionaryValueCallBacks);
173
241
 
174
- st_foreach(RHASH_TBL(h), dictionary_set, (st_data_t)dict);
242
+ rb_hash_foreach(h, dictionary_set, (st_data_t)dict);
175
243
 
176
244
  CFURLRef fileURL = str_to_url(path);
177
245
  CFWriteStreamRef stream = CFWriteStreamCreateWithFile(NULL, fileURL);
@@ -1,31 +1,163 @@
1
1
  module Xcodeproj
2
+ # This class holds the data for a Xcode build settings file (xcconfig) and
3
+ # serializes it.
2
4
  class Config
3
- def initialize(xcconfig = {})
5
+ # Returns a new instance of Config
6
+ #
7
+ # @param [Hash, File, String] xcconfig_hash_or_file Initial data.
8
+ require 'set'
9
+
10
+ attr_accessor :attributes, :frameworks ,:libraries
11
+
12
+ def initialize(xcconfig_hash_or_file = {})
4
13
  @attributes = {}
5
- merge!(xcconfig)
14
+ @includes = []
15
+ @frameworks, @libraries = Set.new, Set.new
16
+ merge!(extract_hash(xcconfig_hash_or_file))
6
17
  end
7
18
 
19
+ # @return [Hash] The internal data.
8
20
  def to_hash
9
- @attributes
21
+ hash = @attributes.dup
22
+ flags = hash['OTHER_LDFLAGS'] || ''
23
+ flags = flags.dup.strip
24
+ flags << libraries.to_a.sort.reduce('') {| memo, l | memo << " -l#{l}" }
25
+ flags << frameworks.to_a.sort.reduce('') {| memo, f | memo << " -framework #{f}" }
26
+ hash['OTHER_LDFLAGS'] = flags.strip
27
+ hash.delete('OTHER_LDFLAGS') if flags.strip.empty?
28
+ hash
10
29
  end
11
30
 
31
+ def ==(other)
32
+ other.respond_to?(:to_hash) && other.to_hash == self.to_hash
33
+ end
34
+
35
+ # @return [Array] Config's include file list
36
+ # @example
37
+ #
38
+ # Consider following xcconfig file:
39
+ #
40
+ # #include "SomeConfig"
41
+ # Key1 = Value1
42
+ # Key2 = Value2
43
+ #
44
+ # config.includes # => [ "SomeConfig" ]
45
+ def includes
46
+ @includes
47
+ end
48
+
49
+ # Merges the given xcconfig hash or Config into the internal data.
50
+ #
51
+ # If a key in the given hash already exists, in the internal data, then its
52
+ # value is appended to the value in the internal data.
53
+ #
54
+ # @example
55
+ #
56
+ # config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
57
+ # config.merge!('OTHER_LDFLAGS' => '-lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"')
58
+ # config.to_hash # => { 'PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2 -lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"' }
59
+ #
60
+ # @param [Hash, Config] xcconfig The data to merge into the internal data.
12
61
  def merge!(xcconfig)
13
- xcconfig.to_hash.each do |key, value|
14
- if existing_value = @attributes[key]
15
- @attributes[key] = "#{existing_value} #{value}"
16
- else
17
- @attributes[key] = value
18
- end
62
+ if xcconfig.is_a? Config
63
+ @attributes.merge!(xcconfig.attributes) { |key, v1, v2| "#{v1} #{v2}" }
64
+ @libraries.merge xcconfig.libraries
65
+ @frameworks.merge xcconfig.frameworks
66
+ else
67
+ @attributes.merge!(xcconfig.to_hash) { |key, v1, v2| "#{v1} #{v2}" }
68
+ # Parse frameworks and libraries. Then remove the from the linker flags
69
+ flags = @attributes['OTHER_LDFLAGS']
70
+ return unless flags
71
+
72
+ frameworks = flags.scan(/-framework\s+([^\s]+)/).map { |m| m[0] }
73
+ libraries = flags.scan(/-l ?([^\s]+)/).map { |m| m[0] }
74
+ @frameworks.merge frameworks
75
+ @libraries.merge libraries
76
+
77
+ new_flags = flags.dup
78
+ frameworks.each {|f| new_flags.gsub!("-framework #{f}", "") }
79
+ libraries.each {|l| new_flags.gsub!("-l#{l}", ""); new_flags.gsub!("-l #{l}", "") }
80
+ @attributes['OTHER_LDFLAGS'] = new_flags.gsub("\w*", ' ').strip
19
81
  end
20
82
  end
21
83
  alias_method :<<, :merge!
22
84
 
85
+ def merge(config)
86
+ self.dup.tap { |x|x.merge!(config) }
87
+ end
88
+
89
+ def dup
90
+ Xcodeproj::Config.new(self.to_hash.dup)
91
+ end
92
+
93
+ # Serializes the internal data in the xcconfig format.
94
+ #
95
+ # @example
96
+ #
97
+ # config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
98
+ # config.to_s # => "PODS_ROOT = \"$(SRCROOT)/Pods\"\nOTHER_LDFLAGS = -lxml2"
99
+ #
100
+ # @return [String] The serialized internal data.
23
101
  def to_s
24
- @attributes.map { |key, value| "#{key} = #{value}" }.join("\n")
102
+ to_hash.map { |key, value| "#{key} = #{value}" }.join("\n")
103
+ end
104
+
105
+ def inspect
106
+ to_hash.inspect
25
107
  end
26
108
 
109
+ # Writes the serialized representation of the internal data to the given
110
+ # path.
111
+ #
112
+ # @param [Pathname] pathname The file that the data should be written to.
27
113
  def save_as(pathname)
28
114
  pathname.open('w') { |file| file << to_s }
29
115
  end
116
+
117
+ private
118
+
119
+ def extract_hash(argument)
120
+ if argument.respond_to? :read
121
+ hash_from_file_content(argument.read)
122
+ elsif File.readable? argument.to_s
123
+ hash_from_file_content(File.read(argument))
124
+ else
125
+ argument
126
+ end
127
+ end
128
+
129
+ def hash_from_file_content(raw_string)
130
+ hash = {}
131
+ raw_string.split("\n").each do |line|
132
+ uncommented_line = strip_comment(line)
133
+ if include = extract_include(uncommented_line)
134
+ @includes.push include
135
+ else
136
+ key, value = extract_key_value(uncommented_line)
137
+ hash[key] = value if key
138
+ end
139
+ end
140
+ hash
141
+ end
142
+
143
+ def strip_comment(line)
144
+ line.partition('//').first
145
+ end
146
+
147
+ def extract_include(line)
148
+ regexp = /#include\s*"(.+)"/
149
+ match = line.match(regexp)
150
+ match[1] if match
151
+ end
152
+
153
+ def extract_key_value(line)
154
+ key, value = line.split('=', 2)
155
+ if key && value
156
+ [key.strip, value.strip]
157
+ else
158
+ []
159
+ end
160
+ end
161
+
30
162
  end
31
163
  end
@@ -189,7 +189,7 @@ module Xcodeproj
189
189
  if first_letter_in_uppercase
190
190
  lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
191
191
  else
192
- lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
192
+ lower_case_and_underscored_word[0,1].downcase + camelize(lower_case_and_underscored_word)[1..-1]
193
193
  end
194
194
  end
195
195
 
@@ -0,0 +1,51 @@
1
+ module Xcodeproj
2
+ class Project
3
+ module Object
4
+ class Association
5
+
6
+ class HasMany < Association
7
+ def direct_get
8
+ uuids = @owner.send(@reflection.attribute_getter)
9
+ if @block
10
+ # Evaluate the block, which was specified at the class level, in
11
+ # the instance’s context.
12
+ @owner.list_by_class(uuids, @reflection.klass) do |list|
13
+ list.let(:push) do |new_object|
14
+ @owner.instance_exec(new_object, &@block)
15
+ end
16
+ end
17
+ else
18
+ @owner.list_by_class(uuids, @reflection.klass)
19
+ end
20
+ end
21
+
22
+ def inverse_get
23
+ PBXObjectList.new(@reflection.klass, @owner.project) do |list|
24
+ list.let(:uuid_scope) do
25
+ @owner.project.objects.list_by_class(@reflection.klass).select do |object|
26
+ object.send(@reflection.inverse.attribute_getter) == @owner.uuid
27
+ end.map(&:uuid)
28
+ end
29
+ list.let(:push) do |new_object|
30
+ new_object.send(@reflection.inverse.attribute_setter, @owner.uuid)
31
+ end
32
+ end
33
+ end
34
+
35
+ # @todo Currently this does not call the @block, which means that
36
+ # in theory (like with a group's children) the object stays
37
+ # asociated with its old group.
38
+ def direct_set(list)
39
+ @owner.send(@reflection.attribute_setter, list.map(&:uuid))
40
+ end
41
+
42
+ def inverse_set(list)
43
+ raise NotImplementedError
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,39 @@
1
+ module Xcodeproj
2
+ class Project
3
+ module Object
4
+ class Association
5
+
6
+ # @todo Does this need 'new object'-callback support too?
7
+ class HasOne < Association
8
+ def direct_get
9
+ @owner.project.objects[@owner.send(@reflection.attribute_getter)]
10
+ end
11
+
12
+ def inverse_get
13
+ # Loop over all objects of the class and find the one that includes
14
+ # this object in the specified uuid list.
15
+ @owner.project.objects.list_by_class(@reflection.klass).find do |object|
16
+ object.send(@reflection.inverse.attribute_getter).include?(@owner.uuid)
17
+ end
18
+ end
19
+
20
+ def direct_set(object)
21
+ @owner.send(@reflection.attribute_setter, object.uuid)
22
+ end
23
+
24
+ def inverse_set(object)
25
+ # Remove this object from the uuid list of the target
26
+ # that this object was associated to.
27
+ if previous = @owner.send(@reflection.name)
28
+ previous.send(@reflection.inverse.attribute_getter).delete(@owner.uuid)
29
+ end
30
+ # Now assign this object to the new object
31
+ object.send(@reflection.inverse.attribute_getter) << @owner.uuid if object
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,88 @@
1
+ require 'xcodeproj/inflector'
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+
7
+ class AbstractPBXObject
8
+ def self.reflections
9
+ @reflections ||= []
10
+ end
11
+
12
+ def self.create_reflection(type, name, options)
13
+ (reflections << Association::Reflection.new(type, name, options)).last
14
+ end
15
+
16
+ def self.reflection(name)
17
+ reflections.find { |r| r.name.to_s == name.to_s }
18
+ end
19
+ end
20
+
21
+ class Association
22
+ class Reflection
23
+ def initialize(type, name, options)
24
+ @type, @name, @options = type, name.to_s, options
25
+ end
26
+
27
+ attr_reader :type, :name, :options
28
+
29
+ def klass
30
+ @options[:class] ||= begin
31
+ name = "PBX#{@name.classify}"
32
+ name = "XC#{@name.classify}" unless Xcodeproj::Project::Object.const_defined?(name)
33
+ Xcodeproj::Project::Object.const_get(name)
34
+ end
35
+ end
36
+
37
+ def inverse
38
+ klass.reflection(@options[:inverse_of])
39
+ end
40
+
41
+ def inverse?
42
+ !!@options[:inverse_of]
43
+ end
44
+
45
+ def attribute_name
46
+ (@options[:uuid] || @options[:uuids] || @name).to_sym
47
+ end
48
+
49
+ def attribute_getter
50
+ case type
51
+ when :has_many
52
+ uuid_method_name.pluralize
53
+ when :has_one
54
+ uuid_method_name
55
+ end.to_sym
56
+ end
57
+
58
+ def attribute_setter
59
+ "#{attribute_getter}=".to_sym
60
+ end
61
+
62
+ def getter
63
+ @name.to_sym
64
+ end
65
+
66
+ def setter
67
+ "#{@name}=".to_sym
68
+ end
69
+
70
+ def association_for(owner, &block)
71
+ case type
72
+ when :has_many then Association::HasMany
73
+ when :has_one then Association::HasOne
74
+ end.new(owner, self, &block)
75
+ end
76
+
77
+ private
78
+
79
+ def uuid_method_name
80
+ (@options[:uuids_as] || @options[:uuid] || @options[:uuids] || "#{@name.singularize}_reference").to_s.singularize
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,54 @@
1
+ require 'xcodeproj/project/association/has_many'
2
+ require 'xcodeproj/project/association/has_one'
3
+ require 'xcodeproj/project/association/reflection'
4
+
5
+ module Xcodeproj
6
+ class Project
7
+ module Object
8
+
9
+ class AbstractPBXObject
10
+ class << self
11
+ def has_many(plural_attr_name, options = {}, &block)
12
+ create_association(:has_many, plural_attr_name, options, &block)
13
+ end
14
+
15
+ def has_one(singular_attr_name, options = {})
16
+ create_association(:has_one, singular_attr_name, options)
17
+ end
18
+
19
+ private
20
+
21
+ def create_association(type, name, options, &block)
22
+ reflection = create_reflection(type, name, options)
23
+ unless reflection.inverse?
24
+ attribute(reflection.attribute_name, :as => reflection.attribute_getter)
25
+ end
26
+ define_method(reflection.getter) do
27
+ reflection.association_for(self, &block).get
28
+ end
29
+ define_method(reflection.setter) do |new_value|
30
+ reflection.association_for(self, &block).set(new_value)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class Association
37
+ attr_reader :owner, :reflection
38
+
39
+ def initialize(owner, reflection, &block)
40
+ @owner, @reflection, @block = owner, reflection, block
41
+ end
42
+
43
+ def get
44
+ @reflection.inverse? ? inverse_get : direct_get
45
+ end
46
+
47
+ def set(value)
48
+ @reflection.inverse? ? inverse_set(value) : direct_set(value)
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end