Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ lib/bundler/man
pkg
rdoc
spec/reports
spec/fixtures/vcr_cassettes
test/tmp
test/version_tmp
tmp
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ gemfile:
- Gemfile
- gemfiles/rails_3.2.gemfile
- gemfiles/rails_4.0.gemfile
script:
- bundle exec rake
15 changes: 15 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
1 Setters for has many associations:
# project.tasks << new_nask

Impediment: has_many association doesn't know how current entity named in collections.
For example: GeneralUser has_many Assignables, but Assignable doesn't have any
GeneralUser directly - Assignable belongs to GeneralUser as owner.

2 (Need to discuss) belongs_to associations setters works fine when called on entity:
# task.owner = general_user
# task.save
But we still can not pass entity inside #new:
# task = TargetProcess::Task.new(name: "aaa", project: tp_project_instance)
# task.save #=> Error

3 Update README
27 changes: 5 additions & 22 deletions lib/target_process.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
require 'target_process/api_error'
require 'target_process/api_client'
require 'target_process/base'
Dir["./lib/target_process/entities/*.rb"].each {|file| require file }

module TargetProcess
class ConfigurationError < StandardError; end

def self.configuration
msg = "TargetProcess is not configured yet"
msg = 'TargetProcess is not configured yet'
@configuration || raise(TargetProcess::ConfigurationError.new(msg))
end

Expand All @@ -21,26 +22,8 @@ def self.configure
yield(@configuration)
end

def self.context(options={})
options.each { |item| item.to_s.slice(1..-2) if item.is_a?(Array)}
TargetProcess.client.get("context/", options)
def self.context(options = {})
options.each { |item| item.to_s.slice(1..-2) if item.is_a?(Array) }
TargetProcess.client.get('context/', options)
end

ENTITIES = ["Task", "UserStory", "Feature", "Bug", "User", "Project",
"Release", "Iteration", "Request", "TestCase", "Impediment",
"Comment", "Process", "Priority", "Severity", "EntityState",
"Program", "Testplan", "TestPlanRun", "TestCaseRun", "Time",
"Assignment", "Role", "RoleEffort", "ProjectMember", "Build",
"Company", "CustomActivity", "Attachment", "EntityType",
"General", "Assignable", "GeneralUser", "RequestType", "Message",
"MessageUid", "Milestone", "Relation", "RelationType",
"Requester", "Revision", "RevisionFile", "Tag", "Team",
"TeamIteration", "TeamMember", "TeamProject"]

init_code = ""
ENTITIES.each do |name|
init_code += "class #{name}; include Base; end \n"
end

eval init_code
end
41 changes: 21 additions & 20 deletions lib/target_process/api_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
module TargetProcess
class APIClient

def get(path, options={})
def get(path, options = {})
options.merge!(format: 'json')
options = {body: options}
options = { body: options }
response = perform(:get, path, options)
normalize_response(response.parsed_response)
end

def post(path, attr_hash)
content = prepare_data(attr_hash).to_json
options = {body: content, headers: {'Content-Type' => 'application/json'}}
options = { body: content,
headers: { 'Content-Type' => 'application/json' } }
response = perform(:post, path, options)
normalize_response(response.parsed_response)
end
Expand All @@ -25,7 +26,7 @@ def delete(path)

private

def perform(type, path, options={})
def perform(type, path, options = {})
auth = { username: TargetProcess.configuration.username,
password: TargetProcess.configuration.password }
options.merge!(basic_auth: auth)
Expand All @@ -41,36 +42,36 @@ def check_for_api_errors(response)
end

def generate_url(path)
if TargetProcess.configuration.api_url[-1] == "/"
if TargetProcess.configuration.api_url[-1] == '/'
TargetProcess.configuration.api_url + path
else
TargetProcess.configuration.api_url + "/" + path
TargetProcess.configuration.api_url + '/' + path
end
end

def normalize_response(hash)
hash = Hash[hash.map {|k, v| [k.underscore.to_sym, v] }]
hash.each do |k,v|
hash = Hash[hash.map { |k, v| [k.underscore.to_sym, v] }]
hash.each do |k, v|
hash[k] = case v
when Hash
normalize_response(v)
when Array
v.collect! { |el| normalize_response(el) }
when /Date\((\d+)-(\d+)\)/
::Time.at($1.to_i/1000)
else
v
end
when Hash
normalize_response(v)
when Array
v.collect! { |el| normalize_response(el) }
when /Date\((\d+)-(\d+)\)/
::Time.at($1.to_i / 1000)
else
v
end
end
end

def json_date(time)
"\/Date(#{time.to_i}000+0#{time.utc_offset/3600}00)\/"
"\/Date(#{time.to_i}000+0#{time.utc_offset / 3600}00)\/"
end

def prepare_data(hash)
hash = Hash[hash.map {|k, v| [k.to_s.camelize.to_sym, v] }]
hash.each { |k,v| hash[k] = json_date(v) if v.is_a?(::Time) }
hash = Hash[hash.map { |k, v| [k.to_s.camelize.to_sym, v] }]
hash.each { |k, v| hash[k] = json_date(v) if v.is_a?(::Time) }
end
end
end
9 changes: 4 additions & 5 deletions lib/target_process/api_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@ class Unauthorized < APIError; end

def self.parse(response)
error = response['Error']
status = error['Status'] || response['Status'] || "Undefined"
status = error['Status'] || response['Status'] || 'Undefined'
message = raw_message(response.parsed_response)
self.constants.include?(status.to_sym) ?
"#{self}::#{status}".safe_constantize.new(message) :
self.new(message)
type = "#{self}::#{status}".safe_constantize
constants.include?(status.to_sym) ? type.new(message) : new(message)
end

def self.raw_message(response)
case response
when Hash
response["Error"]["Message"]
response['Error']['Message']
when String
response.match(/<title>(.+)<\/title>/)[1]
end
Expand Down
184 changes: 102 additions & 82 deletions lib/target_process/base.rb
Original file line number Diff line number Diff line change
@@ -1,115 +1,135 @@
require 'active_support/inflector'

module TargetProcess
module Base

def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
end

class Base
attr_reader :attributes, :changed_attributes

module InstanceMethods
def initialize(hash={})
@changed_attributes = hash
@attributes = {}
end
def initialize(hash = {})
@changed_attributes = hash
@attributes = {}
end

def delete
path = entity_path
resp = TargetProcess.client.delete(path)
true if resp.code == "200"
end
def delete
resp = TargetProcess.client.delete(entity_path)
resp.code == '200' ? true : false
end

def save
path = self.class.collection_path
@changed_attributes.merge!(id: @attributes[:id]) if @attributes[:id]
resp = TargetProcess.client.post(path, @changed_attributes)
@changed_attributes = {}
@attributes.merge!(resp)
self
end
def save
path = self.class.collection_path
@changed_attributes.merge!(id: @attributes[:id]) if @attributes[:id]
resp = TargetProcess.client.post(path, @changed_attributes)
@changed_attributes = {}
@attributes.merge!(resp)
self
end

def ==(obj)
if self.class == obj.class && self.all_attrs-obj.all_attrs==[]
(self.all_attrs|obj.all_attrs).all? do |key|
self.send(key) == obj.send(key)
end
else
false
def ==(other)
if self.class == other.class && all_attrs - other.all_attrs == []
(all_attrs | other.all_attrs).all? do |key|
send(key) == other.send(key)
end
else
false
end
end

def method_missing(name, *args)
if self.respond_to?(name)
if name.to_s.match(/=\z/)
key = name.to_s.delete("=").to_sym
if @attributes[key] == args.first
@changed_attributes.delete(key)
else
@changed_attributes[key] = args.first
end
def method_missing(name, *args)
if respond_to_missing?(name)
if name.to_s.match(/=\z/)
key = name.to_s.delete('=').to_sym
if @attributes[key] == args.first
@changed_attributes.delete(key)
else
if @changed_attributes.has_key?(name)
@changed_attributes[name]
else
@attributes[name]
end
@changed_attributes[key] = args.first
end
else
super
if @changed_attributes.has_key?(name)
@changed_attributes[name]
else
@attributes[name]
end
end
else
super
end
end

def respond_to_missing?(name, include_private = false)
if name.to_s.match(/\A[a-z_]+\z/) && self.all_attrs.include?(name)
true
elsif name.to_s.match(/\A[a-z_]+=\z/) && name != :id=
true
else
super
end
def respond_to_missing?(name, include_private = false)
if name.to_s.match(/\A[a-z_]+\z/) && all_attrs.include?(name)
true
elsif name.to_s.match(/\A[a-z_]+=\z/) && name != :id=
true
else
super
end
end

def entity_path
self.class.collection_path + @attributes[:id].to_s
end
def entity_path
self.class.collection_path + @attributes[:id].to_s + '/'
end

def all_attrs
@attributes.keys | @changed_attributes.keys
end
def all_attrs
@attributes.keys | @changed_attributes.keys
end

module ClassMethods
def where(params_str, options={})
options.merge!(where: params_str)
self.all(options)
end
def self.where(params_str, options = {})
options.merge!(where: params_str)
all(options)
end

def all(options={})
path = collection_path
TargetProcess.client.get(path, options)[:items].collect! do |hash|
result = self.new
result.attributes.merge!(hash)
result || []
end
def self.all(options = {})
path = collection_path
TargetProcess.client.get(path, options)[:items].collect! do |hash|
entity = new
entity.attributes.merge!(hash)
entity
end
end

def self.find(id, options = {})
path = collection_path + id.to_s
entity = new
entity.attributes.merge!(TargetProcess.client.get(path, options))
entity
end

def find(id, options={})
path = collection_path + "#{id}"
result = self.new
result.attributes.merge!(TargetProcess.client.get(path, options))
result
def self.collection_path
to_s.demodulize.pluralize + '/'
end

def self.meta
TargetProcess.client.get(collection_path + '/meta')
end

def self.has_many(name, klass = nil)
klass ||= name.to_s.singularize.camelize
define_method(name) do
path = entity_path + name.to_s.camelize
collection = TargetProcess.client.get(path)[:items].collect do |hash|
entity = "TargetProcess::#{klass}".constantize.new
entity.attributes.merge!(hash)
entity
end
end
end

def collection_path
self.to_s.demodulize.pluralize + "/"
def self.belongs_to (name, klass = nil)
klass ||= name.to_s.camelize
define_method(name) do
if @attributes[name]
"TargetProcess::#{klass}".constantize.find(@attributes[name][:id])
else
nil
end
end

def meta
TargetProcess.client.get(collection_path + "/meta")
setter_name = (name.to_s + '=').to_sym
define_method(setter_name) do |val|
if val.class.to_s.demodulize == klass
@changed_attributes.merge!(name => { id: val.id })
end
end
end

end
end
Loading