diff --git a/Gemfile.lock b/Gemfile.lock index 19f7e0d9..35bf7947 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,7 +50,7 @@ GIT GIT remote: git://github.com/travis-ci/travis-core.git - revision: a82f25fb00a39c3c64b8c09c716c206e6f4c6fad + revision: 4cfa414b1d384e518d567c70d572b34a74cbf0bf specs: travis-core (0.0.1) actionmailer (~> 3.2.19) diff --git a/lib/travis/api/v3.rb b/lib/travis/api/v3.rb index 824f90bd..d2c19859 100644 --- a/lib/travis/api/v3.rb +++ b/lib/travis/api/v3.rb @@ -21,6 +21,7 @@ module Travis end extend self + load_dir("#{__dir__}/v3/extensions") load_dir("#{__dir__}/v3") ClientError = Error .create(status: 400) diff --git a/lib/travis/api/v3/access_control/generic.rb b/lib/travis/api/v3/access_control/generic.rb index 8c49dce9..fd68c26f 100644 --- a/lib/travis/api/v3/access_control/generic.rb +++ b/lib/travis/api/v3/access_control/generic.rb @@ -52,8 +52,17 @@ module Travis::API::V3 private def dispatch(object, method = caller_locations.first.base_label) - method = object.class.name.underscore + ?_.freeze + method + method = method_for(object.class, method) send(method, object) if respond_to?(method, true) end + + @@method_for_cache = Tool::ThreadLocal.new + + def method_for(type, method) + @@method_for_cache[[type, method]] ||= begin + prefix = type.name.sub(/^Travis::API::V3::Models::/, ''.freeze).underscore + "#{prefix}_#{method}" + end + end end end diff --git a/lib/travis/api/v3/access_control/user.rb b/lib/travis/api/v3/access_control/user.rb index 181bb399..ecc54315 100644 --- a/lib/travis/api/v3/access_control/user.rb +++ b/lib/travis/api/v3/access_control/user.rb @@ -5,6 +5,7 @@ module Travis::API::V3 attr_reader :user, :permissions def initialize(user) + user = Models::User.find(user.id) if user.is_a? ::User @user = user @permissions = user.permissions.where(user_id: user.id) super() diff --git a/lib/travis/api/v3/extensions/belongs_to.rb b/lib/travis/api/v3/extensions/belongs_to.rb new file mode 100644 index 00000000..28aaa918 --- /dev/null +++ b/lib/travis/api/v3/extensions/belongs_to.rb @@ -0,0 +1,49 @@ +module Travis::API::V3 + module Extensions + # This is a patch to ActiveRecord to allow classes for polymorphic relations to be nested in a module without the + # module name being part of the type field. + # + # Example: + # + # # Without this patch + # Repository.find(2).owner.class # => User + # Travis::API::V3::Models::Repository.find(2).owner.class # => User + # + # # With this patch + # Repository.find(2).owner.class # => User + # Travis::API::V3::Models::Repository.find(2).owner.class # => Travis::API::V3::Models::User + # + # ActiveRecord does not support this out of the box. We accomplish this feature by tracking polymorphic relations + # and then adding the namespace when calling ActiveRecord::Base#[] with the foreign type key and removing it again + # in ActiveRecord::Base#[]=, so we don't break other code by accidentially writing the prefixed version to the + # database. + module BelongsTo + module ClassMethods + def polymorfic_foreign_types + @polymorfic_foreign_types ||= [] + end + + def belongs_to(field, options = {}) + polymorfic_foreign_types << (options[:foreign_type] || "#{field}_type") if options[:polymorphic] + super + end + end + + def self.included(base) + base.extend(ClassMethods) + super + end + + def [](key) + value = super + value &&= "#{self.class.parent}::#{value}" if self.class.polymorfic_foreign_types.include?(key) + value + end + + def []=(key, value) + value &&= value.sub("#{self.class.parent}::", ''.freeze) if self.class.polymorfic_foreign_types.include?(key) + super(key, value) + end + end + end +end diff --git a/lib/travis/api/v3/model.rb b/lib/travis/api/v3/model.rb new file mode 100644 index 00000000..90b26d1d --- /dev/null +++ b/lib/travis/api/v3/model.rb @@ -0,0 +1,6 @@ +module Travis::API::V3 + class Model < ActiveRecord::Base + include Extensions::BelongsTo + self.abstract_class = true + end +end diff --git a/lib/travis/api/v3/models.rb b/lib/travis/api/v3/models.rb new file mode 100644 index 00000000..3504a13a --- /dev/null +++ b/lib/travis/api/v3/models.rb @@ -0,0 +1,5 @@ +module Travis::API::V3 + module Models + extend ConstantResolver + end +end diff --git a/lib/travis/api/v3/models/broadcast.rb b/lib/travis/api/v3/models/broadcast.rb new file mode 100644 index 00000000..9c713d8e --- /dev/null +++ b/lib/travis/api/v3/models/broadcast.rb @@ -0,0 +1,5 @@ +module Travis::API::V3 + class Models::Broadcast < Model + belongs_to :recipient, polymorphic: true + end +end diff --git a/lib/travis/api/v3/models/build.rb b/lib/travis/api/v3/models/build.rb new file mode 100644 index 00000000..9b7f766b --- /dev/null +++ b/lib/travis/api/v3/models/build.rb @@ -0,0 +1,10 @@ +module Travis::API::V3 + class Models::Build < Model + belongs_to :repository + belongs_to :commit + belongs_to :request + belongs_to :repository, autosave: true + belongs_to :owner, polymorphic: true + has_many :jobs, as: :source, order: :id, dependent: :destroy + end +end diff --git a/lib/travis/api/v3/models/commit.rb b/lib/travis/api/v3/models/commit.rb new file mode 100644 index 00000000..ac1add22 --- /dev/null +++ b/lib/travis/api/v3/models/commit.rb @@ -0,0 +1,6 @@ +module Travis::API::V3 + class Models::Commit < Model + belongs_to :repository + has_one :request + end +end diff --git a/lib/travis/api/v3/models/email.rb b/lib/travis/api/v3/models/email.rb new file mode 100644 index 00000000..6fd9cd7e --- /dev/null +++ b/lib/travis/api/v3/models/email.rb @@ -0,0 +1,5 @@ +module Travis::API::V3 + class Models::Email < Model + belongs_to :user + end +end diff --git a/lib/travis/api/v3/models/job.rb b/lib/travis/api/v3/models/job.rb new file mode 100644 index 00000000..2db5df10 --- /dev/null +++ b/lib/travis/api/v3/models/job.rb @@ -0,0 +1,10 @@ +module Travis::API::V3 + class Models::Job < Model + has_one :log, dependent: :destroy + belongs_to :repository + belongs_to :commit + belongs_to :build, autosave: true, foreign_key: 'source_id' + belongs_to :owner, polymorphic: true + serialize :config + end +end diff --git a/lib/travis/api/v3/models/log.rb b/lib/travis/api/v3/models/log.rb new file mode 100644 index 00000000..d1c3219b --- /dev/null +++ b/lib/travis/api/v3/models/log.rb @@ -0,0 +1,7 @@ +module Travis::API::V3 + class Models::Log < Model + belongs_to :job + belongs_to :removed_by, class_name: 'User', foreign_key: :removed_by + has_many :log_parts, dependent: :destroy + end +end diff --git a/lib/travis/api/v3/models/log_part.rb b/lib/travis/api/v3/models/log_part.rb new file mode 100644 index 00000000..43fc7370 --- /dev/null +++ b/lib/travis/api/v3/models/log_part.rb @@ -0,0 +1,5 @@ +module Travis::API::V3 + class Models::LogPart < Model + belongs_to :log + end +end diff --git a/lib/travis/api/v3/models/membership.rb b/lib/travis/api/v3/models/membership.rb new file mode 100644 index 00000000..b32fe2ea --- /dev/null +++ b/lib/travis/api/v3/models/membership.rb @@ -0,0 +1,6 @@ +module Travis::API::V3 + class Models::Membership < Model + belongs_to :user + belongs_to :organization + end +end diff --git a/lib/travis/api/v3/models/organization.rb b/lib/travis/api/v3/models/organization.rb new file mode 100644 index 00000000..e42e3364 --- /dev/null +++ b/lib/travis/api/v3/models/organization.rb @@ -0,0 +1,7 @@ +module Travis::API::V3 + class Models::Organization < Model + has_many :memberships + has_many :users, through: :memberships + has_many :repositories, as: :owner + end +end diff --git a/lib/travis/api/v3/models/permission.rb b/lib/travis/api/v3/models/permission.rb new file mode 100644 index 00000000..7c592903 --- /dev/null +++ b/lib/travis/api/v3/models/permission.rb @@ -0,0 +1,6 @@ +module Travis::API::V3 + class Models::Permission < Model + belongs_to :user + belongs_to :repository + end +end diff --git a/lib/travis/api/v3/models/repository.rb b/lib/travis/api/v3/models/repository.rb new file mode 100644 index 00000000..67aa37e8 --- /dev/null +++ b/lib/travis/api/v3/models/repository.rb @@ -0,0 +1,23 @@ +module Travis::API::V3 + class Models::Repository < Model + has_many :commits, dependent: :delete_all + has_many :requests, dependent: :delete_all + has_many :builds, dependent: :delete_all + has_many :permissions, dependent: :delete_all + has_many :users, through: :permissions + + belongs_to :owner, polymorphic: true + + has_one :last_build, + class_name: 'Travis::API::V3::Models::Build'.freeze, + order: 'id DESC'.freeze + + def slug + @slug ||= "#{owner_name}/#{name}" + end + + def last_build_on(branch) + builds.order('id DESC'.freeze).where(branch: branch, event_type: 'push'.freeze).first + end + end +end diff --git a/lib/travis/api/v3/models/request.rb b/lib/travis/api/v3/models/request.rb new file mode 100644 index 00000000..fd1387a8 --- /dev/null +++ b/lib/travis/api/v3/models/request.rb @@ -0,0 +1,10 @@ +module Travis::API::V3 + class Models::Request < Model + belongs_to :commit + belongs_to :repository + belongs_to :owner, polymorphic: true + has_many :builds + serialize :config + serialize :payload + end +end diff --git a/lib/travis/api/v3/models/ssl_key.rb b/lib/travis/api/v3/models/ssl_key.rb new file mode 100644 index 00000000..eb45758c --- /dev/null +++ b/lib/travis/api/v3/models/ssl_key.rb @@ -0,0 +1,5 @@ +module Travis::API::V3 + class Models::SSLKey < Model + belongs_to :repository + end +end diff --git a/lib/travis/api/v3/models/user.rb b/lib/travis/api/v3/models/user.rb new file mode 100644 index 00000000..c07f3a4b --- /dev/null +++ b/lib/travis/api/v3/models/user.rb @@ -0,0 +1,9 @@ +module Travis::API::V3 + class Models::User < Model + has_many :memberships, dependent: :destroy + has_many :organizations, through: :memberships + has_many :permissions, dependent: :destroy + has_many :repositories, through: :permissions + has_many :emails, dependent: :destroy + end +end diff --git a/lib/travis/api/v3/queries/build.rb b/lib/travis/api/v3/queries/build.rb index 7d1594a2..1f97e7c7 100644 --- a/lib/travis/api/v3/queries/build.rb +++ b/lib/travis/api/v3/queries/build.rb @@ -3,8 +3,8 @@ module Travis::API::V3 params :id def find - return ::Build.find_by_id(id) if id - raise WrongParams + return Models::Build.find_by_id(id) if id + raise WrongParams, 'missing build.id'.freeze end end end diff --git a/lib/travis/api/v3/queries/organization.rb b/lib/travis/api/v3/queries/organization.rb index a88f412c..8c07c910 100644 --- a/lib/travis/api/v3/queries/organization.rb +++ b/lib/travis/api/v3/queries/organization.rb @@ -3,8 +3,8 @@ module Travis::API::V3 params :id def find - return ::Organization.find_by_id(id) if id - raise WrongParams + return Models::Organization.find_by_id(id) if id + raise WrongParams, 'missing organization.id'.freeze end end end diff --git a/lib/travis/api/v3/queries/organizations.rb b/lib/travis/api/v3/queries/organizations.rb index 42d84d4f..faa3f68b 100644 --- a/lib/travis/api/v3/queries/organizations.rb +++ b/lib/travis/api/v3/queries/organizations.rb @@ -1,7 +1,7 @@ module Travis::API::V3 class Queries::Organizations < Query def for_member(user) - ::Organization.joins(:users).where(users: user_condition(user)) + Models::Organization.joins(:users).where(users: user_condition(user)) end end end diff --git a/lib/travis/api/v3/queries/repositories.rb b/lib/travis/api/v3/queries/repositories.rb index 1f4c0c22..04834ccd 100644 --- a/lib/travis/api/v3/queries/repositories.rb +++ b/lib/travis/api/v3/queries/repositories.rb @@ -8,7 +8,7 @@ module Travis::API::V3 def all @all ||= begin - all = ::Repository + all = Models::Repository all = all.where(active: bool(active)) unless active.nil? all = all.where(private: bool(private)) unless private.nil? all diff --git a/lib/travis/api/v3/queries/repository.rb b/lib/travis/api/v3/queries/repository.rb index fa1e6d35..d8ceaff9 100644 --- a/lib/travis/api/v3/queries/repository.rb +++ b/lib/travis/api/v3/queries/repository.rb @@ -3,8 +3,8 @@ module Travis::API::V3 params :id def find - return ::Repository.find_by_id(id) if id - raise WrongParams + return Models::Repository.find_by_id(id) if id + raise WrongParams, 'missing repository.id'.freeze end end end diff --git a/lib/travis/api/v3/query.rb b/lib/travis/api/v3/query.rb index 3676065f..48989490 100644 --- a/lib/travis/api/v3/query.rb +++ b/lib/travis/api/v3/query.rb @@ -46,9 +46,9 @@ module Travis::API::V3 def user_condition(value) case value - when String then { login: value } - when Integer then { id: value } - when ::User then { id: value.id } + when String then { login: value } + when Integer then { id: value } + when Models::User then { id: value.id } else raise WrongParams end end diff --git a/lib/travis/api/v3/renderer/model_renderer.rb b/lib/travis/api/v3/renderer/model_renderer.rb index 0c508172..65de8be1 100644 --- a/lib/travis/api/v3/renderer/model_renderer.rb +++ b/lib/travis/api/v3/renderer/model_renderer.rb @@ -47,17 +47,17 @@ module Travis::API::V3 result end - def render_model(model, type: model.class.name.to_sym, mode: :minimal, **options) + def render_model(model, type: model.class.name[/[^:]+$/].to_sym, mode: :minimal, **options) Renderer[type].render(model, mode, script_name: script_name, **options) end def render_value(value) case value - when Hash then value.map { |k, v| [k, render_value(v)] }.to_h - when Array then value.map { |v | render_value(v) } - when *PRIMITIVE then value - when Time then value.strftime('%Y-%m-%dT%H:%M:%SZ') - when Travis::Model then render_model(value) + when Hash then value.map { |k, v| [k, render_value(v)] }.to_h + when Array then value.map { |v | render_value(v) } + when *PRIMITIVE then value + when Time then value.strftime('%Y-%m-%dT%H:%M:%SZ') + when Model then render_model(value) else raise ArgumentError, 'cannot render %p (%p)' % [value.class, value] end end diff --git a/spec/v3/extensions/belongs_to_spec.rb b/spec/v3/extensions/belongs_to_spec.rb new file mode 100644 index 00000000..dd031ab5 --- /dev/null +++ b/spec/v3/extensions/belongs_to_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Travis::API::V3::Extensions::BelongsTo do + describe 'reading polymorphic relation' do + subject(:repo) { Travis::API::V3::Models::Repository.first } + example { expect(repo.owner).to be_a(Travis::API::V3::Models::User) } + end + + describe 'writing polymorphic relation' do + let(:repo) { Travis::API::V3::Models::Repository.create(owner: user) } + let(:user) { Travis::API::V3::Models::User.create } + after { repo.destroy; user.destroy } + + example { expect(repo.owner).to be_a(Travis::API::V3::Models::User) } + example { expect(::Repository.find(repo.id).owner).to be_a(::User) } + end +end \ No newline at end of file