contentful_model 0.2.0 → 1.0.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.
Files changed (84) hide show
  1. checksums.yaml +5 -5
  2. data/lib/contentful_model.rb +17 -13
  3. data/lib/contentful_model/asset.rb +30 -0
  4. data/lib/contentful_model/asset_dimension_query.rb +70 -0
  5. data/lib/contentful_model/asset_dimensions.rb +54 -0
  6. data/lib/contentful_model/associations/associations.rb +10 -4
  7. data/lib/contentful_model/associations/belongs_to.rb +9 -4
  8. data/lib/contentful_model/associations/belongs_to_many.rb +22 -45
  9. data/lib/contentful_model/associations/has_many.rb +15 -13
  10. data/lib/contentful_model/associations/has_many_nested.rb +39 -37
  11. data/lib/contentful_model/associations/has_one.rb +22 -14
  12. data/lib/contentful_model/base.rb +115 -74
  13. data/lib/contentful_model/client.rb +14 -3
  14. data/lib/contentful_model/errors.rb +0 -1
  15. data/lib/contentful_model/manageable.rb +49 -21
  16. data/lib/contentful_model/management.rb +1 -0
  17. data/lib/contentful_model/migrations/content_type.rb +24 -24
  18. data/lib/contentful_model/migrations/content_type_factory.rb +9 -6
  19. data/lib/contentful_model/migrations/migration.rb +9 -4
  20. data/lib/contentful_model/queries.rb +60 -9
  21. data/lib/contentful_model/query.rb +148 -5
  22. data/lib/contentful_model/validations/lambda_validation.rb +17 -0
  23. data/lib/contentful_model/validations/validates_presence_of.rb +4 -1
  24. data/lib/contentful_model/validations/validations.rb +56 -8
  25. data/lib/contentful_model/version.rb +1 -1
  26. data/spec/asset_spec.rb +141 -0
  27. data/spec/associations/belongs_to_many_spec.rb +38 -0
  28. data/spec/associations/belongs_to_spec.rb +22 -0
  29. data/spec/associations/has_many_nested_spec.rb +321 -0
  30. data/spec/associations/has_many_spec.rb +33 -0
  31. data/spec/associations/has_one_spec.rb +32 -0
  32. data/spec/base_spec.rb +199 -0
  33. data/spec/chainable_queries_spec.rb +199 -0
  34. data/spec/client_spec.rb +11 -0
  35. data/spec/contentful_model_spec.rb +25 -0
  36. data/spec/fixtures/vcr_cassettes/asset/all.yml +161 -0
  37. data/spec/fixtures/vcr_cassettes/asset/find.yml +118 -0
  38. data/spec/fixtures/vcr_cassettes/association/belongs_to_many.yml +439 -0
  39. data/spec/fixtures/vcr_cassettes/association/has_many.yml +161 -0
  40. data/spec/fixtures/vcr_cassettes/association/has_one.yml +161 -0
  41. data/spec/fixtures/vcr_cassettes/association/nested_with_root_root.yml +240 -0
  42. data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_higher_include.yml +123 -0
  43. data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_parent.yml +609 -0
  44. data/spec/fixtures/vcr_cassettes/association/nested_without_root_childless.yml +688 -0
  45. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle.yml +319 -0
  46. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_higher_include.yml +161 -0
  47. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_parent.yml +489 -0
  48. data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless.yml +331 -0
  49. data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless_higher_include.yml +82 -0
  50. data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle.yml +161 -0
  51. data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle_higher_include.yml +82 -0
  52. data/spec/fixtures/vcr_cassettes/base/content_type.yml +185 -0
  53. data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty.yml +202 -0
  54. data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty_with_value.yml +124 -0
  55. data/spec/fixtures/vcr_cassettes/client.yml +98 -0
  56. data/spec/fixtures/vcr_cassettes/dog.yml +161 -0
  57. data/spec/fixtures/vcr_cassettes/human.yml +199 -0
  58. data/spec/fixtures/vcr_cassettes/management/client.yml +1362 -0
  59. data/spec/fixtures/vcr_cassettes/management/nyancat.yml +1449 -0
  60. data/spec/fixtures/vcr_cassettes/management/nyancat_2.yml +1449 -0
  61. data/spec/fixtures/vcr_cassettes/management/nyancat_publish.yml +481 -0
  62. data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_fail.yml +558 -0
  63. data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_save.yml +620 -0
  64. data/spec/fixtures/vcr_cassettes/management/nyancat_save.yml +479 -0
  65. data/spec/fixtures/vcr_cassettes/management/nyancat_save_2.yml +479 -0
  66. data/spec/fixtures/vcr_cassettes/nyancat.yml +196 -0
  67. data/spec/fixtures/vcr_cassettes/playground/nyancat.yml +177 -0
  68. data/spec/fixtures/vcr_cassettes/query/each_entry.yml +319 -0
  69. data/spec/fixtures/vcr_cassettes/query/each_page.yml +319 -0
  70. data/spec/fixtures/vcr_cassettes/query/empty.yml +180 -0
  71. data/spec/fixtures/vcr_cassettes/query/load.yml +197 -0
  72. data/spec/fixtures/vcr_cassettes/query/manual_pagination.yml +161 -0
  73. data/spec/fixtures/vcr_cassettes/query/nyancat_invalid_elements.yml +247 -0
  74. data/spec/manageable_spec.rb +186 -0
  75. data/spec/management_spec.rb +17 -0
  76. data/spec/migrations/content_type_factory_spec.rb +41 -0
  77. data/spec/migrations/content_type_spec.rb +176 -0
  78. data/spec/migrations/migration_spec.rb +75 -0
  79. data/spec/queries_spec.rb +85 -0
  80. data/spec/query_spec.rb +126 -0
  81. data/spec/spec_helper.rb +58 -0
  82. data/spec/validations/validations_spec.rb +182 -0
  83. metadata +213 -19
  84. data/lib/contentful_model/chainable_queries.rb +0 -104
@@ -1,10 +1,12 @@
1
1
  module ContentfulModel
2
2
  module Associations
3
+ # Module for Has Many Nested association
3
4
  module HasManyNested
4
5
  def self.included(base)
5
6
  base.extend ClassMethods
6
7
  end
7
8
 
9
+ # Class method
8
10
  module ClassMethods
9
11
  # has_many_nested allows you to set up a tree relationship
10
12
  # it calls has_many and belongs_to_many on the class, and sets up
@@ -13,7 +15,7 @@ module ContentfulModel
13
15
  # To set this up in contentful, add a multi-entry field validated to the same model
14
16
  # as the parent, and give it a name. For example, Page might have a field called childPages:
15
17
  #
16
- # has_many_nested :child_pages, root: -> { Page.find("some_id")}
18
+ # has_many_nested :child_pages, root: -> { Page.find("some_id") }
17
19
  #
18
20
  # would setup up an instance attribute called parent_pages which lists all the direct
19
21
  # parents of this page. It would also create methods to find a page based on an array
@@ -21,26 +23,26 @@ module ContentfulModel
21
23
  # ancestors which called the object; because 'many' associations in Contentful are actually
22
24
  # 'belongs_to_many' from the child end, we might have several ancestors to a page. You will need
23
25
  # to write your own recursion for this, because it's probably an implementation-specific problem.
26
+ # rubocop:disable Style/PredicateName
24
27
  def has_many_nested(association_name, options = {})
25
- has_many association_name, inverse_of: :"parent_#{self.to_s.underscore}"
26
- belongs_to_many :"parent_#{self.to_s.underscore.pluralize}", class_name: self.to_s, inverse_of: association_name
27
- if options[:root].is_a?(Proc)
28
- @root_method = options[:root]
29
- end
28
+ has_many association_name, class_name: to_s, inverse_of: :"parent_#{to_s.underscore}"
29
+ belongs_to_many :"parent_#{to_s.underscore.pluralize}", class_name: to_s
30
+ root_method = options[:root] if options[:root].is_a?(Proc)
30
31
 
31
32
  # If there's a root method defined, set up a class method called root_[class name]. In our example this would be
32
33
  # Page.root_page.
33
34
  # @return [Object] the root entity returned from the proc defined in has_many_nested
34
- if defined?(@root_method) && @root_method.is_a?(Proc)
35
+ if defined?(root_method) && root_method.is_a?(Proc)
35
36
  # @return [Object] the root entity
36
- define_singleton_method :"root_#{self.to_s.underscore}" do
37
- @root_method.call
37
+ define_method :"root_#{to_s.underscore}" do
38
+ root_method.call
38
39
  end
39
40
  end
40
41
 
41
42
  # A utility method which returns the parent object; saves passing around interpolated strings
42
43
  define_method :parent do
43
- self.send(:"parent_#{self.class.to_s.underscore}")
44
+ parents = send(:"parent_#{self.class.to_s.underscore.pluralize}")
45
+ parents.first unless parents.nil?
44
46
  end
45
47
 
46
48
  # Determine if the object has any parents. If it doesn't, it's considered a root.
@@ -55,43 +57,44 @@ module ContentfulModel
55
57
  define_method :find_ancestors do |&block|
56
58
  return enum_for(:find_ancestors) unless block
57
59
  if parent.nil?
58
- #this *is* the parent
60
+ # this *is* the parent
59
61
  return self
60
62
  end
61
- block.call(parent)
62
- unless parent && parent.root?
63
- parent.find_ancestors {|a| block.call(a)}
64
- end
63
+ block[parent]
64
+
65
+ parent.find_ancestors { |a| block[a] } if parent && !parent.root?
65
66
  end
66
67
 
67
68
  # A utility method to return the results of `find_ancestors` as an array
68
69
  # @return [Array] of ancestors in reverse order (root last)
69
70
  define_method :ancestors do
70
- self.find_ancestors.to_a
71
+ find_ancestors.to_a
71
72
  end
72
73
 
73
- # Return the last member of the enumerable, which is the root
74
+ # If this entry is the root, return self.
75
+ # Otherwise, return the last member of the ancestors, which is the root
74
76
  # @return the root instance of this object
75
77
  define_method :root do
78
+ return self if root?
79
+
76
80
  find_ancestors.to_a.last
77
81
  end
78
82
 
79
83
  # @return [Boolean] whether or not this instance has children
80
- define_method :has_children? do
81
- !self.send(association_name).empty?
84
+ define_method :children? do
85
+ !send(association_name).empty?
82
86
  end
83
87
 
84
88
  # @return [Array] a collection of child objects, based on the association name
85
89
  define_method :children do
86
- self.send(association_name)
90
+ send(association_name)
87
91
  end
88
92
 
89
93
  # @return [Hash] a hash of nested child objects
90
94
  define_method :nested_children do
91
- self.children.inject({}) do |h,c|
92
- children = c.has_children? ? c.nested_children : nil
93
- h[c] = children
94
- h
95
+ children.each_with_object({}) do |e, a|
96
+ children = e.children? ? e.nested_children : nil
97
+ a[e] = children
95
98
  end
96
99
  end
97
100
 
@@ -99,38 +102,37 @@ module ContentfulModel
99
102
  # @param field [Symbol] the field you want to return, nested for each child
100
103
  # @return [Hash] of nested children, by that field
101
104
  define_method :nested_children_by do |field|
102
- self.children.inject({}) do |h,c|
103
- children = c.has_children? ? c.nested_children_by(field) : nil
104
- h[c.send(field)] = children
105
- h
105
+ children.each_with_object({}) do |e, a|
106
+ children = e.children? ? e.nested_children_by(field) : nil
107
+ a[e.send(field)] = children
106
108
  end
107
109
  end
108
110
 
109
111
  # Return a flattened hash of children by the specified field
110
112
  define_method :all_child_paths_by do |field, opts = {}|
111
- options = {prefix: nil}.merge!(opts)
113
+ options = { prefix: nil }.merge!(opts)
112
114
  flatten_hash(nested_children_by(field)).keys.collect do |path|
113
115
  options[:prefix] ? path.unshift(options[:prefix]) : path
114
116
  end
115
117
  end
116
118
 
117
119
  # Search for a child by a certain field. This is called on the parent(s).
118
- # e.g. Page.root.find_child_path_by(:slug, "some-slug"). Accepts a prefix if you want to
120
+ # e.g. Page.root.find_child_path_by(:slug, "some-slug"). Accepts a prefix if you want to
119
121
  # prefix the children with the parent
120
122
  define_method :find_child_path_by do |field, value, opts = {}|
121
- all_child_paths_by(field,opts).select {|child| child.include?(value)}
123
+ all_child_paths_by(field, opts).select { |child| child.include?(value) }
122
124
  end
123
125
 
124
126
  # Private method to flatten a hash. Courtesy Cary Swoveland http://stackoverflow.com/a/23861946
125
- define_method :flatten_hash do |h,f=[],g={}|
126
- return g.update({ f=>h }) unless h.is_a? Hash
127
- h.each { |k,r| flatten_hash(r,f+[k],g) }
127
+ define_method :flatten_hash do |h, f = [], g = {}|
128
+ return g.update(f => h) unless h.is_a? Hash
129
+ h.each { |k, r| flatten_hash(r, f + [k], g) }
128
130
  g
129
131
  end
130
- self.send(:private, :flatten_hash)
131
-
132
+ send(:private, :flatten_hash)
132
133
  end
134
+ # rubocop:enable Style/PredicateName
133
135
  end
134
136
  end
135
137
  end
136
- end
138
+ end
@@ -1,45 +1,53 @@
1
1
  module ContentfulModel
2
2
  module Associations
3
+ # Module for Has One association
3
4
  module HasOne
4
5
  def self.included(base)
5
6
  base.extend ClassMethods
6
7
  end
7
8
 
9
+ # Class method
8
10
  module ClassMethods
9
- #has_one defines a method on the parent which wraps around the superclass's implementation. In most cases this
10
- #will end up at ContentfulModel::Base#method_missing and look at the fields on a content object.
11
- #We wrap around it like this so we can specify a class_name option to call a different method from the association
12
- #name.
11
+ # has_one defines a method on the parent which wraps around the superclass's implementation. In most cases this
12
+ # will end up at ContentfulModel::Base#method_missing and look at the fields on a content object.
13
+ # We wrap around it like this so we can specify a class_name option to call a different method from the association
14
+ # name.
15
+ #
13
16
  # class Foo
14
17
  # has_one :special_bar, class_name: "Bar"
15
18
  # end
19
+ #
16
20
  # @param association_name [Symbol] the name of the association. In this case Foo.special_bar.
17
21
  # @param options [Hash] a hash, the only key of which is important is class_name.
22
+ # rubocop:disable Style/PredicateName
18
23
  def has_one(association_name, options = {})
19
24
  default_options = {
20
25
  class_name: association_name.to_s.classify,
21
- inverse_of: self.to_s.underscore.to_sym
26
+ inverse_of: to_s.underscore.to_sym
22
27
  }
23
28
  options = default_options.merge(options)
29
+
30
+ include_discovered(options[:class_name])
31
+
24
32
  # Define a method which matches the association name
25
33
  define_method association_name do
26
34
  begin
27
35
  # Start by calling the association name as a method on the superclass.
28
36
  # this will end up in ContentfulModel::Base#method_missing and return the value from Contentful.
29
37
  # We set the singular of the association name on this object to allow easy recursing.
30
- super().send(:"#{options[:inverse_of]}=",self)
31
- rescue ContentfulModel::AttributeNotFoundError
32
- # If method_missing returns an error, the field doesn't exist. If a class is specified, try that.
33
- if options[:class_name].underscore.to_sym != association_name
34
- self.send(options[:class_name].underscore.to_sym)
35
- else
36
- #otherwise give up and return nil
37
- nil
38
+ super().tap do |child|
39
+ child.send(:"#{options[:inverse_of]}=", self) if child.respond_to?(:"#{options[:inverse_of]}=")
38
40
  end
41
+ rescue NoMethodError
42
+ # If method_missing returns an error, the field doesn't exist. If a class is specified, try that.
43
+ possible_field_name = options[:class_name].underscore.to_sym
44
+ return send(possible_field_name) if possible_field_name != association_name && respond_to?(possible_field_name)
45
+ nil
39
46
  end
40
47
  end
41
48
  end
49
+ # rubocop:enable Style/PredicateName
42
50
  end
43
51
  end
44
52
  end
45
- end
53
+ end
@@ -1,109 +1,133 @@
1
+ require_relative 'associations/associations'
2
+ require_relative 'validations/validations'
3
+ require_relative 'manageable'
4
+ require_relative 'queries'
5
+ require_relative 'errors'
6
+ require_relative 'client'
7
+
1
8
  module ContentfulModel
2
- class Base < Contentful::DynamicEntry
3
- include ContentfulModel::ChainableQueries
9
+ # Wrapper for Contentful::Entry which provides ActiveModel-like syntax
10
+ class Base < Contentful::Entry
4
11
  include ContentfulModel::Associations
5
12
  include ContentfulModel::Validations
6
13
  include ContentfulModel::Manageable
14
+ include ContentfulModel::Queries
7
15
 
8
- def initialize(*args)
16
+ def initialize(*)
9
17
  super
10
- define_getters
11
- self.class.coercions ||= {}
18
+ override_getters
19
+ end
20
+
21
+ def cache_key(*timestamp_names)
22
+ fail ArgumentError, "ContentfulModel::Base models don't support named timestamps." if timestamp_names.present?
23
+
24
+ "#{self.class.to_s.underscore}/#{id}-#{updated_at.utc.to_s(:usec)}"
25
+ end
26
+
27
+ def hash
28
+ "#{sys[:content_type].id}-#{sys[:id]}".hash
29
+ end
30
+
31
+ def eql?(other)
32
+ super || other.instance_of?(self.class) && sys[:id].present? && other.sys[:id] == sys[:id]
12
33
  end
13
34
 
14
35
  private
15
36
 
16
- def define_getters
17
- fields.each do |k, v|
18
- if Contentful::Constants::KNOWN_LOCALES.include?(k.to_s)
19
- v.keys.each do |name|
20
- define_getter(name)
21
- end
22
- else
23
- define_getter(k)
37
+ def define_sys_methods
38
+ sys.keys.each do |name|
39
+ define_singleton_method name do
40
+ self.class.coerce_value(name, sys[name])
24
41
  end
25
42
  end
26
43
  end
27
44
 
28
- def define_getter(name)
29
- define_singleton_method "#{name.to_s.underscore}" do
30
- fields(default_locale)[name]
31
- end
45
+ def filter_invalids(value)
46
+ return value.reject { |v| v.is_a?(Contentful::Link) || (v.respond_to?(:invalid?) && v.invalid?) } if value.is_a?(Array)
47
+ return nil if value.is_a?(Contentful::Link) || value.respond_to?(:fields) && value.fields.empty?
48
+
49
+ value
32
50
  end
33
51
 
34
- #use method_missing to call fields on the model
35
- def method_missing(method, *args, &block)
36
- result = fields[:"#{method.to_s.camelize(:lower)}"]
37
- # we need to pull out any Contentful::Link references, and also things which don't have any fields at all
38
- # because they're newly created
39
- if result.is_a?(Array)
40
- result.reject! {|r| r.is_a?(Contentful::Link) || (r.respond_to?(:invalid) && r.invalid?)}
41
- elsif result.is_a?(Contentful::Link)
42
- result = nil
43
- elsif result.respond_to?(:fields) && result.send(:fields).empty?
44
- result = nil
45
- elsif result.respond_to?(:invalid?) && result.invalid?
46
- result = nil
47
- end
52
+ def nillable?(name, value)
53
+ value.nil? &&
54
+ self.class.return_nil_for_empty_attribute_fields &&
55
+ self.class.return_nil_for_empty_attribute_fields.include?(name)
56
+ end
48
57
 
49
- if result.nil?
50
- # if self.class.rescue_from_no_attribute_fields.member?()
51
- # end
52
- if self.class.return_nil_for_empty_attribute_fields && self.class.return_nil_for_empty_attribute_fields.include?(method)
53
- return nil
54
- else
55
- raise ContentfulModel::AttributeNotFoundError, "no attribute #{method} found"
56
- end
57
- else
58
- # if there's no coercion specified, return the result
59
- if self.class.coercions[method].nil?
60
- return result
61
- #if there's a coercion specified for the field and it's a proc, pass the result
62
- #to the proc
63
- elsif self.class.coercions[method].is_a?(Proc)
64
- return self.class.coercions[method].call(result)
65
- #provided the coercion is in the COERCIONS constant, call the proc on that
66
- elsif !self.class::COERCIONS[self.class.coercions[method]].nil?
67
- return self.class::COERCIONS[self.class.coercions[method]].call(result)
68
- else
69
- #... or just return the result
70
- return result
58
+ def define_fields_methods
59
+ fields.keys.each do |name|
60
+ define_singleton_method name do
61
+ result = filter_invalids(
62
+ self.class.coerce_value(name, fields[name])
63
+ )
64
+
65
+ return nil if nillable?(name, result)
66
+
67
+ result
71
68
  end
72
69
  end
73
70
  end
74
71
 
75
- def respond_to_missing?(method, private=false)
76
- if fields[:"#{method.to_s.camelize(:lower)}"].nil?
77
- super
78
- else
79
- true
80
- end
72
+ def override_getters
73
+ define_sys_methods
74
+ define_fields_methods
81
75
  end
82
76
 
83
- def cache_key(*timestamp_names)
84
- if timestamp_names.present?
85
- raise ArgumentError, "ContentfulModel::Base models don't support named timestamps."
86
- end
87
-
88
- "#{self.class.to_s.underscore}/#{self.id}-#{self.updated_at.utc.to_s(:number)}"
77
+ def respond_to_missing?(method, private = false)
78
+ key = fields.keys.detect { |k| k.to_s.underscore == method.to_s.underscore }
79
+ return super if key.nil?
80
+ true
89
81
  end
90
82
 
91
83
  class << self
92
- attr_accessor :content_type_id, :coercions, :return_nil_for_empty_attribute_fields
84
+ attr_accessor :content_type_id, :coercions, :return_nil_for_empty_attribute_fields, :client
93
85
 
94
86
  def descendents
95
87
  ObjectSpace.each_object(Class).select { |klass| klass < self }
96
88
  end
97
89
 
98
- def add_entry_mapping
99
- unless ContentfulModel.configuration.entry_mapping.has_key?(@content_type_id)
100
- ContentfulModel.configuration.entry_mapping[@content_type_id] = self.to_s.constantize
90
+ def discovered_includes
91
+ @discovered_includes ||= []
92
+ end
93
+
94
+ def discovered_include_level
95
+ @discovered_include_level ||= nil
96
+ return @discovered_include_level unless @discovered_include_level.nil?
97
+
98
+ # Recreate content type tree
99
+ includes = {}
100
+ discovered_includes.each do |klass|
101
+ # Recursively find includes - remove self from reference lists
102
+ includes[klass] = klass.constantize.discovered_includes.reject { |i| i == to_s } + [klass]
101
103
  end
104
+
105
+ include_level = includes.values.map(&:size).max # Longest include chain
106
+ return @discovered_include_level = 1 if include_level.nil? || include_level.zero?
107
+ return @discovered_include_level = 10 if include_level >= 10
108
+ @discovered_include_level = include_level + 1
109
+ end
110
+
111
+ # Add a class to the known referenced classes
112
+ def include_discovered(klass)
113
+ # This should be nil already in most cases,
114
+ # but if another class is defined in Runtime after a query was already run, we want to make sure this is reset
115
+ @discovered_include_level = nil
116
+
117
+ discovered_includes << klass unless discovered_includes.include?(klass)
118
+ end
119
+
120
+ def mapping?
121
+ ContentfulModel.configuration.entry_mapping.key?(@content_type_id)
122
+ end
123
+
124
+ def add_entry_mapping
125
+ ContentfulModel.configuration.entry_mapping[@content_type_id] = to_s.constantize unless mapping?
102
126
  end
103
127
 
104
128
  def client
105
129
  # add an entry mapping for this content type
106
- self.add_entry_mapping
130
+ add_entry_mapping
107
131
  if ContentfulModel.use_preview_api
108
132
  @preview_client ||= ContentfulModel::Client.new(ContentfulModel.configuration.to_hash)
109
133
  else
@@ -123,14 +147,31 @@ module ContentfulModel
123
147
  @coercions
124
148
  end
125
149
 
126
- def return_nil_for_empty(*fields)
150
+ def coerce_value(field_name, value)
151
+ return value if coercions.nil?
152
+
153
+ coercion = coercions[field_name]
154
+
155
+ case coercion
156
+ when Symbol, String
157
+ coercion = Contentful::Field::KNOWN_TYPES[coercion.to_s]
158
+ return coercion.new(value).coerce unless coercion.nil?
159
+ when Proc
160
+ coercion[value]
161
+ else
162
+ value
163
+ end
164
+ end
165
+
166
+ def return_nil_for_empty(*nillable_fields)
127
167
  @return_nil_for_empty_attribute_fields ||= []
128
168
 
129
- fields.each do |field|
169
+ nillable_fields.each do |field|
130
170
  define_method field do
131
171
  begin
132
- super()
133
- rescue ContentfulModel::AttributeNotFoundError
172
+ result = super()
173
+ result.present? ? result : nil
174
+ rescue NoMethodError
134
175
  nil
135
176
  end
136
177
  end