activeproject 0.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.
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ # Handles configuration for the ActiveProject gem, including adapter settings.
5
+ class Configuration
6
+ attr_reader :adapter_configs
7
+ attr_accessor :user_agent
8
+
9
+ # Maps adapter names (symbols) to their specific configuration classes.
10
+ # Add other adapters here when they need specific config classes.
11
+ ADAPTER_CONFIG_CLASSES = {
12
+ trello: Configurations::TrelloConfiguration
13
+ # :jira => Configurations::JiraConfiguration,
14
+ # :basecamp => Configurations::BasecampConfiguration,
15
+ }.freeze
16
+
17
+ def initialize
18
+ @adapter_configs = {}
19
+ end
20
+ @user_agent = "ActiveProject Gem (github.com/seuros/activeproject)"
21
+
22
+ # Adds or updates the configuration for a specific adapter.
23
+ # If a block is given and a specific configuration class exists for the adapter,
24
+ # an instance of that class is yielded to the block. Otherwise, a basic
25
+ # configuration object is created from the options hash.
26
+ #
27
+ # @param name [Symbol] The name of the adapter (e.g., :jira, :trello).
28
+ # @param options [Hash] Configuration options for the adapter (e.g., site, api_key, token).
29
+ # @yield [BaseAdapterConfiguration] Yields an adapter-specific configuration object if a block is given.
30
+ def add_adapter(name, options = {}, &block)
31
+ unless name.is_a?(Symbol)
32
+ raise ArgumentError, "Adapter name must be a Symbol (e.g., :jira)"
33
+ end
34
+
35
+ config_class = ADAPTER_CONFIG_CLASSES[name]
36
+
37
+ # Use specific config class if block is given and class exists
38
+ if block && config_class
39
+ adapter_config_obj = config_class.new(options)
40
+ yield adapter_config_obj # Allow block to modify the specific config object
41
+ @adapter_configs[name] = adapter_config_obj.freeze
42
+ # Use specific config class if no block but class exists (handles options like status_mappings passed directly)
43
+ elsif config_class
44
+ adapter_config_obj = config_class.new(options)
45
+ @adapter_configs[name] = adapter_config_obj.freeze
46
+ # Fallback to base config class if no specific class or no block
47
+ else
48
+ @adapter_configs[name] = Configurations::BaseAdapterConfiguration.new(options).freeze
49
+ end
50
+ end
51
+
52
+ # Retrieves the configuration object for a specific adapter.
53
+ # @param name [Symbol] The name of the adapter.
54
+ # @return [BaseAdapterConfiguration, nil] The configuration object or nil if not found.
55
+ def adapter_config(name)
56
+ @adapter_configs[name]
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ module Configurations
5
+ # Base class for adapter configurations, holding common options.
6
+ class BaseAdapterConfiguration
7
+ attr_reader :options
8
+
9
+ def initialize(options = {})
10
+ @options = options.dup # Duplicate to allow modification before freezing
11
+ end
12
+
13
+ # Allow accessing options via method calls
14
+ def method_missing(method_name, *arguments, &block)
15
+ if options.key?(method_name) && arguments.empty? && !block
16
+ options[method_name]
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def respond_to_missing?(method_name, include_private = false)
23
+ options.key?(method_name) || super
24
+ end
25
+
26
+ def freeze
27
+ @options.freeze
28
+ super
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ module Configurations
5
+ # Holds Trello-specific configuration options.
6
+ class TrelloConfiguration < BaseAdapterConfiguration
7
+ # @!attribute [rw] status_mappings
8
+ # @return [Hash] Mappings from Board IDs to List ID/Name to Status Symbol.
9
+ # @example
10
+ # {
11
+ # 'board_id_1' => { 'list_id_open' => :open, 'list_id_closed' => :closed },
12
+ # 'board_id_2' => { 'Done List Name' => :closed } # Example using names (less reliable)
13
+ # }
14
+ attr_accessor :status_mappings
15
+
16
+ def initialize(options = {})
17
+ super
18
+ @status_mappings = options.delete(:status_mappings) || {}
19
+ end
20
+
21
+ def freeze
22
+ # Ensure nested hashes are also frozen
23
+ @status_mappings.each do |board_id, mappings|
24
+ mappings.freeze
25
+ end
26
+ @status_mappings.freeze
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ # Base class for all ActiveProject errors
5
+ class Error < StandardError; end
6
+
7
+ # Raised when authentication with the external API fails
8
+ class AuthenticationError < Error; end
9
+
10
+ # Raised when a requested resource is not found
11
+ class NotFoundError < Error; end
12
+
13
+ # Raised when the external API rate limit is exceeded
14
+ class RateLimitError < Error; end
15
+
16
+ # Raised for general API errors (e.g., 5xx status codes)
17
+ class ApiError < Error
18
+ attr_reader :original_error, :status_code, :response_body
19
+
20
+ def initialize(message = nil, original_error: nil, status_code: nil, response_body: nil)
21
+ super(message)
22
+ @original_error = original_error
23
+ @status_code = status_code
24
+ @response_body = response_body
25
+ end
26
+ end
27
+
28
+ # Raised for validation errors (e.g., 400/422 status codes with field details)
29
+ class ValidationError < ApiError
30
+ attr_reader :errors
31
+
32
+ def initialize(message = nil, errors: {}, original_error: nil, status_code: nil, response_body: nil)
33
+ super(message, original_error: original_error, status_code: status_code, response_body: response_body)
34
+ @errors = errors # Expects a hash like { field: ['message1', 'message2'] }
35
+ end
36
+ end
37
+
38
+ # Raised when an adapter method is not implemented
39
+ class NotImplementedError < Error; end
40
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ # Factory class for creating and finding resource objects (Project, Issue, etc.)
5
+ # associated with a specific adapter.
6
+ class ResourceFactory
7
+ # @param adapter [Adapters::Base] The adapter instance.
8
+ # @param resource_class [Class] The resource class (e.g., Resources::Project).
9
+ def initialize(adapter:, resource_class:)
10
+ @adapter = adapter
11
+ @resource_class = resource_class
12
+ end
13
+
14
+ # Fetches all resources of the associated type.
15
+ # Delegates to the appropriate adapter list method.
16
+ # @param args [Array] Arguments to pass to the adapter's list method (e.g., project_id for issues).
17
+ # @return [Array<BaseResource>] An array of resource objects.
18
+ def all(*args)
19
+ list_method = determine_list_method
20
+ options = args.last.is_a?(Hash) ? args.pop : {}
21
+ primary_arg = args.first
22
+
23
+ # Call adapter method with appropriate arguments
24
+ if primary_arg
25
+ @adapter.send(list_method, primary_arg, options)
26
+ else
27
+ # Handle case where list method might not take options (like list_projects)
28
+ if @adapter.method(list_method).arity == 0
29
+ @adapter.send(list_method)
30
+ else
31
+ @adapter.send(list_method, options)
32
+ end
33
+ end
34
+ end
35
+
36
+ # Finds a specific resource by its ID.
37
+ # Delegates to the appropriate adapter find method.
38
+ # @param id [String, Integer] The ID or key of the resource.
39
+ # @param context [Hash] Optional context needed by some find methods (e.g., :project_id for Basecamp issues).
40
+ # @return [BaseResource, nil] The found resource object or nil.
41
+ def find(id, context = {})
42
+ find_method = determine_find_method
43
+
44
+ # Pass context only if provided and the find method accepts it
45
+ if !context.empty?
46
+ @adapter.send(find_method, id, context)
47
+ else
48
+ @adapter.send(find_method, id)
49
+ end
50
+ rescue ActiveProject::NotFoundError
51
+ nil # Return nil if the resource is not found by the adapter
52
+ end
53
+
54
+ # Fetches the first resource of the associated type.
55
+ # @param args [Array] Arguments to pass to the adapter's list method.
56
+ # @return [BaseResource, nil] The first resource object found or nil.
57
+ def first(*args)
58
+ all(*args).first
59
+ end
60
+
61
+ # Filters resources based on given conditions (client-side).
62
+ # @param conditions [Hash] Conditions to filter by.
63
+ # @param list_args [Array] Arguments for the underlying #all call.
64
+ # @return [Array<BaseResource>] Matching resources.
65
+ def where(conditions, *list_args)
66
+ resources = all(*list_args)
67
+ resources.select do |resource|
68
+ conditions.all? do |key, value|
69
+ resource.respond_to?(key) && resource.send(key) == value
70
+ end
71
+ end
72
+ end
73
+
74
+ # Builds a new, unsaved resource instance.
75
+ # @param attributes [Hash] Attributes for the new resource.
76
+ # @return [BaseResource] A new instance of the resource class.
77
+ def build(attributes = {})
78
+ merged_attrs = attributes.merge(
79
+ adapter_source: @adapter.class.name.split("::").last.sub("Adapter", "").downcase.to_sym,
80
+ raw_data: attributes
81
+ )
82
+ @resource_class.new(@adapter, merged_attrs)
83
+ end
84
+
85
+ # Builds and saves a new resource instance.
86
+ # @param attributes [Hash] Attributes for the new resource.
87
+ # @return [BaseResource] The created resource object.
88
+ # @raise [NotImplementedError] Currently raises because #save is not implemented.
89
+ def create(attributes = {})
90
+ # Determine the correct adapter create method based on resource type
91
+ create_method = determine_create_method
92
+ # Note: Assumes create methods on adapters take attributes hash directly
93
+ # Context like project_id needs to be part of the attributes hash if required by adapter
94
+ @adapter.send(create_method, attributes)
95
+ # A full implementation would likely involve build then save:
96
+ # resource = build(attributes)
97
+ # resource.save
98
+ # resource
99
+ end
100
+
101
+ private
102
+
103
+ def determine_list_method
104
+ method_name = case @resource_class.name
105
+ when "ActiveProject::Resources::Project" then :list_projects
106
+ when "ActiveProject::Resources::Issue" then :list_issues
107
+ else raise "Cannot determine list method for #{@resource_class.name}"
108
+ end
109
+ raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}" unless @adapter.respond_to?(method_name)
110
+ method_name
111
+ end
112
+
113
+ def determine_find_method
114
+ method_name = case @resource_class.name
115
+ when "ActiveProject::Resources::Project" then :find_project
116
+ when "ActiveProject::Resources::Issue" then :find_issue
117
+ else raise "Cannot determine find method for #{@resource_class.name}"
118
+ end
119
+ raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}" unless @adapter.respond_to?(method_name)
120
+ method_name
121
+ end
122
+
123
+ def determine_create_method
124
+ singular_name = @resource_class.name.split("::").last.downcase.to_sym
125
+ method_name = :"create_#{singular_name}"
126
+ raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}" unless @adapter.respond_to?(method_name)
127
+ method_name
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ module Resources
5
+ # Base class for resource objects (Project, Issue, etc.)
6
+ # Provides common initialization and attribute access via method_missing.
7
+ class BaseResource
8
+ attr_reader :adapter, :raw_data, :attributes
9
+
10
+ # @param adapter [Adapters::Base] The adapter instance that fetched/created this resource.
11
+ # @param attributes [Hash] A hash of attributes for the resource.
12
+ def initialize(adapter, attributes = {})
13
+ @adapter = adapter
14
+ # Store raw_data if provided, otherwise duplicate the input attributes
15
+ @raw_data = attributes.key?(:raw_data) ? attributes[:raw_data] : attributes.dup
16
+ # Store attributes for method_missing access, remove raw_data key if it exists
17
+ @attributes = attributes.dup
18
+ @attributes.delete(:raw_data)
19
+ end
20
+
21
+ # Basic inspection using defined members if available, otherwise attributes.
22
+ def inspect
23
+ members_to_show = self.class.members.empty? ? @attributes.keys : self.class.members
24
+ attrs_str = members_to_show.map { |m| "#{m}=#{send(m).inspect}" }.join(", ")
25
+ "#<#{self.class.name} #{attrs_str}>"
26
+ end
27
+
28
+ # Method missing for accessing attributes stored in @attributes hash.
29
+ def method_missing(method_name, *arguments, &block)
30
+ if @attributes.key?(method_name)
31
+ # Return attribute value if no arguments are given (getter)
32
+ arguments.empty? ? @attributes[method_name] : super
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ # Ensure respond_to? works correctly with method_missing.
39
+ def respond_to_missing?(method_name, include_private = false)
40
+ @attributes.key?(method_name) || super
41
+ end
42
+
43
+ # Class method to define expected members (mainly for introspection/documentation).
44
+ def self.members
45
+ @members ||= []
46
+ end
47
+
48
+ # Defines expected members for the resource class.
49
+ def self.def_members(*args)
50
+ @members ||= []
51
+ @members.concat(args.map(&:to_sym))
52
+ # No explicit attr_reader needed when using method_missing
53
+ end
54
+
55
+ # Placeholder methods for ORM-like behavior
56
+ def save
57
+ raise NotImplementedError, "#save not yet implemented for #{self.class.name}"
58
+ end
59
+
60
+ def update(attributes)
61
+ raise NotImplementedError, "#update not yet implemented for #{self.class.name}"
62
+ end
63
+
64
+ def delete
65
+ raise NotImplementedError, "#delete not yet implemented for #{self.class.name}"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_resource"
4
+
5
+ module ActiveProject
6
+ module Resources
7
+ # Represents a Comment on an Issue
8
+ class Comment < BaseResource
9
+ def_members :id, :body, :author, :created_at, :updated_at, :issue_id,
10
+ :adapter_source
11
+ # raw_data and adapter are inherited from BaseResource
12
+
13
+ # Add comment-specific methods here later (e.g., save, update, delete)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ module Resources
5
+ # Represents an Issue (e.g., Jira Issue, Trello Card, Basecamp Todo)
6
+ class Issue < BaseResource
7
+ def_members :id, :key, :title, :description, :status, :assignees,
8
+ :reporter, :project_id, :created_at, :updated_at, :due_on,
9
+ :priority, :adapter_source
10
+ # raw_data and adapter are inherited from BaseResource
11
+
12
+
13
+ # Saves the issue (creates if new, updates if existing).
14
+ # Placeholder - Full implementation requires attribute tracking and adapter delegation.
15
+ # @return [Boolean] true if save was successful, false otherwise.
16
+ def save
17
+ raise NotImplementedError, "#save not yet implemented for #{self.class.name}"
18
+ end
19
+
20
+ # Updates the issue with the given attributes and saves it.
21
+ # Placeholder - Full implementation requires attribute tracking and adapter delegation.
22
+ # @param attributes [Hash] Attributes to update.
23
+ # @return [Boolean] true if update was successful, false otherwise.
24
+ def update(attributes)
25
+ # Basic implementation could be:
26
+ # attributes.each { |k, v| instance_variable_set("@#{k}", v) if respond_to?(k) } # Need setters or direct ivar access
27
+ # save
28
+ raise NotImplementedError, "#update not yet implemented for #{self.class.name}"
29
+ end
30
+
31
+
32
+ # Returns an association proxy for accessing comments on this issue.
33
+ # @return [AssociationProxy<Resources::Comment>]
34
+ def comments
35
+ AssociationProxy.new(owner: self, adapter: @adapter, association_name: :comments)
36
+ end
37
+
38
+ # Add issue-specific methods here later (e.g., comments association, save, update)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ module Resources
5
+ # Represents a Project (e.g., Jira Project, Trello Board, Basecamp Project)
6
+ class Project < BaseResource
7
+ def_members :id, :key, :name, :adapter_source
8
+ # raw_data and adapter are inherited from BaseResource
9
+
10
+
11
+ # Returns an association proxy for accessing issues within this project.
12
+ # @return [AssociationProxy<Resources::Issue>]
13
+ def issues
14
+ AssociationProxy.new(owner: self, adapter: @adapter, association_name: :issues)
15
+ end
16
+
17
+ # Add project-specific methods here later (e.g., issues association)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_resource"
4
+
5
+ module ActiveProject
6
+ module Resources
7
+ # Represents a User
8
+ class User < BaseResource
9
+ def_members :id, :name, :email, :adapter_source
10
+ # raw_data and adapter are inherited from BaseResource
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveProject
2
+ VERSION = "0.0.0"
3
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveProject
4
+ # Represents a standardized event parsed from a webhook payload.
5
+ # Using Struct for simplicity for now. Could be a full class inheriting BaseResource if needed.
6
+ WebhookEvent = Struct.new(
7
+ :event_type, # Symbol representing the event (e.g., :issue_created, :comment_added)
8
+ :object_kind, # Symbol representing the type of object involved (e.g., :issue, :comment)
9
+ :event_object_id, # String or Integer ID of the primary object (renamed from object_id)
10
+ :object_key, # String key/slug of the primary object (if applicable, e.g., Jira issue key)
11
+ :project_id, # String or Integer ID of the associated project/board/bucket
12
+ :actor, # User resource or Hash representing the user who triggered the event
13
+ :timestamp, # Time object representing when the event occurred
14
+ :adapter_source, # Symbol identifying the source adapter (e.g., :jira, :trello, :basecamp)
15
+ :changes, # Hash detailing specific changes (if applicable, e.g., for updates)
16
+ :object_data, # Optional: Hash containing more detailed data about the object
17
+ :raw_data, # The original, parsed webhook payload hash
18
+ keyword_init: true
19
+ )
20
+ end
@@ -0,0 +1,61 @@
1
+ require "zeitwerk"
2
+ require_relative "active_project/errors"
3
+ require_relative "active_project/version"
4
+
5
+ module ActiveProject
6
+ class << self
7
+ attr_writer :configuration
8
+
9
+ def configuration
10
+ @configuration ||= Configuration.new
11
+ end
12
+
13
+ def configure
14
+ yield(configuration)
15
+ end
16
+
17
+
18
+ # Returns the configured User-Agent string, including the gem version.
19
+ # @return [String] The User-Agent string.
20
+ def user_agent
21
+ base_agent = configuration.user_agent || "ActiveProject Gem (github.com/seuros/activeproject)"
22
+ "#{base_agent} v#{ActiveProject::VERSION}"
23
+ end
24
+
25
+ # Returns a memoized instance of the requested adapter.
26
+ # @param adapter_name [Symbol] The name of the adapter (e.g., :jira, :trello).
27
+ # @return [Adapters::Base] An instance of the requested adapter.
28
+ # @raise [ArgumentError] if the adapter configuration is missing or invalid.
29
+ # @raise [LoadError] if the adapter class cannot be found.
30
+ def adapter(adapter_name)
31
+ @adapters ||= {}
32
+ @adapters[adapter_name] ||= begin
33
+ config = configuration.adapter_config(adapter_name)
34
+
35
+ unless config.is_a?(ActiveProject::Configurations::BaseAdapterConfiguration)
36
+ raise ArgumentError, "Configuration for adapter ':#{adapter_name}' not found or invalid. Use ActiveProject.configure."
37
+ end
38
+
39
+ # Use string-based constant lookup with the full namespace path
40
+ adapter_class_name = "ActiveProject::Adapters::#{adapter_name.to_s.capitalize}Adapter"
41
+
42
+ # Ensure the adapter class is loaded
43
+ require "active_project/adapters/#{adapter_name}_adapter"
44
+
45
+ # Get the constant with the full path
46
+ adapter_class = Object.const_get(adapter_class_name)
47
+
48
+ adapter_class.new(config: config)
49
+ rescue LoadError, NameError => e
50
+ raise LoadError, "Could not find adapter class #{adapter_class_name}: #{e.message}"
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
57
+ loader.inflector.inflect("activeproject" => "ActiveProject")
58
+ loader.do_not_eager_load("#{__dir__}/active_project/adapters")
59
+ loader.ignore("#{__dir__}/active_project/errors.rb")
60
+ loader.ignore("#{__dir__}/active_project/version.rb")
61
+ loader.setup
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activeproject
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Abdelkader Boudih
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-04-07 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '8.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: faraday
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: faraday-retry
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ - !ruby/object:Gem::Dependency
61
+ name: mocha
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ description: Provides a unified API client for interacting with various project management
75
+ platforms like Jira, Basecamp, and Trello. Aims to normalize core models (projects,
76
+ tasks, comments) and workflows for easier integration in Rails applications.
77
+ email:
78
+ - terminale@gmail.com
79
+ executables: []
80
+ extensions: []
81
+ extra_rdoc_files: []
82
+ files:
83
+ - MIT-LICENSE
84
+ - README.md
85
+ - Rakefile
86
+ - lib/active_project/adapters/base.rb
87
+ - lib/active_project/adapters/basecamp_adapter.rb
88
+ - lib/active_project/adapters/jira_adapter.rb
89
+ - lib/active_project/adapters/trello_adapter.rb
90
+ - lib/active_project/association_proxy.rb
91
+ - lib/active_project/configuration.rb
92
+ - lib/active_project/configurations/base_adapter_configuration.rb
93
+ - lib/active_project/configurations/trello_configuration.rb
94
+ - lib/active_project/errors.rb
95
+ - lib/active_project/resource_factory.rb
96
+ - lib/active_project/resources/base_resource.rb
97
+ - lib/active_project/resources/comment.rb
98
+ - lib/active_project/resources/issue.rb
99
+ - lib/active_project/resources/project.rb
100
+ - lib/active_project/resources/user.rb
101
+ - lib/active_project/version.rb
102
+ - lib/active_project/webhook_event.rb
103
+ - lib/activeproject.rb
104
+ homepage: https://github.com/seuros/activeproject
105
+ licenses:
106
+ - MIT
107
+ metadata:
108
+ homepage_uri: https://github.com/seuros/activeproject
109
+ source_code_uri: https://github.com/seuros/activeproject
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubygems_version: 3.6.2
125
+ specification_version: 4
126
+ summary: A standardized Ruby interface for multiple project management APIs (Jira,
127
+ Basecamp, Trello, etc.).
128
+ test_files: []