frodata 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +2 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +2 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +75 -0
  8. data/CHANGELOG.md +150 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE.txt +23 -0
  11. data/README.md +427 -0
  12. data/Rakefile +7 -0
  13. data/TODO.md +55 -0
  14. data/frodata.gemspec +34 -0
  15. data/lib/frodata.rb +36 -0
  16. data/lib/frodata/entity.rb +332 -0
  17. data/lib/frodata/entity_container.rb +75 -0
  18. data/lib/frodata/entity_set.rb +161 -0
  19. data/lib/frodata/errors.rb +68 -0
  20. data/lib/frodata/navigation_property.rb +29 -0
  21. data/lib/frodata/navigation_property/proxy.rb +80 -0
  22. data/lib/frodata/properties.rb +32 -0
  23. data/lib/frodata/properties/binary.rb +50 -0
  24. data/lib/frodata/properties/boolean.rb +37 -0
  25. data/lib/frodata/properties/collection.rb +50 -0
  26. data/lib/frodata/properties/complex.rb +114 -0
  27. data/lib/frodata/properties/date.rb +27 -0
  28. data/lib/frodata/properties/date_time.rb +83 -0
  29. data/lib/frodata/properties/date_time_offset.rb +17 -0
  30. data/lib/frodata/properties/decimal.rb +50 -0
  31. data/lib/frodata/properties/enum.rb +62 -0
  32. data/lib/frodata/properties/float.rb +67 -0
  33. data/lib/frodata/properties/geography.rb +13 -0
  34. data/lib/frodata/properties/geography/base.rb +162 -0
  35. data/lib/frodata/properties/geography/line_string.rb +33 -0
  36. data/lib/frodata/properties/geography/point.rb +31 -0
  37. data/lib/frodata/properties/geography/polygon.rb +38 -0
  38. data/lib/frodata/properties/guid.rb +17 -0
  39. data/lib/frodata/properties/integer.rb +107 -0
  40. data/lib/frodata/properties/number.rb +14 -0
  41. data/lib/frodata/properties/string.rb +72 -0
  42. data/lib/frodata/properties/time.rb +40 -0
  43. data/lib/frodata/properties/time_of_day.rb +27 -0
  44. data/lib/frodata/property.rb +139 -0
  45. data/lib/frodata/property_registry.rb +41 -0
  46. data/lib/frodata/query.rb +233 -0
  47. data/lib/frodata/query/criteria.rb +92 -0
  48. data/lib/frodata/query/criteria/comparison_operators.rb +49 -0
  49. data/lib/frodata/query/criteria/date_functions.rb +61 -0
  50. data/lib/frodata/query/criteria/geography_functions.rb +21 -0
  51. data/lib/frodata/query/criteria/lambda_operators.rb +27 -0
  52. data/lib/frodata/query/criteria/string_functions.rb +40 -0
  53. data/lib/frodata/query/in_batches.rb +58 -0
  54. data/lib/frodata/railtie.rb +19 -0
  55. data/lib/frodata/schema.rb +155 -0
  56. data/lib/frodata/schema/complex_type.rb +79 -0
  57. data/lib/frodata/schema/enum_type.rb +95 -0
  58. data/lib/frodata/service.rb +254 -0
  59. data/lib/frodata/service/request.rb +85 -0
  60. data/lib/frodata/service/response.rb +162 -0
  61. data/lib/frodata/service/response/atom.rb +40 -0
  62. data/lib/frodata/service/response/json.rb +41 -0
  63. data/lib/frodata/service/response/plain.rb +36 -0
  64. data/lib/frodata/service/response/xml.rb +40 -0
  65. data/lib/frodata/service_registry.rb +52 -0
  66. data/lib/frodata/version.rb +3 -0
  67. data/spec/fixtures/files/entity_to_xml.xml +17 -0
  68. data/spec/fixtures/files/error.xml +5 -0
  69. data/spec/fixtures/files/metadata.xml +150 -0
  70. data/spec/fixtures/files/product_0.json +10 -0
  71. data/spec/fixtures/files/product_0.xml +28 -0
  72. data/spec/fixtures/files/products.json +106 -0
  73. data/spec/fixtures/files/products.xml +308 -0
  74. data/spec/fixtures/files/supplier_0.json +26 -0
  75. data/spec/fixtures/files/supplier_0.xml +32 -0
  76. data/spec/fixtures/vcr_cassettes/entity_set_specs.yml +1635 -0
  77. data/spec/fixtures/vcr_cassettes/entity_set_specs/bad_entry.yml +183 -0
  78. data/spec/fixtures/vcr_cassettes/entity_set_specs/existing_entry.yml +256 -0
  79. data/spec/fixtures/vcr_cassettes/entity_set_specs/new_entry.yml +185 -0
  80. data/spec/fixtures/vcr_cassettes/entity_specs.yml +285 -0
  81. data/spec/fixtures/vcr_cassettes/navigation_property_proxy_specs.yml +346 -0
  82. data/spec/fixtures/vcr_cassettes/query/result_specs.yml +189 -0
  83. data/spec/fixtures/vcr_cassettes/query_specs.yml +1060 -0
  84. data/spec/fixtures/vcr_cassettes/schema/complex_type_specs.yml +127 -0
  85. data/spec/fixtures/vcr_cassettes/service/request_specs.yml +193 -0
  86. data/spec/fixtures/vcr_cassettes/service_registry_specs.yml +129 -0
  87. data/spec/fixtures/vcr_cassettes/service_specs.yml +127 -0
  88. data/spec/fixtures/vcr_cassettes/usage_example_specs.yml +1330 -0
  89. data/spec/frodata/entity/shared_examples.rb +82 -0
  90. data/spec/frodata/entity_container_spec.rb +38 -0
  91. data/spec/frodata/entity_set_spec.rb +168 -0
  92. data/spec/frodata/entity_spec.rb +151 -0
  93. data/spec/frodata/errors_spec.rb +48 -0
  94. data/spec/frodata/navigation_property/proxy_spec.rb +44 -0
  95. data/spec/frodata/navigation_property_spec.rb +55 -0
  96. data/spec/frodata/properties/binary_spec.rb +50 -0
  97. data/spec/frodata/properties/boolean_spec.rb +72 -0
  98. data/spec/frodata/properties/collection_spec.rb +44 -0
  99. data/spec/frodata/properties/date_spec.rb +23 -0
  100. data/spec/frodata/properties/date_time_offset_spec.rb +30 -0
  101. data/spec/frodata/properties/date_time_spec.rb +23 -0
  102. data/spec/frodata/properties/decimal_spec.rb +51 -0
  103. data/spec/frodata/properties/float_spec.rb +45 -0
  104. data/spec/frodata/properties/geography/line_string_spec.rb +33 -0
  105. data/spec/frodata/properties/geography/point_spec.rb +29 -0
  106. data/spec/frodata/properties/geography/polygon_spec.rb +55 -0
  107. data/spec/frodata/properties/geography/shared_examples.rb +72 -0
  108. data/spec/frodata/properties/guid_spec.rb +17 -0
  109. data/spec/frodata/properties/integer_spec.rb +58 -0
  110. data/spec/frodata/properties/string_spec.rb +46 -0
  111. data/spec/frodata/properties/time_of_day_spec.rb +23 -0
  112. data/spec/frodata/properties/time_spec.rb +15 -0
  113. data/spec/frodata/property_registry_spec.rb +16 -0
  114. data/spec/frodata/property_spec.rb +71 -0
  115. data/spec/frodata/query/criteria_spec.rb +229 -0
  116. data/spec/frodata/query_spec.rb +199 -0
  117. data/spec/frodata/schema/complex_type_spec.rb +96 -0
  118. data/spec/frodata/schema/enum_type_spec.rb +112 -0
  119. data/spec/frodata/schema_spec.rb +97 -0
  120. data/spec/frodata/service/request_spec.rb +49 -0
  121. data/spec/frodata/service/response_spec.rb +85 -0
  122. data/spec/frodata/service_registry_spec.rb +18 -0
  123. data/spec/frodata/service_spec.rb +191 -0
  124. data/spec/frodata/usage_example_spec.rb +188 -0
  125. data/spec/spec_helper.rb +32 -0
  126. data/spec/support/coverage.rb +2 -0
  127. data/spec/support/vcr.rb +9 -0
  128. metadata +401 -0
@@ -0,0 +1,92 @@
1
+ require 'frodata/query/criteria/comparison_operators'
2
+ require 'frodata/query/criteria/string_functions'
3
+ require 'frodata/query/criteria/date_functions'
4
+ require 'frodata/query/criteria/geography_functions'
5
+ require 'frodata/query/criteria/lambda_operators'
6
+
7
+ module FrOData
8
+ class Query
9
+ # Represents a discreet criteria within an FrOData::Query. Should not,
10
+ # normally, be instantiated directly.
11
+ class Criteria
12
+ # The property name that is the target of the criteria.
13
+ attr_reader :property
14
+ # The operator of the criteria.
15
+ attr_reader :operator
16
+ # The value of the criteria.
17
+ attr_reader :value
18
+ # A function to apply to the property.
19
+ attr_reader :function
20
+ # An optional argument to the function.
21
+ attr_reader :argument
22
+
23
+ # Initializes a new criteria with provided options.
24
+ # @param options [Hash]
25
+ def initialize(options = {})
26
+ @property = options[:property]
27
+ @operator = options[:operator]
28
+ @function = options[:function]
29
+ @argument = options[:argument]
30
+ @value = options[:value]
31
+ end
32
+
33
+ include ComparisonOperators
34
+ include StringFunctions
35
+ include DateFunctions
36
+ include GeographyFunctions
37
+ include LambdaOperators
38
+
39
+ # Returns criteria as query-ready string.
40
+ def to_s
41
+ query = function ? function_expression : property_name
42
+
43
+ if operator && !lambda_operator?
44
+ "#{query} #{operator} #{url_value(value)}"
45
+ else
46
+ query
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def property_name
53
+ property.name
54
+ rescue NoMethodError
55
+ property.to_s
56
+ end
57
+
58
+ def function_expression
59
+ return lambda_expression if lambda_operator?
60
+
61
+ if argument
62
+ "#{function}(#{property_name},#{url_value(argument)})"
63
+ else
64
+ "#{function}(#{property_name})"
65
+ end
66
+ end
67
+
68
+ def lambda_expression
69
+ "#{property_name}/#{function}(d:d/#{argument} #{operator} #{url_value(value)})"
70
+ end
71
+
72
+ def url_value(value)
73
+ property.value = value
74
+ property.url_value
75
+ rescue
76
+ value
77
+ end
78
+
79
+ def set_operator_and_value(operator, value)
80
+ @operator = operator
81
+ @value = value
82
+ self
83
+ end
84
+
85
+ def set_function_and_argument(function, argument)
86
+ @function = function
87
+ @argument = argument
88
+ self
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,49 @@
1
+ module FrOData
2
+ class Query
3
+ class Criteria
4
+ module ComparisonOperators
5
+ # Sets up equality operator.
6
+ # @param value [to_s]
7
+ # @return [self]
8
+ def eq(value)
9
+ set_operator_and_value(:eq, value)
10
+ end
11
+
12
+ # Sets up non-equality operator.
13
+ # @param value [to_s]
14
+ # @return [self]
15
+ def ne(value)
16
+ set_operator_and_value(:ne, value)
17
+ end
18
+
19
+ # Sets up greater-than operator.
20
+ # @param value [to_s]
21
+ # @return [self]
22
+ def gt(value)
23
+ set_operator_and_value(:gt, value)
24
+ end
25
+
26
+ # Sets up greater-than-or-equal operator.
27
+ # @param value [to_s]
28
+ # @return [self]
29
+ def ge(value)
30
+ set_operator_and_value(:ge, value)
31
+ end
32
+
33
+ # Sets up less-than operator.
34
+ # @param value [to_s]
35
+ # @return [self]
36
+ def lt(value)
37
+ set_operator_and_value(:lt, value)
38
+ end
39
+
40
+ # Sets up less-than-or-equal operator.
41
+ # @param value [to_s]
42
+ # @return [self]
43
+ def le(value)
44
+ set_operator_and_value(:le, value)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,61 @@
1
+ module FrOData
2
+ class Query
3
+ class Criteria
4
+ module DateFunctions
5
+ # Applies the `year` function.
6
+ # @return [self]
7
+ def year
8
+ set_function_and_argument(:year, nil)
9
+ end
10
+
11
+ # Applies the `month` function.
12
+ # @return [self]
13
+ def month
14
+ set_function_and_argument(:month, nil)
15
+ end
16
+
17
+ # Applies the `day` function.
18
+ # @return [self]
19
+ def day
20
+ set_function_and_argument(:day, nil)
21
+ end
22
+
23
+ # Applies the `hour` function.
24
+ # @return [self]
25
+ def hour
26
+ set_function_and_argument(:hour, nil)
27
+ end
28
+
29
+ # Applies the `minute` function.
30
+ # @return [self]
31
+ def minute
32
+ set_function_and_argument(:minute, nil)
33
+ end
34
+
35
+ # Applies the `second` function.
36
+ # @return [self]
37
+ def second
38
+ set_function_and_argument(:second, nil)
39
+ end
40
+
41
+ # Applies the `fractionalseconds` function.
42
+ # @return [self]
43
+ def fractionalseconds
44
+ set_function_and_argument(:fractionalseconds, nil)
45
+ end
46
+
47
+ # Applies the `date` function.
48
+ # @return [self]
49
+ def date
50
+ set_function_and_argument(:date, nil)
51
+ end
52
+
53
+ # Applies the `time` function.
54
+ # @return [self]
55
+ def time
56
+ set_function_and_argument(:time, nil)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ module FrOData
2
+ class Query
3
+ class Criteria
4
+ module GeographyFunctions
5
+ # Applies the `geo.distance` function.
6
+ # @param to [to_s]
7
+ # @return [self]
8
+ def distance(to)
9
+ set_function_and_argument(:'geo.distance', to)
10
+ end
11
+
12
+ # Applies the `geo.intersects` function.
13
+ # @param what [to_s]
14
+ # @return [self]
15
+ def intersects(what)
16
+ set_function_and_argument(:'geo.intersects', what)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module FrOData
2
+ class Query
3
+ class Criteria
4
+ module LambdaOperators
5
+ # Applies the `any` lambda operator to the given property
6
+ # @param property [to_s]
7
+ # @return [self]
8
+ def any(property)
9
+ set_function_and_argument(:any, property)
10
+ end
11
+
12
+ # Applies the `any` lambda operator to the given property
13
+ # @param property [to_s]
14
+ # @return [self]
15
+ def all(property)
16
+ set_function_and_argument(:all, property)
17
+ end
18
+
19
+ private
20
+
21
+ def lambda_operator?
22
+ [:any, :all].include?(function)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ module FrOData
2
+ class Query
3
+ class Criteria
4
+ module StringFunctions
5
+ # Sets up a `contains` function criterium.
6
+ # @param str [to_s]
7
+ # @return [self]
8
+ def contains(str)
9
+ set_function_and_argument(:contains, str)
10
+ end
11
+
12
+ # Sets up a `startswith` function criterium.
13
+ # @param str [to_s]
14
+ # @return [self]
15
+ def startswith(str)
16
+ set_function_and_argument(:startswith, str)
17
+ end
18
+
19
+ # Sets up a `endswith` function criterium.
20
+ # @param str [to_s]
21
+ # @return [self]
22
+ def endswith(str)
23
+ set_function_and_argument(:endswith, str)
24
+ end
25
+
26
+ # Applies the `tolower` function to the property.
27
+ # @return [self]
28
+ def tolower
29
+ set_function_and_argument(:tolower, nil)
30
+ end
31
+
32
+ # Applies the `toupper` function to the property.
33
+ # @return [self]
34
+ def toupper
35
+ set_function_and_argument(:toupper, nil)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,58 @@
1
+ module FrOData
2
+ class Query
3
+ module InBatches
4
+ DEFAULT_BATCH_SIZE = 10
5
+
6
+ # Process results in batches.
7
+ #
8
+ # When a block is given, yields `FrOData::Query::Result`
9
+ # objects of specified batch size to the block.
10
+ #
11
+ # service['Products'].query.in_batches(of: 10) do |batch|
12
+ # batch.count # batch size (10 except for last batch)
13
+ # batch.is_a? FrOData::Query::Result # true
14
+ # end
15
+ #
16
+ # Returns an Enumerator to process results individually.
17
+ #
18
+ # service['Products'].query.in_batches.each do |entity|
19
+ # entity.is_a? FrOData::Entity # true
20
+ # end
21
+ #
22
+ # @param of: [int] batch size
23
+ # @return [Enumerator]
24
+ def in_batches(of: DEFAULT_BATCH_SIZE, &block)
25
+ per_page = of
26
+
27
+ if block_given?
28
+ each_batch(of, &block)
29
+ else
30
+ Enumerator.new do |result|
31
+ each_batch(of) do |batch|
32
+ batch.each { |entity| result << entity }
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def each_batch(per_page, &block)
41
+ page = 0
42
+
43
+ loop do
44
+ batch = get_paginated_entities(per_page, page)
45
+ break if batch.empty?
46
+
47
+ yield batch
48
+
49
+ page += 1
50
+ end
51
+ end
52
+
53
+ def get_paginated_entities(per_page, page)
54
+ skip(per_page * page).limit(per_page).execute
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+ module FrOData
2
+ class Railtie < Rails::Railtie
3
+ config.before_initialize do
4
+ ::FrOData::Railtie.load_configuration!
5
+ ::FrOData::Railtie.setup_service_registry!
6
+ end
7
+
8
+ # Looks for config/odata.yml and loads the configuration.
9
+ def self.load_configuration!
10
+ # TODO Implement Rails configuration loading
11
+ end
12
+
13
+ # Examines the loaded configuration and populates the
14
+ # FrOData::ServiceRegistry accordingly.
15
+ def self.setup_service_registry!
16
+ # TODO Populate FrOData::ServiceRegistry based on configuration
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,155 @@
1
+ require 'frodata/schema/complex_type'
2
+ require 'frodata/schema/enum_type'
3
+
4
+ module FrOData
5
+ class Schema
6
+ # The schema's parent service
7
+ attr_reader :service
8
+ # The schema's metadata (i.e its XML definition)
9
+ attr_reader :metadata
10
+
11
+ # Creates a new schema.
12
+ #
13
+ # @param schema_definition [Nokogiri::XML] The schema's XML definition
14
+ # @param service [FrOData::Service] The schema's parent service
15
+ def initialize(schema_definition, service)
16
+ @metadata = schema_definition
17
+ @service = service
18
+ end
19
+
20
+ # Returns the schema's `Namespace` attribute (mandatory).
21
+ # @return [String]
22
+ def namespace
23
+ @namespace ||= metadata.attributes['Namespace'].value
24
+ end
25
+
26
+ # Returns a list of actions defined by the schema.
27
+ # @return [Array<String>]
28
+ def actions
29
+ @actions ||= metadata.xpath('//Action').map do |action|
30
+ action.attributes['Name'].value
31
+ end
32
+ end
33
+
34
+ # Returns a list of entities defined by the schema.
35
+ # @return [Array<String>]
36
+ def entity_types
37
+ @entity_types ||= metadata.xpath('//EntityType').map do |entity|
38
+ entity.attributes['Name'].value
39
+ end
40
+ end
41
+
42
+ # Returns a list of `ComplexType`s defined by the schema.
43
+ # @return [Hash<String, FrOData::Schema::ComplexType>]
44
+ def complex_types
45
+ @complex_types ||= metadata.xpath('//ComplexType').map do |entity|
46
+ [
47
+ entity.attributes['Name'].value,
48
+ ComplexType.new(entity, self)
49
+ ]
50
+ end.to_h
51
+ end
52
+
53
+ # Returns a list of EnumTypes defined by the schema.
54
+ # @return [Hash<String, FrOData::Schema::EnumType>]
55
+ def enum_types
56
+ @enum_types ||= metadata.xpath('//EnumType').map do |entity|
57
+ [
58
+ entity.attributes['Name'].value,
59
+ EnumType.new(entity, self)
60
+ ]
61
+ end.to_h
62
+ end
63
+
64
+ # Returns a list of functions defined by the schema.
65
+ # @return [Array<String>]
66
+ def functions
67
+ @functions ||= metadata.xpath('//Function').map do |function|
68
+ function.attributes['Name'].value
69
+ end
70
+ end
71
+
72
+ # Returns a list of type definitions defined by the schema.
73
+ # @return [Array<String>]
74
+ def type_definitions
75
+ @typedefs ||= metadata.xpath('//TypeDefinition').map do |typedef|
76
+ typedef.attributes['Name'].value
77
+ end
78
+ end
79
+
80
+ # Returns a hash for finding an association through an entity type's defined
81
+ # NavigationProperty elements.
82
+ # @return [Hash<Hash<FrOData::NavigationProperty>>]
83
+ def navigation_properties
84
+ @navigation_properties ||= metadata.xpath('//EntityType').map do |entity_type_def|
85
+ [
86
+ entity_type_def.attributes['Name'].value,
87
+ entity_type_def.xpath('./NavigationProperty').map do |nav_property_def|
88
+ [
89
+ nav_property_def.attributes['Name'].value,
90
+ ::FrOData::NavigationProperty.build(nav_property_def)
91
+ ]
92
+ end.to_h
93
+ ]
94
+ end.to_h
95
+ end
96
+
97
+ # Get the property type for an entity from metadata.
98
+ #
99
+ # @param entity_name [to_s] the name of the relevant entity
100
+ # @param property_name [to_s] the property name needed
101
+ # @return [String] the name of the property's type
102
+ def get_property_type(entity_name, property_name)
103
+ metadata.xpath("//EntityType[@Name='#{entity_name}']/Property[@Name='#{property_name}']").first.attributes['Type'].value
104
+ end
105
+
106
+ # Get the primary key for the supplied Entity.
107
+ #
108
+ # @param entity_name [to_s]
109
+ # @return [String]
110
+ def primary_key_for(entity_name)
111
+ metadata.xpath("//EntityType[@Name='#{entity_name}']/Key/PropertyRef").first.attributes['Name'].value
112
+ end
113
+
114
+ # Get the list of properties and their various options for the supplied
115
+ # Entity name.
116
+ # @param entity_name [to_s]
117
+ # @return [Hash]
118
+ # @api private
119
+ def properties_for_entity(entity_name)
120
+ type_definition = metadata.xpath("//EntityType[@Name='#{entity_name}']").first
121
+ raise ArgumentError, "Unknown EntityType: #{entity_name}" if type_definition.nil?
122
+ properties_to_return = {}
123
+ type_definition.xpath('./Property').each do |property_xml|
124
+ property_name, property = process_property_from_xml(property_xml)
125
+ properties_to_return[property_name] = property
126
+ end
127
+ properties_to_return
128
+ end
129
+
130
+ private
131
+
132
+ def process_property_from_xml(property_xml)
133
+ property_name = property_xml.attributes['Name'].value
134
+ property_type = property_xml.attributes['Type'].value
135
+ property_options = { service: service }
136
+
137
+ property_type, value_type = property_type.split(/\(|\)/)
138
+ if property_type == 'Collection'
139
+ klass = ::FrOData::Properties::Collection
140
+ property_options.merge(value_type: value_type)
141
+ else
142
+ klass = ::FrOData::PropertyRegistry[property_type]
143
+ end
144
+
145
+ if klass.nil?
146
+ raise RuntimeError, "Unknown property type: #{value_type}"
147
+ else
148
+ property_options[:allows_nil] = false if property_xml.attributes['Nullable'] == 'false'
149
+ property = klass.new(property_name, nil, property_options)
150
+ end
151
+
152
+ return [property_name, property]
153
+ end
154
+ end
155
+ end