Class: JIRA::Base
- Inherits:
-
Object
- Object
- JIRA::Base
- Defined in:
- lib/jira/base.rb
Overview
This class provides the basic object <-> REST mapping for all JIRA::Resource subclasses, i.e. the Create, Retrieve, Update, Delete lifecycle methods.
Lifecycle methods
Note that not all lifecycle methods are available for all resources, for example some resources cannot be updated or deleted.
Retrieving all resources
client.Resource.all
Retrieving a single resource
client.Resource.find(id)
Creating a resource
resource = client.Resource.build({'name' => '')
resource.save
Updating a resource
resource = client.Resource.find(id)
resource.save('updated_attribute' => 'new value')
Deleting a resource
resource = client.Resource.find(id)
resource.delete
Nested resources
Some resources are not defined in the top level of the URL namespace within the JIRA API, but are always nested under the context of another resource. For example, a JIRA::Resource::Comment always belongs to a JIRA::Resource::Issue.
These resources must be indexed and built from an instance of the class they are nested under:
issue = client.Issue.find(id)
comments = issue.comments
new_comment = issue.comments.build
Direct Known Subclasses
Resource::Agile, Resource::ApplicationLink, Resource::Attachment, Resource::Board, Resource::BoardConfiguration, Resource::Comment, Resource::Component, Resource::Createmeta, Resource::Field, Resource::Filter, Resource::Issue, Resource::IssuePickerSuggestions, Resource::IssuePickerSuggestionsIssue, Resource::Issuelink, Resource::Issuelinktype, Resource::Issuetype, Resource::Priority, Resource::Project, Resource::RapidView, Resource::Remotelink, Resource::Resolution, Resource::ServerInfo, Resource::Sprint, Resource::SprintReport, Resource::Status, Resource::SuggestedIssue, Resource::Transition, Resource::User, Resource::Version, Resource::Watcher, Resource::Webhook, Resource::Worklog
Constant Summary collapse
- QUERY_PARAMS_FOR_SINGLE_FETCH =
Set.new %i[expand fields]
- QUERY_PARAMS_FOR_SEARCH =
Set.new %i[expand fields startAt maxResults]
Instance Attribute Summary collapse
-
#attrs ⇒ Object
The hash of attributes belonging to this instance.
-
#client ⇒ Object
readonly
A reference to the JIRA::Client used to initialize this resource.
-
#deleted ⇒ Object
(also: #deleted?)
Returns true if this instance has been deleted from the server.
-
#expanded ⇒ Object
(also: #expanded?)
Returns true if this instance has been fetched from the server.
Class Method Summary collapse
-
.all(client, options = {}) ⇒ Object
The class methods are never called directly, they are always invoked from a BaseFactory subclass instance.
- .belongs_to(resource) ⇒ Object
- .belongs_to_relationships ⇒ Object
-
.build(client, attrs) ⇒ Object
Builds a new instance of the resource with the given attributes.
- .collection_attributes_are_nested ⇒ Object
-
.collection_path(client, prefix = '/') ⇒ Object
Returns the full path for a collection of this resource.
-
.endpoint_name ⇒ Object
Returns the name of this resource for use in URL components.
-
.find(client, key, options = {}) ⇒ Object
Finds and retrieves a resource with the given ID.
-
.has_many(collection, options = {}) ⇒ Object
Declares that this class contains a collection of another resource within the JSON returned from the JIRA API.
-
.has_one(resource, options = {}) ⇒ Object
Declares that this class contains a singular instance of another resource within the JSON returned from the JIRA API.
-
.key_attribute ⇒ Object
Returns the attribute name of the attribute used for find.
- .nested_collections(value) ⇒ Object
-
.parse_json(string) ⇒ Object
:nodoc:.
-
.singular_path(client, key, prefix = '/') ⇒ Object
Returns the singular path for the resource with the given key.
Instance Method Summary collapse
- #collection_path(prefix = '/') ⇒ Object
-
#delete ⇒ Object
Sends a delete request to the JIRA Api and sets the deleted instance variable on the object to true.
-
#fetch(reload = false, query_params = {}) ⇒ Object
Fetches the attributes for the specified resource from JIRA unless the resource is already expanded and the optional force reload flag is not set.
- #has_errors? ⇒ Boolean
- #id ⇒ Object
-
#initialize(client, options = {}) ⇒ Base
constructor
A new instance of Base.
-
#key_value ⇒ Object
Each resource has a unique key attribute, this method returns the value of that key for this instance.
-
#method_missing(method_name, *_args) ⇒ Object
Overrides method_missing to check the attribute hash for resources matching method_name and proxies the call to the superclass if no match is found.
-
#new_record? ⇒ Boolean
Determines if the resource is newly created by checking whether its key_value is set.
-
#patched_url ⇒ Object
This method fixes issue that there is no / prefix in url.
-
#path_component ⇒ Object
This returns the URL path component that is specific to this instance, for example for Issue id 123 it returns ‘/issue/123’.
-
#respond_to?(method_name, _include_all = false) ⇒ Boolean
Checks if method_name is set in the attributes hash and returns true when found, otherwise proxies the call to the superclass.
-
#save(attrs, path = url) ⇒ Object
Saves the specified resource attributes by sending either a POST or PUT request to JIRA, depending on resource.new_record?.
-
#save!(attrs, path = nil) ⇒ Object
Saves the specified resource attributes by sending either a POST or PUT request to JIRA, depending on resource.new_record?.
-
#set_attrs(hash, clobber = true, target = nil) ⇒ Object
Set the current attributes from a hash.
-
#set_attrs_from_response(response) ⇒ Object
Sets the attributes hash from a HTTPResponse object from JIRA if it is not nil or is not a json response.
-
#to_json(options = {}) ⇒ Object
Returns a JSON representation of the current attributes hash.
- #to_s ⇒ Object
-
#to_sym ⇒ Object
Returns a symbol for the given instance, for example JIRA::Resource::Issue returns :issue.
- #url ⇒ Object
Constructor Details
#initialize(client, options = {}) ⇒ Base
Returns a new instance of Base.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/jira/base.rb', line 72 def initialize(client, = {}) @client = client @attrs = [:attrs] || {} @expanded = [:expanded] || false @deleted = false # If this class has any belongs_to relationships, a value for # each of them must be passed in to the initializer. self.class.belongs_to_relationships.each do |relation| if [relation] instance_variable_set("@#{relation}", [relation]) instance_variable_set("@#{relation}_id", [relation].key_value) elsif ["#{relation}_id".to_sym] instance_variable_set("@#{relation}_id", ["#{relation}_id".to_sym]) else raise ArgumentError, "Required option #{relation.inspect} missing" unless [relation] end end end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name, *_args) ⇒ Object
Overrides method_missing to check the attribute hash for resources matching method_name and proxies the call to the superclass if no match is found.
301 302 303 304 305 306 307 |
# File 'lib/jira/base.rb', line 301 def method_missing(method_name, *_args) if attrs.key?(method_name.to_s) attrs[method_name.to_s] else super(method_name) end end |
Instance Attribute Details
#attrs ⇒ Object
The hash of attributes belonging to this instance. An exact representation of the JSON returned from the JIRA API
67 68 69 |
# File 'lib/jira/base.rb', line 67 def attrs @attrs end |
#client ⇒ Object (readonly)
A reference to the JIRA::Client used to initialize this resource.
57 58 59 |
# File 'lib/jira/base.rb', line 57 def client @client end |
#deleted ⇒ Object Also known as: deleted?
Returns true if this instance has been deleted from the server
63 64 65 |
# File 'lib/jira/base.rb', line 63 def deleted @deleted end |
#expanded ⇒ Object Also known as: expanded?
Returns true if this instance has been fetched from the server
60 61 62 |
# File 'lib/jira/base.rb', line 60 def @expanded end |
Class Method Details
.all(client, options = {}) ⇒ Object
The class methods are never called directly, they are always invoked from a BaseFactory subclass instance.
94 95 96 97 98 99 100 101 |
# File 'lib/jira/base.rb', line 94 def self.all(client, = {}) response = client.get(collection_path(client)) json = parse_json(response.body) json = json[endpoint_name.pluralize] if collection_attributes_are_nested json.map do |attrs| new(client, { attrs: attrs }.merge()) end end |
.belongs_to(resource) ⇒ Object
263 264 265 266 267 |
# File 'lib/jira/base.rb', line 263 def self.belongs_to(resource) belongs_to_relationships.push(resource) attr_reader resource attr_reader "#{resource}_id" end |
.belongs_to_relationships ⇒ Object
259 260 261 |
# File 'lib/jira/base.rb', line 259 def self.belongs_to_relationships @belongs_to_relationships ||= [] end |
.build(client, attrs) ⇒ Object
Builds a new instance of the resource with the given attributes. These attributes will be posted to the JIRA Api if save is called.
113 114 115 |
# File 'lib/jira/base.rb', line 113 def self.build(client, attrs) new(client, attrs: attrs) end |
.collection_attributes_are_nested ⇒ Object
269 270 271 |
# File 'lib/jira/base.rb', line 269 def self.collection_attributes_are_nested @collection_attributes_are_nested ||= false end |
.collection_path(client, prefix = '/') ⇒ Object
129 130 131 |
# File 'lib/jira/base.rb', line 129 def self.collection_path(client, prefix = '/') client.[:rest_base_path] + prefix + endpoint_name end |
.endpoint_name ⇒ Object
121 122 123 |
# File 'lib/jira/base.rb', line 121 def self.endpoint_name name.split('::').last.downcase end |
.find(client, key, options = {}) ⇒ Object
Finds and retrieves a resource with the given ID.
104 105 106 107 108 109 |
# File 'lib/jira/base.rb', line 104 def self.find(client, key, = {}) instance = new(client, ) instance.attrs[key_attribute.to_s] = key instance.fetch(false, query_params_for_single_fetch()) instance end |
.has_many(collection, options = {}) ⇒ Object
Declares that this class contains a collection of another resource within the JSON returned from the JIRA API.
class Example < JIRA::Base
has_many :children
end
example = client.Example.find(1)
example.children # Returns an instance of Jira::Resource::HasManyProxy,
# which behaves exactly like an array of
# Jira::Resource::Child
The following options can be used to override the default behaviour of the relationship:
- :attribute_key
-
The relationship will by default reference a JSON key on the object with the same name as the relationship.
has_many :children # => {"id":"123",{"children":[{"id":"456"},{"id":"789"}]}}
Use this option if the key in the JSON is named differently.
# Respond to resource.children, but return the value of resource.attrs['kids'] has_many :children, :attribute_key => 'kids' # => {"id":"123",{"kids":[{"id":"456"},{"id":"789"}]}}
- :class
-
The class of the child instance will be inferred from the name of the relationship. E.g.
has_many :children
will return an instance ofJIRA::Resource::HasManyProxy
containing the collection ofJIRA::Resource::Child
. Use this option to override the inferred class.has_many :children, :class => JIRA::Resource::Kid
- :nested_under
-
In some cases, the JSON return from JIRA is nested deeply for particular relationships. This option allows the nesting to be specified.
# Specify a single depth of nesting. has_many :children, :nested_under => 'foo' # => Looks for {"foo":{"children":{}}} # Specify deeply nested JSON has_many :children, :nested_under => ['foo', 'bar', 'baz'] # => Looks for {"foo":{"bar":{"baz":{"children":{}}}}}
245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/jira/base.rb', line 245 def self.has_many(collection, = {}) attribute_key = [:attribute_key] || collection.to_s child_class = [:class] || ('JIRA::Resource::' + collection.to_s.classify).constantize self_class_basename = name.split('::').last.downcase.to_sym define_method(collection) do = { self_class_basename => self } attribute = maybe_nested_attribute(attribute_key, [:nested_under]) || [] collection = attribute.map do |child_attributes| child_class.new(client, .merge(attrs: child_attributes)) end HasManyProxy.new(self, child_class, collection) end end |
.has_one(resource, options = {}) ⇒ Object
Declares that this class contains a singular instance of another resource within the JSON returned from the JIRA API.
class Example < JIRA::Base
has_one :child
end
example = client.Example.find(1)
example.child # Returns a JIRA::Resource::Child
The following options can be used to override the default behaviour of the relationship:
- :attribute_key
-
The relationship will by default reference a JSON key on the object with the same name as the relationship.
has_one :child # => {"id":"123",{"child":{"id":"456"}}}
Use this option if the key in the JSON is named differently.
# Respond to resource.child, but return the value of resource.attrs['kid'] has_one :child, :attribute_key => 'kid' # => {"id":"123",{"kid":{"id":"456"}}}
- :class
-
The class of the child instance will be inferred from the name of the relationship. E.g.
has_one :child
will return aJIRA::Resource::Child
. Use this option to override the inferred class.has_one :child, :class => JIRA::Resource::Kid
- :nested_under
-
In some cases, the JSON return from JIRA is nested deeply for particular relationships. This option allows the nesting to be specified.
# Specify a single depth of nesting. has_one :child, :nested_under => 'foo' # => Looks for {"foo":{"child":{}}} # Specify deeply nested JSON has_one :child, :nested_under => ['foo', 'bar', 'baz'] # => Looks for {"foo":{"bar":{"baz":{"child":{}}}}}
194 195 196 197 198 199 200 201 202 |
# File 'lib/jira/base.rb', line 194 def self.has_one(resource, = {}) attribute_key = [:attribute_key] || resource.to_s child_class = [:class] || ('JIRA::Resource::' + resource.to_s.classify).constantize define_method(resource) do attribute = maybe_nested_attribute(attribute_key, [:nested_under]) return nil unless attribute child_class.new(client, attrs: attribute) end end |
.key_attribute ⇒ Object
Returns the attribute name of the attribute used for find. Defaults to :id unless overridden.
149 150 151 |
# File 'lib/jira/base.rb', line 149 def self.key_attribute :id end |
.nested_collections(value) ⇒ Object
273 274 275 |
# File 'lib/jira/base.rb', line 273 def self.nested_collections(value) @collection_attributes_are_nested = value end |
.parse_json(string) ⇒ Object
:nodoc:
153 154 155 |
# File 'lib/jira/base.rb', line 153 def self.parse_json(string) # :nodoc: JSON.parse(string) end |
.singular_path(client, key, prefix = '/') ⇒ Object
Returns the singular path for the resource with the given key. E.g.
JIRA::Resource::Issue.singular_path('123')
# => /jira/rest/api/2/issue/123
If a prefix parameter is provided it will be injected between the base path and the endpoint. E.g.
JIRA::Resource::Comment.singular_path('456','/issue/123/')
# => /jira/rest/api/2/issue/123/comment/456
143 144 145 |
# File 'lib/jira/base.rb', line 143 def self.singular_path(client, key, prefix = '/') collection_path(client, prefix) + '/' + key end |
Instance Method Details
#collection_path(prefix = '/') ⇒ Object
315 316 317 318 |
# File 'lib/jira/base.rb', line 315 def collection_path(prefix = '/') # Just proxy this to the class method self.class.collection_path(client, prefix) end |
#delete ⇒ Object
Sends a delete request to the JIRA Api and sets the deleted instance variable on the object to true.
409 410 411 412 |
# File 'lib/jira/base.rb', line 409 def delete client.delete(url) @deleted = true end |
#fetch(reload = false, query_params = {}) ⇒ Object
Fetches the attributes for the specified resource from JIRA unless the resource is already expanded and the optional force reload flag is not set
332 333 334 335 336 337 |
# File 'lib/jira/base.rb', line 332 def fetch(reload = false, query_params = {}) return if && !reload response = client.get(url_with_query_params(url, query_params)) set_attrs_from_response(response) @expanded = true end |
#has_errors? ⇒ Boolean
414 415 416 |
# File 'lib/jira/base.rb', line 414 def has_errors? respond_to?('errors') end |
#id ⇒ Object
277 278 279 |
# File 'lib/jira/base.rb', line 277 def id attrs['id'] end |
#key_value ⇒ Object
Each resource has a unique key attribute, this method returns the value of that key for this instance.
311 312 313 |
# File 'lib/jira/base.rb', line 311 def key_value @attrs[self.class.key_attribute.to_s] end |
#new_record? ⇒ Boolean
Determines if the resource is newly created by checking whether its key_value is set. If it is nil, the record is new and the method will return true.
461 462 463 |
# File 'lib/jira/base.rb', line 461 def new_record? key_value.nil? end |
#patched_url ⇒ Object
This method fixes issue that there is no / prefix in url. It is happened when we call for instance Looks like this issue is actual only in case if you use atlassian sdk your app path is not root (like /jira in example below) issue.save() for existing resource. As a result we got error 400 from JIRA API:
- 07/Jun/2015:15:32:19 +0400
-
“PUT jira/rest/api/2/issue/10111 HTTP/1.1” 400 -
After applying this fix we have normal response:
- 07/Jun/2015:15:17:18 +0400
-
“PUT /jira/rest/api/2/issue/10111 HTTP/1.1” 204 -
443 444 445 446 447 |
# File 'lib/jira/base.rb', line 443 def patched_url result = url return result if result.start_with?('/', 'http') "/#{result}" end |
#path_component ⇒ Object
This returns the URL path component that is specific to this instance, for example for Issue id 123 it returns ‘/issue/123’. For an unsaved issue it returns ‘/issue’
323 324 325 326 327 |
# File 'lib/jira/base.rb', line 323 def path_component path_component = "/#{self.class.endpoint_name}" path_component += '/' + key_value if key_value path_component end |
#respond_to?(method_name, _include_all = false) ⇒ Boolean
Checks if method_name is set in the attributes hash and returns true when found, otherwise proxies the call to the superclass.
290 291 292 293 294 295 296 |
# File 'lib/jira/base.rb', line 290 def respond_to?(method_name, _include_all = false) if attrs.key?(method_name.to_s) true else super(method_name) end end |
#save(attrs, path = url) ⇒ Object
Saves the specified resource attributes by sending either a POST or PUT request to JIRA, depending on resource.new_record?
Accepts an attributes hash of the values to be saved. Will return false if the request fails.
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'lib/jira/base.rb', line 359 def save(attrs, path = url) begin save_status = save!(attrs, path) rescue JIRA::HTTPError => exception begin set_attrs_from_response(exception.response) # Merge error status generated by JIRA REST API rescue JSON::ParserError => parse_exception set_attrs('exception' => { 'class' => exception.response.class.name, 'code' => exception.response.code, 'message' => exception.response. }) end # raise exception save_status = false end save_status end |
#save!(attrs, path = nil) ⇒ Object
Saves the specified resource attributes by sending either a POST or PUT request to JIRA, depending on resource.new_record?
Accepts an attributes hash of the values to be saved. Will throw a JIRA::HTTPError if the request fails (response is not HTTP 2xx).
344 345 346 347 348 349 350 351 352 |
# File 'lib/jira/base.rb', line 344 def save!(attrs, path = nil) path ||= new_record? ? url : patched_url http_method = new_record? ? :post : :put response = client.send(http_method, path, attrs.to_json) set_attrs(attrs, false) set_attrs_from_response(response) @expanded = false true end |
#set_attrs(hash, clobber = true, target = nil) ⇒ Object
Set the current attributes from a hash. If clobber is true, any existing hash values will be clobbered by the new hash, otherwise the hash will be deeply merged into attrs. The target paramater is for internal use only and should not be used.
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
# File 'lib/jira/base.rb', line 391 def set_attrs(hash, clobber = true, target = nil) target ||= @attrs if clobber target.merge!(hash) hash else hash.each do |k, v| if v.is_a?(Hash) set_attrs(v, clobber, target[k]) else target[k] = v end end end end |
#set_attrs_from_response(response) ⇒ Object
Sets the attributes hash from a HTTPResponse object from JIRA if it is not nil or is not a json response.
380 381 382 383 384 385 |
# File 'lib/jira/base.rb', line 380 def set_attrs_from_response(response) unless response.body.nil? || (response.body.length < 2) json = self.class.parse_json(response.body) set_attrs(json) end end |
#to_json(options = {}) ⇒ Object
Returns a JSON representation of the current attributes hash.
454 455 456 |
# File 'lib/jira/base.rb', line 454 def to_json( = {}) attrs.to_json() end |
#to_s ⇒ Object
449 450 451 |
# File 'lib/jira/base.rb', line 449 def to_s "#<#{self.class.name}:#{object_id} @attrs=#{@attrs.inspect}>" end |
#to_sym ⇒ Object
Returns a symbol for the given instance, for example JIRA::Resource::Issue returns :issue
283 284 285 |
# File 'lib/jira/base.rb', line 283 def to_sym self.class.endpoint_name.to_sym end |
#url ⇒ Object
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/jira/base.rb', line 418 def url prefix = '/' unless self.class.belongs_to_relationships.empty? prefix = self.class.belongs_to_relationships.inject(prefix) do |prefix_so_far, relationship| prefix_so_far.to_s + relationship.to_s + '/' + send("#{relationship}_id").to_s + '/' end end if @attrs['self'] the_url = @attrs['self'] the_url = the_url.sub(@client.[:site].chomp('/'), '') if @client.[:site] the_url elsif key_value self.class.singular_path(client, key_value.to_s, prefix) else self.class.collection_path(client, prefix) end end |