Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HasOne Association #7

Open
wants to merge 2 commits into
base: add_association_has_many
Choose a base branch
from
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
11 changes: 11 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,21 @@ Style/ClassAndModuleChildren:

Metrics/LineLength:
Max: 100
Exclude:
- spec/**/*.rb
- examples/**/*.rb

Metrics/MethodLength:
Max: 25

Metrics/AbcSize:
Max: 25

Metrics/BlockLength:
Max: 100
Exclude:
- spec/pdc/**/*.rb
- examples/**/*.rb

Naming/PredicateName:
Enabled: false
1 change: 1 addition & 0 deletions lib/pdc/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Base
include PDC::Resource::Attributes
include PDC::Resource::Scopes
include PDC::Resource::RestApi
include PDC::Resource::Associations

scope :page, ->(value) { where(page: value) }
scope :page_size, ->(value) { where(page_size: value) }
Expand Down
1 change: 1 addition & 0 deletions lib/pdc/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
require 'pdc/resource/relation'
require 'pdc/resource/rest_api'
require 'pdc/resource/wip'
require 'pdc/resource/associations'
44 changes: 44 additions & 0 deletions lib/pdc/resource/associations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require 'pdc/resource/associations/has_many'
require 'pdc/resource/associations/association'
require 'pdc/resource/associations/builder'
require 'pdc/resource/associations/has_one'

module PDC::Resource
module Associations
extend ActiveSupport::Concern

included do
class_attribute :associations
self.associations = {}.freeze
end

module ClassMethods
def has_many(name, options = {})
create_association(name, HasMany, options)

define_method "#{name.to_s.singularize}_ids=" do |ids|
attributes[name] = []
ids.reject(&:blank?).each { |id| association(name).build(id: id) }
end

define_method "#{name.to_s.singularize}_ids" do
association(name).map(&:id)
end
end

def has_one(name, options = {})
create_association(name, HasOne, options)

define_method "build_#{name}" do |attributes = nil|
association(name).build(attributes)
end
end

private

def create_association(name, type, options)
self.associations = associations.merge(name => Builder.new(self, name, type, options))
end
end
end
end
25 changes: 25 additions & 0 deletions lib/pdc/resource/associations/association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'pdc/resource/relation'

module PDC::Resource
module Associations
class Association < Relation
attr_reader :parent, :name

def initialize(klass, parent, name, options = {})
super(klass, options)
@parent = parent
@name = name
end

def load
find_one! # Override for plural associations that return an association object
end

private

def foreign_key
(@options[:foreign_key] || "#{parent.class.model_name.element}_id").to_sym
end
end
end
end
44 changes: 44 additions & 0 deletions lib/pdc/resource/associations/builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require 'active_support/dependencies'

module PDC::Resource
module Associations
class Builder
def initialize(parent_class, name, type, options = {})
@parent_class = parent_class
@name = name
@type = type
@options = options
end

def build(parent)
@type.new(klass, parent, @name, @options)
end

def klass
@klass ||= custom_class || compute_class(@name)
end

private

def custom_class
@options[:class_name].constantize if @options[:class_name]
end

# https://github.com/rails/rails/blob/70ac072976c8cc6f013f0df3777e54ccae3f4f8c/activerecord/lib/active_record/inheritance.rb#L132-L150
def compute_class(type_name)
parent_name = @parent_class.to_s
type_name = type_name.to_s.classify

candidates = []
parent_name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
candidates << type_name

candidates.each do |candidate|
constant = ActiveSupport::Dependencies.safe_constantize(candidate)
return constant if candidate == constant.to_s
end
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
end
end
end
end
23 changes: 23 additions & 0 deletions lib/pdc/resource/associations/has_many.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'pdc/resource/associations/association'

module PDC::Resource
module Associations
class HasMany < Association
def initialize(*args)
super
@options.reverse_merge!(uri: "#{parent_path}/:#{foreign_key}/#{@name}/(:#{primary_key})")
@params[foreign_key] = parent.id
end

def load
self
end

private

def parent_path
parent.class.model_name.element.pluralize
end
end
end
end
29 changes: 29 additions & 0 deletions lib/pdc/resource/associations/has_one.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module PDC::Resource
module Associations
class HasOne < Association
def initialize(*args)
super
@options.reverse_merge!(uri: "#{parent.class.model_name.plural}/:#{foreign_key}/#{@name}")
collect_params
end

def collect_params
p_mapping = parent.class.mapping
s_foreign_key = foreign_key.to_s
if s_foreign_key.include? '/'
s_foreign_key.split('/').each do |fk|
p_mapping_value = p_mapping[fk.to_sym]
@params[fk] =
if p_mapping && p_mapping_value
parent.attributes[p_mapping_value.to_sym]
else
parent.attributes[fk]
end
end
else
@params[foreign_key] = parent.id
end
end
end
end
end
13 changes: 11 additions & 2 deletions lib/pdc/resource/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,24 @@ def use_setters(attributes)
end

def method_missing(name, *args, &block)
if attribute?(name) then attribute(name)
if association?(name) then association(name).load
elsif attribute?(name) then attribute(name)
elsif predicate?(name) then predicate(name)
elsif setter?(name) then set_attribute(name, args.first)
else super
end
end

def respond_to_missing?(name, include_private = false)
attribute?(name) || predicate?(name) || setter?(name) || super
association?(name) || attribute?(name) || predicate?(name) || setter?(name) || super
end

def association?(name)
associations.key?(name)
end

def association(name)
associations[name].build(self)
end

def attribute?(name)
Expand Down
8 changes: 8 additions & 0 deletions lib/pdc/resource/identity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ def resource_path
@resource_path ||= model_name.collection.sub(%r{^pdc\/}, '').tr('_', '-')
end

def mapping
@mapping || {}
end

def mapping=(mapping = {})
@mapping = mapping
end

private

def default_uri
Expand Down
1 change: 1 addition & 0 deletions lib/pdc/resource/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Relation

attr_reader :klass
attr_writer :params
delegate :to_ary, :[], :any?, :empty?, :last, :size, :metadata, to: :contents!

alias all to_a

Expand Down
141 changes: 141 additions & 0 deletions spec/fixtures/vcr/array_like_behavior.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading