hydra_attribute 0.4.2 → 0.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +6 -5
  3. data/CHANGELOG.md +6 -0
  4. data/Gemfile +1 -1
  5. data/README.md +3 -3
  6. data/Rakefile +2 -7
  7. data/gemfiles/activerecord-3.2.gemfile +5 -0
  8. data/hydra_attribute.gemspec +6 -7
  9. data/lib/hydra_attribute.rb +17 -18
  10. data/lib/hydra_attribute/active_record.rb +34 -13
  11. data/lib/hydra_attribute/active_record/association_preloader.rb +47 -28
  12. data/lib/hydra_attribute/active_record/attribute_methods.rb +29 -140
  13. data/lib/hydra_attribute/active_record/mass_assignment_security.rb +39 -0
  14. data/lib/hydra_attribute/active_record/migration.rb +4 -4
  15. data/lib/hydra_attribute/active_record/relation.rb +6 -7
  16. data/lib/hydra_attribute/active_record/relation/query_methods.rb +28 -18
  17. data/lib/hydra_attribute/hydra_attribute.rb +12 -49
  18. data/lib/hydra_attribute/hydra_attribute_set.rb +67 -0
  19. data/lib/hydra_attribute/hydra_entity.rb +110 -0
  20. data/lib/hydra_attribute/hydra_entity_attribute_association.rb +155 -0
  21. data/lib/hydra_attribute/hydra_set.rb +24 -26
  22. data/lib/hydra_attribute/hydra_value.rb +210 -0
  23. data/lib/hydra_attribute/identity_map.rb +18 -0
  24. data/lib/hydra_attribute/middleware/identity_map.rb +15 -0
  25. data/lib/hydra_attribute/migrator.rb +24 -21
  26. data/lib/hydra_attribute/model.rb +47 -0
  27. data/lib/hydra_attribute/model/cacheable.rb +207 -0
  28. data/lib/hydra_attribute/model/dirty.rb +39 -0
  29. data/lib/hydra_attribute/model/has_many_through.rb +168 -0
  30. data/lib/hydra_attribute/model/identity_map.rb +59 -0
  31. data/lib/hydra_attribute/model/mediator.rb +89 -0
  32. data/lib/hydra_attribute/model/notifiable.rb +23 -0
  33. data/lib/hydra_attribute/model/persistence.rb +424 -0
  34. data/lib/hydra_attribute/model/validations.rb +40 -0
  35. data/lib/hydra_attribute/version.rb +1 -1
  36. data/spec/environments/mysql.rb +23 -0
  37. data/spec/environments/postgresql.rb +23 -0
  38. data/spec/environments/sqlite.rb +12 -0
  39. data/spec/fixtures/category.rb +8 -0
  40. data/spec/fixtures/product.rb +8 -0
  41. data/spec/fixtures/product_black_list.rb +13 -0
  42. data/spec/fixtures/product_white_list.rb +13 -0
  43. data/spec/hydra_attribute/active_record/attribute_methods_spec.rb +23 -28
  44. data/spec/hydra_attribute/active_record/mass_assignment_security_spec.rb +41 -0
  45. data/spec/hydra_attribute/active_record_spec.rb +577 -0
  46. data/spec/hydra_attribute/hydra_attribute_set_spec.rb +651 -0
  47. data/spec/hydra_attribute/hydra_attribute_spec.rb +208 -10
  48. data/spec/hydra_attribute/hydra_entity_attribute_association_spec.rb +216 -0
  49. data/spec/hydra_attribute/hydra_entity_spec.rb +71 -0
  50. data/spec/hydra_attribute/hydra_set_spec.rb +51 -10
  51. data/spec/hydra_attribute/hydra_value_spec.rb +286 -0
  52. data/spec/hydra_attribute/identity_map_spec.rb +47 -0
  53. data/spec/hydra_attribute/migrator_spec.rb +411 -0
  54. data/spec/hydra_attribute/model/cacheable_spec.rb +106 -0
  55. data/spec/hydra_attribute/model/has_many_through_spec.rb +132 -0
  56. data/spec/hydra_attribute/model/identity_map_spec.rb +39 -0
  57. data/spec/hydra_attribute/model/mediator_spec.rb +62 -0
  58. data/spec/hydra_attribute/model/persistence_spec.rb +550 -0
  59. data/spec/hydra_attribute/model_spec.rb +39 -0
  60. data/spec/hydra_attribute_spec.rb +36 -0
  61. data/spec/spec_helper.rb +10 -42
  62. metadata +76 -100
  63. data/Appraisals +0 -7
  64. data/cucumber.yml +0 -1
  65. data/features/entity/create.feature +0 -145
  66. data/features/entity/destroy.feature +0 -111
  67. data/features/entity/new.feature +0 -121
  68. data/features/entity/update.feature +0 -147
  69. data/features/hydra_attribute/create.feature +0 -30
  70. data/features/hydra_attribute/destroy.feature +0 -26
  71. data/features/hydra_attribute/update.feature +0 -36
  72. data/features/hydra_set/destroy.feature +0 -31
  73. data/features/migrations/create_and_drop.feature +0 -165
  74. data/features/migrations/migrate_and_rollback.feature +0 -211
  75. data/features/relation/query_methods/group.feature +0 -42
  76. data/features/relation/query_methods/order.feature +0 -67
  77. data/features/relation/query_methods/reorder.feature +0 -29
  78. data/features/relation/query_methods/reverse_order.feature +0 -29
  79. data/features/relation/query_methods/select.feature +0 -50
  80. data/features/relation/query_methods/where.feature +0 -115
  81. data/features/step_definitions/connections.rb +0 -65
  82. data/features/step_definitions/model_steps.rb +0 -136
  83. data/features/step_definitions/query_methods.rb +0 -48
  84. data/features/step_definitions/record_steps.rb +0 -93
  85. data/features/support/env.rb +0 -38
  86. data/features/support/world.rb +0 -61
  87. data/lib/hydra_attribute/active_record/association.rb +0 -113
  88. data/lib/hydra_attribute/active_record/reflection.rb +0 -16
  89. data/lib/hydra_attribute/association_builder.rb +0 -69
  90. data/lib/hydra_attribute/builder.rb +0 -37
  91. data/lib/hydra_attribute/entity_callbacks.rb +0 -26
  92. data/lib/hydra_attribute/hydra_attribute_methods.rb +0 -226
  93. data/lib/hydra_attribute/hydra_methods.rb +0 -528
  94. data/lib/hydra_attribute/hydra_set_methods.rb +0 -95
  95. data/lib/hydra_attribute/hydra_value_methods.rb +0 -21
  96. data/lib/hydra_attribute/memoizable.rb +0 -37
  97. data/spec/hydra_attribute/active_record/relation/query_methods_spec.rb +0 -31
  98. data/spec/hydra_attribute/hydra_attribute_methods_spec.rb +0 -458
  99. data/spec/hydra_attribute/hydra_methods_spec.rb +0 -456
  100. data/spec/hydra_attribute/hydra_set_methods_spec.rb +0 -203
  101. data/spec/hydra_attribute/memoizable_spec.rb +0 -95
@@ -0,0 +1,39 @@
1
+ module HydraAttribute
2
+ module ActiveRecord
3
+ module MassAssignmentSecurity
4
+
5
+ class PermissionSet
6
+ def initialize(entity, authorizer)
7
+ @entity = entity
8
+ @authorizer = authorizer
9
+ end
10
+
11
+ def deny?(attribute_name)
12
+ hydra_attribute = hydra_attribute_by_name(attribute_name)
13
+ hydra_attribute ? !hydra_attribute.white_list : @authorizer.deny?(attribute_name)
14
+ end
15
+
16
+ private
17
+ def respond_to_missing?(method, include_private)
18
+ @authorizer.respond_to?(method, include_private)
19
+ end
20
+
21
+ def method_missing(method, *args, &block)
22
+ @authorizer.send(method, *args, &block)
23
+ end
24
+
25
+ # TODO should be optimized. List of allowed attributes should be cached
26
+ def hydra_attribute_by_name(attribute_name)
27
+ ::HydraAttribute::HydraAttribute.all_by_entity_type(@entity.class.name).find do |attribute|
28
+ attribute.name == attribute_name
29
+ end
30
+ end
31
+ end
32
+
33
+ protected
34
+ def mass_assignment_authorizer(role)
35
+ PermissionSet.new(self, super(role))
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,19 +2,19 @@ module HydraAttribute
2
2
  module ActiveRecord
3
3
  module Migration
4
4
  def create_hydra_entity(name, options = {}, &block)
5
- Migrator.create(self, name, options, &block)
5
+ ::HydraAttribute::Migrator.create(self, name, options, &block)
6
6
  end
7
7
 
8
8
  def drop_hydra_entity(name)
9
- Migrator.drop(self, name)
9
+ ::HydraAttribute::Migrator.drop(self, name)
10
10
  end
11
11
 
12
12
  def migrate_to_hydra_entity(name, options = {}, &block)
13
- Migrator.migrate(self, name, options, &block)
13
+ ::HydraAttribute::Migrator.migrate(self, name, options, &block)
14
14
  end
15
15
 
16
16
  def rollback_from_hydra_entity(name)
17
- Migrator.rollback(self, name)
17
+ ::HydraAttribute::Migrator.rollback(self, name)
18
18
  end
19
19
  end
20
20
  end
@@ -1,3 +1,7 @@
1
+ require 'hydra_attribute/active_record/relation/calculations'
2
+ require 'hydra_attribute/active_record/relation/query_methods'
3
+ require 'hydra_attribute/active_record/association_preloader'
4
+
1
5
  module HydraAttribute
2
6
  module ActiveRecord
3
7
  module Relation
@@ -6,16 +10,11 @@ module HydraAttribute
6
10
  included do
7
11
  include Calculation
8
12
  include QueryMethods
9
-
10
- # @COMPATIBILITY with 3.1.x active_record 3.1 doesn't have "exec_queries" method
11
- target = ::ActiveRecord::VERSION::STRING.starts_with?('3.1.') ? :to_a : :exec_queries
12
- alias_method :__old_exec_queries__, target
13
- alias_method target, :__exec_queries__
14
13
  end
15
14
 
16
- def __exec_queries__
15
+ def exec_queries
17
16
  return @records if loaded?
18
- records = __old_exec_queries__
17
+ records = super
19
18
  return records if records.empty?
20
19
 
21
20
  AssociationPreloader.run(self, records)
@@ -23,7 +23,7 @@ module HydraAttribute
23
23
 
24
24
  if opts.is_a?(Hash)
25
25
  opts.inject(self) do |relation, (name, value)|
26
- if klass.hydra_attribute_names.include?(name.to_s)
26
+ if ::HydraAttribute::HydraAttribute.names_by_entity_type(klass.model_name).include?(name.to_s)
27
27
  relation, name = relation.clone, name.to_s
28
28
  relation.hydra_attributes << name
29
29
  relation.hydra_joins_aliases << hydra_helper.ref_alias(name, value)
@@ -40,16 +40,12 @@ module HydraAttribute
40
40
  end
41
41
 
42
42
  def build_arel
43
+ # remove duplicate columns and add table prefix to all of them
43
44
  @group_values = hydra_helper.quote_columns(@group_values.uniq.reject(&:blank?))
44
45
  @order_values = hydra_helper.quote_columns(@order_values.uniq.reject(&:blank?))
45
46
 
46
- # @COMPATIBILITY with 3.1.x active_record 3.1 uses the separate @reorder_value instance
47
- if instance_variable_defined?(:@reorder_value) and instance_variable_get(:@reorder_value).present?
48
- @reorder_value = hydra_helper.quote_columns(@reorder_value.uniq.reject(&:blank?))
49
- end
50
-
51
47
  # detect hydra attributes from select list
52
- @hydra_select_values, @select_values = @select_values.partition { |value| klass.hydra_attribute_names.include?(value.to_s) }
48
+ @hydra_select_values, @select_values = @select_values.partition { |value| ::HydraAttribute::HydraAttribute.names_by_entity_type(klass.model_name).include?(value.to_s) }
53
49
  @hydra_select_values.map!(&:to_s)
54
50
  @select_values.map!{ |value| hydra_helper.prepend_table_name(value) }
55
51
 
@@ -61,8 +57,16 @@ module HydraAttribute
61
57
  @select_values << hydra_helper.prepend_table_name('hydra_set_id')
62
58
  end
63
59
 
64
- if hydra_attributes.any?
65
- hydra_sets = klass.hydra_sets.select { |hydra_set| hydra_set.hydra_attributes.any? { |attr| attr.name.in?(hydra_attributes) } }
60
+ # Add filter by hydra sets which have all these attributes
61
+ if hydra_attributes.any? or @hydra_select_values.any?
62
+ hydra_attribute_ids = (hydra_attributes | hydra_select_values).map { |name| hydra_helper.hydra_attribute_id(name) }
63
+
64
+ hydra_sets = ::HydraAttribute::HydraSet.all_by_entity_type(klass.model_name).select do |hydra_set|
65
+ hydra_attribute_ids.all? do |hydra_attribute_id|
66
+ hydra_set.has_hydra_attribute_id?(hydra_attribute_id)
67
+ end
68
+ end
69
+
66
70
  @where_values << table[:hydra_set_id].in(hydra_sets.map(&:id)).or(table[:hydra_set_id].eq(nil))
67
71
  end
68
72
 
@@ -125,13 +129,9 @@ module HydraAttribute
125
129
  {ref_alias(name, value) => {value: value}}
126
130
  end
127
131
 
128
- def ref_class(name)
129
- type = klass.hydra_attribute(name).backend_type
130
- AssociationBuilder.class_name(klass, type).constantize
131
- end
132
-
133
132
  def ref_table(name)
134
- ref_class(name).table_name
133
+ hydra_attribute = hydra_attribute_by_name(name)
134
+ "hydra_#{hydra_attribute.backend_type}_#{klass.table_name}"
135
135
  end
136
136
 
137
137
  def ref_alias(name, value)
@@ -139,17 +139,27 @@ module HydraAttribute
139
139
  end
140
140
 
141
141
  def join_type(value)
142
- value.nil? ? 'LEFT' : 'INNER'
142
+ if value.nil? or (value.is_a?(Array) and value.include?(nil))
143
+ 'LEFT'
144
+ else
145
+ 'INNER'
146
+ end
143
147
  end
144
148
 
145
149
  def hydra_attribute_id(name)
146
- klass.hydra_attribute(name).id
150
+ hydra_attribute_by_name(name).id
151
+ end
152
+
153
+ def hydra_attribute_by_name(name)
154
+ ::HydraAttribute::HydraAttribute.all_by_entity_type(klass.model_name).find do |hydra_attribute|
155
+ hydra_attribute.name == name.to_s
156
+ end
147
157
  end
148
158
 
149
159
  def quote_columns(columns)
150
160
  columns.map do |column|
151
161
  column = column.respond_to?(:to_sql) ? column.to_sql : column.to_s
152
- if klass.hydra_attribute_names.include?(column)
162
+ if ::HydraAttribute::HydraAttribute.names_by_entity_type(klass.model_name).include?(column)
153
163
  join_alias = ref_alias(column, 'inner') # alias for inner join
154
164
  join_alias = ref_alias(column, nil) unless relation.hydra_joins_aliases.include?(join_alias) # alias for left join
155
165
 
@@ -1,58 +1,21 @@
1
- require 'active_support/core_ext/object/with_options'
2
-
3
1
  module HydraAttribute
4
- class HydraAttribute < ActiveRecord::Base
5
- self.table_name = 'hydra_attributes'
6
-
7
- with_options as: [:default, :admin] do |klass|
8
- klass.attr_accessible :name, :backend_type, :default_value
9
- end
10
- attr_accessible :white_list, as: :admin
11
-
12
- has_and_belongs_to_many :hydra_sets, join_table: 'hydra_attribute_sets', class_name: 'HydraAttribute::HydraSet', conditions: proc { {hydra_sets: {entity_type: entity_type}} }
13
-
14
- with_options presence: true do |klass|
15
- klass.validates :entity_type, inclusion: { in: lambda { |attr| [(attr.entity_type.constantize.name rescue nil)] } }
16
- klass.validates :name, uniqueness: { scope: :entity_type }
17
- klass.validates :backend_type, inclusion: SUPPORTED_BACKEND_TYPES
18
- end
19
-
20
- before_destroy :delete_dependent_values
21
- after_commit :clear_entity_cache
22
- after_commit :update_mass_assignment_security
23
-
24
- # @COMPATIBILITY with 3.1.x association module is directly added to the class instead of including module
25
- def hydra_sets_with_clearing_cache=(value)
26
- self.hydra_sets_without_clearing_cache = value
27
- entity_type.constantize.clear_hydra_method_cache!
28
- value
29
- end
30
- alias_method_chain :hydra_sets=, :clearing_cache
2
+ class HydraAttribute
31
3
 
32
- def update_mass_assignment_security
33
- if destroyed? or !white_list?
34
- remove_from_white_list
35
- else
36
- add_to_white_list
37
- end
4
+ # This error is raised when created +HydraAttribute::HydraAttribute+ models don't have this ID
5
+ class UnknownHydraAttributeIdError < ArgumentError
38
6
  end
39
7
 
40
- private
41
- def delete_dependent_values
42
- value_class = AssociationBuilder.class_name(entity_type.constantize, backend_type).constantize
43
- value_class.delete_all(hydra_attribute_id: id)
44
- end
8
+ include ::HydraAttribute::Model
45
9
 
46
- def clear_entity_cache
47
- entity_type.constantize.reset_hydra_attribute_methods!
48
- end
10
+ validates :entity_type, presence: true
11
+ validates :name, presence: true, unique: { scope: :entity_type }
12
+ validates :backend_type, presence: true, inclusion: { in: ::HydraAttribute::SUPPORTED_BACKEND_TYPES }
49
13
 
50
- def add_to_white_list
51
- entity_type.constantize.accessible_attributes.add(name)
52
- end
14
+ define_cached_singleton_method :all_by_entity_type, cache_key: :entity_type, cache_value: :self, cache_key_cast: :to_s
15
+ define_cached_singleton_method :ids_by_entity_type, cache_key: :entity_type, cache_value: :id, cache_key_cast: :to_s
16
+ define_cached_singleton_method :names_by_entity_type, cache_key: :entity_type, cache_value: :name, cache_key_cast: :to_s
17
+ define_cached_singleton_method :backend_types_by_entity_type, cache_key: :entity_type, cache_value: :backend_type, cache_key_cast: :to_s
53
18
 
54
- def remove_from_white_list
55
- entity_type.constantize.accessible_attributes.delete(name)
56
- end
19
+ has_many :hydra_sets, through: :hydra_attribute_set, copy_attribute: :entity_type
57
20
  end
58
21
  end
@@ -0,0 +1,67 @@
1
+ module HydraAttribute
2
+ class HydraAttributeSet
3
+ include ::HydraAttribute::Model
4
+
5
+ define_cached_singleton_method :all_by_hydra_attribute_id, cache_key: :hydra_attribute_id, cache_value: :self, cache_key_cast: :to_i
6
+ define_cached_singleton_method :all_by_hydra_set_id, cache_key: :hydra_set_id, cache_value: :self, cache_key_cast: :to_i
7
+ define_cached_singleton_method :hydra_attributes_by_hydra_set_id, cache_key: :hydra_set_id, cache_value: :hydra_attribute, cache_key_cast: :to_i
8
+ define_cached_singleton_method :hydra_sets_by_hydra_attribute_id, cache_key: :hydra_attribute_id, cache_value: :hydra_set, cache_key_cast: :to_i
9
+ define_cached_singleton_method :hydra_attribute_ids_by_hydra_set_id, cache_key: :hydra_set_id, cache_value: :hydra_attribute_id, cache_key_cast: :to_i
10
+ define_cached_singleton_method :hydra_set_ids_by_hydra_attribute_id, cache_key: :hydra_attribute_id, cache_value: :hydra_set_id, cache_key_cast: :to_i
11
+
12
+ register_nested_cache :hydra_attribute_ids_by_hydra_set_id_as_hash
13
+
14
+ observe 'HydraAttribute::HydraAttribute', after_destroy: :hydra_attribute_destroyed
15
+ observe 'HydraAttribute::HydraSet', after_destroy: :hydra_set_destroyed
16
+
17
+ validates :hydra_set_id, presence: true
18
+ validates :hydra_attribute_id, presence: true, unique: { scope: :hydra_set_id }
19
+
20
+ class << self
21
+ def has_hydra_attribute_id_in_hydra_set_id?(hydra_attribute_id, hydra_set_id)
22
+ hydra_attribute_ids = get_from_nested_cache_or_load_all_models(:hydra_attribute_ids_by_hydra_set_id_as_hash, hydra_set_id.to_i)
23
+ hydra_attribute_ids and hydra_attribute_ids.has_key?(hydra_attribute_id.to_i)
24
+ end
25
+
26
+ # Remove hydra attribute from the cache
27
+ def hydra_attribute_destroyed(hydra_attribute) #:nodoc:
28
+ all_by_hydra_attribute_id(hydra_attribute.id).each(&:destroy)
29
+ end
30
+
31
+ # Remove hydra set from the cache
32
+ def hydra_set_destroyed(hydra_set) #:nodoc:
33
+ all_by_hydra_set_id(hydra_set.id).each(&:destroy)
34
+ end
35
+
36
+ private
37
+ def add_to_hydra_attribute_ids_by_hydra_set_id_as_hash_cache(hydra_attribute_set)
38
+ add_value_to_nested_hash_cache(:hydra_attribute_ids_by_hydra_set_id_as_hash, key: hydra_attribute_set.hydra_set_id, value: hydra_attribute_set.hydra_attribute_id)
39
+ end
40
+
41
+ def update_hydra_attribute_ids_by_hydra_set_id_as_hash_cache(hydra_attribute_set)
42
+ delete_value_from_nested_hash_cache(:hydra_attribute_ids_by_hydra_set_id_as_hash, key: hydra_attribute_set.hydra_set_id_was, value: hydra_attribute_set.hydra_attribute_id_was)
43
+ add_to_hydra_attribute_ids_by_hydra_set_id_as_hash_cache(hydra_attribute_set)
44
+ end
45
+
46
+ def delete_from_hydra_attribute_ids_by_hydra_set_id_as_hash_cache(hydra_attribute_set)
47
+ delete_value_from_nested_hash_cache(:hydra_attribute_ids_by_hydra_set_id_as_hash, key: hydra_attribute_set.hydra_set_id, value: hydra_attribute_set.hydra_attribute_id)
48
+ end
49
+ end
50
+
51
+ # Returns hydra attribute for this relation
52
+ #
53
+ # @return [HydraAttribute::HydraAttribute, NilClass]
54
+ def hydra_attribute
55
+ ::HydraAttribute::HydraAttribute.find(hydra_attribute_id) if hydra_attribute_id
56
+ end
57
+ alias_method :hydra_attribute_was, :hydra_attribute # used for cache sweepers
58
+
59
+ # Returns hydra set for this relation
60
+ #
61
+ # @return [HydraAttribute::HydraSet, NilClass]
62
+ def hydra_set
63
+ ::HydraAttribute::HydraSet.find(hydra_set_id) if hydra_set_id
64
+ end
65
+ alias_method :hydra_set_was, :hydra_set # used for cache sweepers
66
+ end
67
+ end
@@ -0,0 +1,110 @@
1
+ module HydraAttribute
2
+ module HydraEntity
3
+ class RelationDecorator
4
+ def initialize(entity_type, model_class)
5
+ @entity_type = entity_type
6
+ @model_class = model_class
7
+ end
8
+
9
+ def create(attributes = {})
10
+ @model_class.create(attributes.merge(entity_type: @entity_type))
11
+ end
12
+
13
+ def build(attributes = {})
14
+ @model_class.new(attributes.merge(entity_type: @entity_type))
15
+ end
16
+
17
+ private
18
+ def respond_to_missing?(symbol, include_private)
19
+ _models.respond_to?(symbol, include_private)
20
+ end
21
+
22
+ def method_missing(method, *args, &block)
23
+ _models.send(method, *args, &block)
24
+ end
25
+
26
+ def _models
27
+ @model_class.all_by_entity_type(@entity_type)
28
+ end
29
+ end
30
+
31
+ extend ActiveSupport::Concern
32
+
33
+ included do
34
+ after_save :save_hydra_attributes
35
+ after_destroy :destroy_hydra_attributes
36
+ end
37
+
38
+ module ClassMethods
39
+ # Returns collection of hydra attributes for current entity
40
+ #
41
+ # @return [HydraAttribute::HydraEntity::RelationDecorator]
42
+ def hydra_attributes
43
+ @hydra_attributes ||= RelationDecorator.new(model_name, ::HydraAttribute::HydraAttribute)
44
+ end
45
+
46
+ # Returns collection of hydra sets for current entity
47
+ #
48
+ # @return [HydraAttribute::HydraEntity::RelationDecorator]
49
+ def hydra_sets
50
+ @hydra_sets ||= RelationDecorator.new(model_name, ::HydraAttribute::HydraSet)
51
+ end
52
+ end
53
+
54
+ # Returns association between hydra attributes and their values
55
+ #
56
+ # @return [HydraAttribute::HydraEntityAttributeAssociation]
57
+ def hydra_attribute_association
58
+ @hydra_attribute_association ||= HydraEntityAttributeAssociation.new(self)
59
+ end
60
+
61
+ # Sets association object which connects hydra attributes with their values
62
+ #
63
+ # @param [HydraAttribute::HydraEntityAttributeAssociation]
64
+ def hydra_attribute_association=(association)
65
+ @hydra_attribute_association = association
66
+ end
67
+
68
+ # Return +HydraSet+ object if it exists
69
+ #
70
+ # @return [HydraAttribute::HydraSet]
71
+ def hydra_set
72
+ HydraSet.find(hydra_set_id) if hydra_set_id
73
+ end
74
+
75
+ # Returns hydra attribute names with theirs values
76
+ #
77
+ # @return [Hash]
78
+ def hydra_attributes
79
+ hydra_attribute_association.hydra_attributes
80
+ end
81
+
82
+ # Returns hydra attribute names with theirs values before type casting
83
+ #
84
+ # @return [Hash]
85
+ def hydra_attributes_before_type_cast
86
+ hydra_attribute_association.hydra_attributes_before_type_cast
87
+ end
88
+
89
+ def respond_to?(method, include_private = false)
90
+ hydra_attribute_association.has_proxy_method?(method) || super
91
+ end
92
+
93
+ private
94
+ def save_hydra_attributes
95
+ hydra_attribute_association.save
96
+ end
97
+
98
+ def destroy_hydra_attributes
99
+ hydra_attribute_association.destroy
100
+ end
101
+
102
+ def method_missing(method, *args, &block)
103
+ hydra_attribute_association.delegate(method, *args, &block)
104
+ rescue HydraSet::MissingAttributeInHydraSetError, HydraEntityAttributeAssociation::AttributeWasNotSelectedError
105
+ raise
106
+ rescue
107
+ super
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,155 @@
1
+ module HydraAttribute
2
+ class HydraEntityAttributeAssociation
3
+ class WrongProxyMethodError < ArgumentError
4
+ def initialize(method)
5
+ super("Unknown :#{method} method")
6
+ end
7
+ end
8
+
9
+ class AttributeWasNotSelectedError < ArgumentError
10
+ def initialize(attribute_id)
11
+ super("Attribute ID #{attribute_id} was not selected from DB")
12
+ end
13
+ end
14
+
15
+ include ::HydraAttribute::Model::Mediator
16
+ include ::HydraAttribute::Model::IdentityMap
17
+
18
+ observe 'HydraAttribute::HydraAttribute', after_create: :hydra_attribute_created, after_update: :hydra_attribute_updated, after_destroy: :hydra_attribute_destroyed
19
+
20
+ attr_reader :entity
21
+
22
+ class << self
23
+ # Generate hydra attribute methods
24
+ def generate_methods
25
+ ::HydraAttribute::HydraAttribute.all.each do |hydra_attribute|
26
+ add_to_cache(hydra_attribute.entity_type, hydra_attribute.name, hydra_attribute.id)
27
+ end
28
+ identity_map[:___methods_generated___] = true
29
+ end
30
+
31
+ # Checks if methods were generated
32
+ def methods_generated?
33
+ identity_map[:___methods_generated___] ||= false
34
+ end
35
+
36
+ # Callback
37
+ def hydra_attribute_created(hydra_attribute) # :nodoc:
38
+ return unless methods_generated?
39
+ add_to_cache(hydra_attribute.entity_type, hydra_attribute.name, hydra_attribute.id)
40
+ end
41
+
42
+ # Callback
43
+ def hydra_attribute_updated(hydra_attribute) # :nodoc:
44
+ delete_from_cache(hydra_attribute.entity_type_was, hydra_attribute.name_was)
45
+ add_to_cache(hydra_attribute.entity_type, hydra_attribute.name, hydra_attribute.id)
46
+ end
47
+
48
+ # Callback
49
+ def hydra_attribute_destroyed(hydra_attribute) # :nodoc:
50
+ delete_from_cache(hydra_attribute.entity_type, hydra_attribute.name)
51
+ end
52
+
53
+ private
54
+ def add_to_cache(entity_type, name, id)
55
+ identity_map[entity_type] ||= {names: {}, names_as_hash: {}, ids_as_hash: {}}
56
+ ::ActiveRecord::Base.attribute_method_matchers.each do |matcher|
57
+ current = matcher.method_name(name).to_sym
58
+ proxy = matcher.method_name(:value)
59
+
60
+ identity_map[entity_type][:names][name] ||= []
61
+ identity_map[entity_type][:names][name] << current
62
+ identity_map[entity_type][:names_as_hash][current] = proxy
63
+ identity_map[entity_type][:ids_as_hash][current] = id
64
+ end
65
+ end
66
+
67
+ def delete_from_cache(entity_type, name)
68
+ current_methods = identity_map[entity_type] && identity_map[entity_type][:names][name]
69
+ return unless current_methods
70
+ current_methods.each do |current_method|
71
+ identity_map[entity_type][:names_as_hash].delete(current_method)
72
+ identity_map[entity_type][:ids_as_hash].delete(current_method)
73
+ end
74
+ identity_map[entity_type][:names].delete(name)
75
+ end
76
+ end
77
+
78
+ def initialize(entity)
79
+ @entity = entity
80
+ end
81
+
82
+ def save
83
+ touch = false
84
+ accessible_hydra_values do |hydra_value|
85
+ touch = true if hydra_value.save
86
+ end
87
+ entity.touch if touch
88
+ end
89
+
90
+ def destroy
91
+ HydraValue.delete_entity_values(entity)
92
+ end
93
+
94
+ def hydra_values
95
+ @hydra_values ||= ::HydraAttribute::HydraAttribute.ids_by_entity_type(entity.class.model_name).inject({}) do |hydra_values, hydra_attribute_id|
96
+ hydra_values[hydra_attribute_id] = HydraValue.new(entity, hydra_attribute_id: hydra_attribute_id)
97
+ hydra_values
98
+ end
99
+ end
100
+
101
+ def hydra_attributes
102
+ to_enum(:accessible_hydra_values).each_with_object({}) do |hydra_value, hydra_attributes|
103
+ hydra_attributes[hydra_value.hydra_attribute.name] = hydra_value.value
104
+ end
105
+ end
106
+
107
+ def hydra_attributes_before_type_cast
108
+ to_enum(:accessible_hydra_values).each_with_object({}) do |hydra_value, hydra_attributes|
109
+ hydra_attributes[hydra_value.hydra_attribute.name] = hydra_value.value_before_type_cast
110
+ end
111
+ end
112
+
113
+ def lock_values
114
+ @hydra_values ||= {}
115
+ end
116
+
117
+ def hydra_value_options=(options = {})
118
+ hydra_values[options[:hydra_attribute_id]] = HydraValue.new(entity, options)
119
+ end
120
+
121
+ def has_proxy_method?(method)
122
+ self.class.generate_methods unless self.class.methods_generated?
123
+ identity_map = self.class.identity_map[entity.class.model_name]
124
+ return false unless identity_map
125
+ !!identity_map[:names_as_hash][method.to_sym]
126
+ end
127
+
128
+ def delegate(method, *args, &block)
129
+ self.class.generate_methods unless self.class.methods_generated?
130
+ identity_map = self.class.identity_map[entity.class.model_name] or raise WrongProxyMethodError, method
131
+ attribute_id = identity_map[:ids_as_hash][method] or raise WrongProxyMethodError, method
132
+ attribute_method = identity_map[:names_as_hash][method] or raise WrongProxyMethodError, method
133
+
134
+ if has_attribute_id?(attribute_id)
135
+ hydra_value = hydra_values[attribute_id] or raise AttributeWasNotSelectedError, attribute_id
136
+ hydra_value.send(attribute_method, *args, &block)
137
+ else
138
+ raise HydraSet::MissingAttributeInHydraSetError, "Attribute ID #{attribute_id} is missed in Set ID #{entity.hydra_set.id}"
139
+ end
140
+ end
141
+
142
+ private
143
+ def has_attribute_id?(hydra_attribute_id)
144
+ !entity.hydra_set || entity.hydra_set.has_hydra_attribute_id?(hydra_attribute_id)
145
+ end
146
+
147
+ def accessible_hydra_values
148
+ hydra_values.each do |hydra_attribute_id, hydra_value|
149
+ if has_attribute_id?(hydra_attribute_id)
150
+ yield hydra_value
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end