class Spaceship::Base
Spaceship::Base
is the superclass for models in Apple Developer Portal
. It's mainly responsible for mapping responses to objects.
A class-level attribute `client` is used to maintain the spaceship which we are using to talk to ADP.
Example of creating a new ADP model:
class Widget < Spaceship::Base attr_accessor :id, :name, :foo_bar, :wiz_baz attr_mapping({ 'name' => :name, 'fooBar' => :foo_bar, 'wizBaz' => :wiz_baz }) end
When you want to instantiate a model pass in the parsed response: `Widget.new(widget_json)`
Attributes
@return (Spaceship::Client
) The current spaceship client used by the model to make requests.
@return (Hash/Array) Holds the raw data we got from Apple's
server to use it later
Public Class Methods
From stackoverflow.com/questions/2487333/fastest-one-liner-way-to-list-attr-accessors-in-ruby This will store a list of defined attr_accessors to easily access them when inspecting the values
# File spaceship/lib/spaceship/base.rb, line 247 def self.attr_accessor(*vars) @attributes ||= [] @attributes.concat(vars) super(*vars) end
Defines the attribute mapping between the response from Apple and our model objects. Keys are to match keys in the response and the values are to match attributes on the model.
Example of using `attr_mapping`
class Widget < Spaceship::Base attr_accessor :id, :name, :foo_bar, :wiz_baz attr_mapping({ 'name' => :name, 'fooBar' => :foo_bar, 'wizBaz' => :wiz_baz }) end
# File spaceship/lib/spaceship/base.rb, line 136 def attr_mapping(attr_map = nil) if attr_map @attr_mapping = attr_map @attr_mapping.values.each do |method_name| getter = method_name.to_sym setter = "#{method_name}=".to_sym # Seems like the `public_instance_methods.include?` doesn't always work # More context https://github.com/fastlane/fastlane/issues/11481 # That's why we have the `begin` `rescue` code here begin remove_method(getter) if public_instance_methods.include?(getter) rescue NameError end begin remove_method(setter) if public_instance_methods.include?(setter) rescue NameError end end include(mapping_module(@attr_mapping)) else begin @attr_mapping ||= ancestors[1].attr_mapping rescue NoMethodError rescue NameError end end return @attr_mapping end
# File spaceship/lib/spaceship/base.rb, line 253 def self.attributes @attributes ||= [] par = [] par = (self.superclass.attributes || []) unless self == Base @attributes + par end
The factory class-method. This should only be used or overridden in very specific use-cases
The only time it makes sense to use or override this method is when we want a base class to return a sub-class based on attributes.
Here, we define the method to be the same as `Spaceship::Base.new(attrs)`, be it should be used only by classes that override it.
Example:
Certificate.factory(attrs) #=> #<PushCertificate ... >
# File spaceship/lib/spaceship/base.rb, line 209 def factory(attrs, existing_client = nil) self.new(attrs, existing_client) end
Binds attributes getters and setters to underlying data returned from the API. Setting any properties will alter the `raw_data` hash.
@return (Module) with the mapped getters and setters defined. Can be `include`, `extend`, or `prepend` into a class or object
# File spaceship/lib/spaceship/base.rb, line 104 def mapping_module(attr_mapping) Module.new do attr_mapping.each do |source, dest| getter = dest.to_sym setter = "#{dest}=".to_sym define_method(getter) do raw_data.get(*source.split('.')) end define_method(setter) do |value| self.raw_data ||= DataHash.new({}) raw_data.set(source.split('.'), value) end end end end
Call a method to return a subclass constant.
If `method_sym` is an underscored name of a class, return the class with the current client passed into it. If the method does not match, NoMethodError is raised.
Example:
Certificate.production_push #=> Certificate::ProductionPush ProvisioningProfile.ad_hoc #=> ProvisioningProfile::AdHoc ProvisioningProfile.some_other_method #=> NoMethodError: undefined method `some_other_method' for ProvisioningProfile
# File spaceship/lib/spaceship/base.rb, line 183 def method_missing(method_sym, *args, &block) module_name = method_sym.to_s module_name.sub!(/^[a-z\d]/) { $&.upcase } module_name.gsub!(%r{(?:_|(/))([a-z\d])}) { $2.upcase } if const_defined?(module_name) klass = const_get(module_name) klass.set_client(@client) else super end end
The initialize method accepts a parsed response from Apple and sets all attributes that are defined by `attr_mapping`
Do not override `initialize` in your own models.
# File spaceship/lib/spaceship/base.rb, line 228 def initialize(attrs = {}, existing_client = nil) attrs.each do |key, val| self.send("#{key}=", val) if respond_to?("#{key}=") end self.raw_data = DataHash.new(attrs) @client = existing_client || self.class.client self.setup end
Sets client and returns self for chaining. @return (Spaceship::Base
) rubocop:disable Naming/AccessorMethodName
# File spaceship/lib/spaceship/base.rb, line 93 def set_client(client) self.client = client self end
Public Instance Methods
# File spaceship/lib/spaceship/base.rb, line 262 def attributes self.class.attributes end
@!group Inspect related code
# File spaceship/lib/spaceship/base.rb, line 270 def inspect # To avoid circular references, we keep track of the references # of all objects already inspected from the first call to inspect # in this call stack # We use a Thread local storage for multi-thread friendliness thread = Thread.current tree_root = thread[:inspected_objects].nil? thread[:inspected_objects] = Set.new if tree_root if thread[:inspected_objects].include?(self) # already inspected objects have a default value, # let's follow Ruby's convention for circular references value = "#<Object ...>" else thread[:inspected_objects].add(self) begin value = inspect_value ensure thread[:inspected_objects] = nil if tree_root end end "<#{self.class.name} \n#{value}>" end
This method can be used by subclasses to do additional initialisation using the `raw_data`
# File spaceship/lib/spaceship/base.rb, line 239 def setup; end
# File spaceship/lib/spaceship/base.rb, line 304 def to_s self.inspect end
Private Instance Methods
# File spaceship/lib/spaceship/base.rb, line 295 def inspect_value self.attributes.map do |k| v = self.send(k).inspect v = v.gsub("\n", "\n\t") # to align nested elements "\t#{k}=#{v}" end.join(", \n") end