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
@@ -1,36 +1,34 @@
1
- require 'active_support/core_ext/object/with_options'
2
-
3
1
  module HydraAttribute
4
- class HydraSet < ActiveRecord::Base
5
- self.table_name = 'hydra_sets'
2
+ # TODO add #build_entity and #create_entity methods
3
+ class HydraSet
4
+
5
+ # This error is raised when called method for attribute which doesn't exist in current hydra set
6
+ #
7
+ # @example
8
+ # Product.hydra_attributes.create(name: 'price', backend_type: 'float')
9
+ # Product.hydra_attributes.create(name: 'title', backend_type: 'string')
10
+ #
11
+ # hydra_set = Product.hydra_sets.create(name: 'Default')
12
+ # hydra_set.hydra_attributes = [Product.hydra_attribute('title')]
13
+ #
14
+ # product = Product.new(hydra_set_id: hydra_set.id)
15
+ # product.title = 'Toy' # ok
16
+ # product.price = 2.50 # raise HydraAttribute::HydraSet::MissingAttributeInHydraSetError
17
+ class MissingAttributeInHydraSetError < NoMethodError
18
+ end
6
19
 
7
- attr_accessible :name
20
+ include ::HydraAttribute::Model
8
21
 
9
- has_and_belongs_to_many :hydra_attributes, join_table: 'hydra_attribute_sets', class_name: 'HydraAttribute::HydraAttribute', conditions: proc { {hydra_attributes: {entity_type: entity_type}} }
22
+ define_cached_singleton_method :all_by_entity_type, cache_key: :entity_type, cache_value: :self, cache_key_cast: :to_s
10
23
 
11
- with_options presence: true do |klass|
12
- klass.validates :entity_type, inclusion: { in: lambda { |attr| [(attr.entity_type.constantize.name rescue nil)] } }
13
- klass.validates :name, uniqueness: { scope: :entity_type }
14
- end
24
+ validates :entity_type, presence: true # TODO add match validation with its hydra_attributes.entity_type
25
+ validates :name, presence: true, unique: { scope: :entity_type }
15
26
 
16
- after_destroy :detach_entities
17
- after_commit :clear_entity_cache
27
+ has_many :hydra_attributes, through: :hydra_attribute_set, copy_attribute: :entity_type
18
28
 
19
- # @COMPATIBILITY with 3.1.x association module is directly added to the class instead of including module
20
- def hydra_attributes_with_clearing_cache=(value)
21
- self.hydra_attributes_without_clearing_cache = value
22
- clear_entity_cache
23
- value
29
+ def has_hydra_attribute_id?(hydra_attribute_id)
30
+ HydraAttributeSet.has_hydra_attribute_id_in_hydra_set_id?(hydra_attribute_id, id)
24
31
  end
25
- alias_method_chain :hydra_attributes=, :clearing_cache
26
-
27
- private
28
- def clear_entity_cache
29
- entity_type.constantize.clear_hydra_method_cache!
30
- end
31
32
 
32
- def detach_entities
33
- entity_type.constantize.where(hydra_set_id: id).update_all(hydra_set_id: nil)
34
- end
35
33
  end
36
34
  end
@@ -0,0 +1,210 @@
1
+ module HydraAttribute
2
+ class HydraValue
3
+
4
+ # This error is raised when +:hydra_attribute_id+ key isn't passed to initialize.
5
+ # This key is important for determination the type of attribute which this model represents.
6
+ class HydraAttributeIdIsMissedError < ArgumentError
7
+ def initialize(msg = 'Key :hydra_attribute_id is missed')
8
+ super
9
+ end
10
+ end
11
+
12
+ # This error is raised when <tt>HydraAttribute::HydraValue</tt> model is saved
13
+ # but <tt>entity model</tt> isn't persisted
14
+ class EntityModelIsNotPersistedError < RuntimeError
15
+ def initialize(msg = 'HydraValue model cannot be saved is entity model is not persisted')
16
+ super
17
+ end
18
+ end
19
+
20
+ include ::HydraAttribute::Model::IdentityMap
21
+ include ActiveModel::AttributeMethods
22
+ include ActiveModel::Dirty
23
+
24
+ attr_reader :entity, :value
25
+
26
+ define_attribute_method :value
27
+
28
+ # Initialize hydra value object
29
+ #
30
+ # @param [ActiveRecord::Base] entity link to entity model
31
+ # @param [Hash] attributes contain values of table row
32
+ # @option attributes [Symbol] :id
33
+ # @option attributes [Symbol] :hydra_attribute_id this field is required
34
+ # @option attributes [Symbol] :value
35
+ def initialize(entity, attributes = {})
36
+ raise HydraAttributeIdIsMissedError unless attributes.has_key?(:hydra_attribute_id)
37
+ @entity = entity
38
+ @attributes = attributes
39
+ if attributes.has_key?(:value)
40
+ @value = column.type_cast(attributes[:value])
41
+ else
42
+ @value = column.type_cast(column.default)
43
+ attributes[:value] = column.default
44
+ end
45
+ end
46
+
47
+ class << self
48
+ # Holds <tt>Arel::Table</tt> objects grouped by entity table and backend type of attribute
49
+ #
50
+ # @return [Hash]
51
+ def arel_tables
52
+ @arel_tables ||= Hash.new do |entity_tables, entity_table|
53
+ entity_tables[entity_table] = Hash.new do |backend_types, backend_type|
54
+ backend_types[backend_type] = Arel::Table.new("hydra_#{backend_type}_#{entity_table}", ::ActiveRecord::Base)
55
+ end
56
+ end
57
+ end
58
+
59
+ # Returns database adapter
60
+ #
61
+ # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
62
+ def connection
63
+ @connection ||= ::ActiveRecord::Base.connection
64
+ end
65
+
66
+ # Returns virtual value column
67
+ #
68
+ # @param [Fixnum] hydra_attribute_id
69
+ # @return [ActiveRecord::ConnectionAdapters::Column]
70
+ def column(hydra_attribute_id)
71
+ nested_identity_map(:column).cache(hydra_attribute_id.to_i) do
72
+ hydra_attribute = ::HydraAttribute::HydraAttribute.find(hydra_attribute_id)
73
+ ::ActiveRecord::ConnectionAdapters::Column.new(hydra_attribute.name, hydra_attribute.default_value, hydra_attribute.backend_type)
74
+ end
75
+ end
76
+
77
+ # Delete all values for current entity
78
+ #
79
+ # @param [HydraAttribute::HydraEntity] entity
80
+ # @return [NilClass]
81
+ def delete_entity_values(entity)
82
+ hydra_attributes = ::HydraAttribute::HydraAttribute.all_by_entity_type(entity.class.model_name)
83
+ hydra_attributes = hydra_attributes.group_by(&:backend_type)
84
+ hydra_attributes.each do |backend_type, attributes|
85
+ table = arel_tables[entity.class.table_name][backend_type]
86
+ where = table['hydra_attribute_id'].in(attributes.map(&:id)).and(table['entity_id'].eq(entity.id))
87
+ arel = table.from(table)
88
+ connection.delete(arel.where(where).compile_delete, 'SQL')
89
+ end
90
+ end
91
+ end
92
+
93
+ # Returns virtual value column
94
+ #
95
+ # @return [ActiveRecord::ConnectionAdapters::Column]
96
+ def column
97
+ self.class.column(@attributes[:hydra_attribute_id])
98
+ end
99
+
100
+ # Returns model ID
101
+ #
102
+ # @return [Fixnum]
103
+ def id
104
+ @attributes[:id]
105
+ end
106
+
107
+ # Sets new type casted attribute value
108
+ #
109
+ # @param [Object] new_value
110
+ # @return [NilClass]
111
+ def value=(new_value)
112
+ value_will_change! unless value == new_value
113
+ @attributes[:value] = new_value
114
+ @value = column.type_cast(new_value)
115
+ end
116
+
117
+ # Returns not type cased value
118
+ #
119
+ # @return [Object]
120
+ def value_before_type_cast
121
+ @attributes[:value]
122
+ end
123
+
124
+ # Checks if value not blank and not zero for number types
125
+ #
126
+ # @return [TrueClass, FalseClass]
127
+ def value?
128
+ return false unless value
129
+
130
+ if column.number?
131
+ !value.zero?
132
+ else
133
+ value.present?
134
+ end
135
+ end
136
+
137
+ # Returns hydra attribute model which contains meta information about attribute
138
+ #
139
+ # @return [HydraAttribute::HydraAttribute]
140
+ def hydra_attribute
141
+ @hydra_attribute ||= ::HydraAttribute::HydraAttribute.find(@attributes[:hydra_attribute_id])
142
+ end
143
+
144
+ # Checks if model is persisted
145
+ #
146
+ # @return [TrueClass, FalseClass]
147
+ def persisted?
148
+ @attributes[:id].present?
149
+ end
150
+
151
+ # Saves model
152
+ # Performs +insert+ or +update+ sql query
153
+ # Method doesn't perform sql query if model isn't modified
154
+ #
155
+ # @return [TrueClass, FalseClass]
156
+ def save
157
+ raise EntityModelIsNotPersistedError unless entity.persisted?
158
+
159
+ if persisted?
160
+ return false unless changed?
161
+ update
162
+ else
163
+ create
164
+ end
165
+
166
+ @previously_changed = changes
167
+ @changed_attributes.clear
168
+
169
+ true
170
+ end
171
+
172
+ private
173
+ # Creates arel insert manager
174
+ #
175
+ # @return [Arel::InsertManager]
176
+ def arel_insert
177
+ table = self.class.arel_tables[entity.class.table_name][hydra_attribute.backend_type]
178
+ fields = {}
179
+ fields[table[:entity_id]] = entity.id
180
+ fields[table[:hydra_attribute_id]] = hydra_attribute.id
181
+ fields[table[:value]] = value
182
+ fields[table[:created_at]] = Time.now
183
+ fields[table[:updated_at]] = Time.now
184
+ table.compile_insert(fields)
185
+ end
186
+
187
+ # Creates arel update manager
188
+ #
189
+ # @return [Arel::UpdateManager]
190
+ def arel_update
191
+ table = self.class.arel_tables[entity.class.table_name][hydra_attribute.backend_type]
192
+ arel = table.from(table)
193
+ arel.where(table[:id].eq(id)).compile_update(table[:value] => value, table[:updated_at] => Time.now)
194
+ end
195
+
196
+ # Performs sql insert query
197
+ #
198
+ # @return [Integer] primary key
199
+ def create
200
+ @attributes[:id] = self.class.connection.insert(arel_insert, 'SQL')
201
+ end
202
+
203
+ # Performs sql update query
204
+ #
205
+ # @return [NilClass]
206
+ def update
207
+ self.class.connection.update(arel_update, 'SQL')
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,18 @@
1
+ # TODO isn't thread safe
2
+ module HydraAttribute
3
+ class IdentityMap < Hash
4
+
5
+ # Get value if key exists otherwise set a value
6
+ # Block has a higher priority then value parameter
7
+ #
8
+ # @param [String, Symbol] key
9
+ # @param [Object] value
10
+ # @yield
11
+ # @return [Object]
12
+ def cache(key, value = nil)
13
+ fetch(key) do
14
+ self[key] = block_given? ? yield : value
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module HydraAttribute
2
+ module Middleware
3
+ class IdentityMap
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ @app.call(env)
10
+ ensure
11
+ HydraAttribute.identity_map.clear
12
+ end
13
+ end
14
+ end
15
+ end
@@ -53,43 +53,42 @@ module HydraAttribute
53
53
  t.integer :hydra_set_id, null: true
54
54
  yield t if block_given?
55
55
  end
56
- add_index name, :hydra_set_id, unique: false, name: "#{name}_hydra_set_id_index"
56
+ add_index name, :hydra_set_id, unique: false, name: "#{name}_hydra_set_id_idx"
57
57
  end
58
58
 
59
59
  def migrate_entity(name, options = {})
60
60
  change_table name, options do |t|
61
61
  t.integer :hydra_set_id, null: true
62
62
  end
63
- add_index name, :hydra_set_id, uniqie: false, name: "#{name}_hydra_set_id_index"
63
+ add_index name, :hydra_set_id, unique: false, name: "#{name}_hydra_set_id_idx"
64
64
  end
65
65
 
66
66
  def create_attribute
67
67
  create_table :hydra_attributes do |t|
68
- t.string :entity_type, limit: 32, null: false
69
- t.string :name, limit: 32, null: false
70
- t.string :backend_type, limit: 16, null: false
71
- t.string :default_value
72
- t.boolean :white_list, null: false, default: false
73
- t.datetime :created_at # @COMPATIBILITY with 3.1.x use "datetime" method instead of "timestamps" because Rails 3.1.x and 3.2.x have a different "null" value
74
- t.datetime :updated_at # @COMPATIBILITY with 3.1.x
68
+ t.string :entity_type, limit: 32, null: false
69
+ t.string :name, limit: 32, null: false
70
+ t.string :backend_type, limit: 16, null: false
71
+ t.string :default_value
72
+ t.boolean :white_list, null: false, default: false
73
+ t.timestamps
75
74
  end
76
- add_index :hydra_attributes, [:entity_type, :name], unique: true, name: 'hydra_attributes_index'
75
+ add_index :hydra_attributes, [:entity_type, :name], unique: true, name: 'hydra_attributes_idx'
77
76
  end
78
77
 
79
78
  def create_set
80
79
  create_table :hydra_sets do |t|
81
- t.string :entity_type, limit: 32, null: false
82
- t.string :name, limit: 32, null: false
83
- t.datetime :created_at # @COMPATIBILITY with 3.1.x
84
- t.datetime :updated_at # @COMPATIBILITY with 3.1.x
80
+ t.string :entity_type, limit: 32, null: false
81
+ t.string :name, limit: 32, null: false
82
+ t.timestamps
85
83
  end
86
- add_index :hydra_sets, [:entity_type, :name], unique: true, name: 'hydra_sets_index'
84
+ add_index :hydra_sets, [:entity_type, :name], unique: true, name: 'hydra_sets_idx'
87
85
 
88
- create_table :hydra_attribute_sets, id: false do |t|
86
+ create_table :hydra_attribute_sets do |t|
89
87
  t.integer :hydra_attribute_id, null: false
90
88
  t.integer :hydra_set_id, null: false
89
+ t.timestamps
91
90
  end
92
- add_index :hydra_attribute_sets, [:hydra_attribute_id, :hydra_set_id], unique: true, name: 'hydra_attribute_sets_index'
91
+ add_index :hydra_attribute_sets, [:hydra_attribute_id, :hydra_set_id], unique: true, name: 'hydra_attribute_sets_idx'
93
92
  end
94
93
 
95
94
  def create_values(name)
@@ -98,11 +97,15 @@ module HydraAttribute
98
97
  create_table table_name do |t|
99
98
  t.integer :entity_id, null: false
100
99
  t.integer :hydra_attribute_id, null: false
101
- t.send type, :value
102
- t.datetime :created_at # @COMPATIBILITY with 3.1.x
103
- t.datetime :updated_at # @COMPATIBILITY with 3.1.x
100
+ case type
101
+ when 'decimal'
102
+ t.send type, :value, precision: 10, scale: 4, null: true
103
+ else
104
+ t.send type, :value, null: true
105
+ end
106
+ t.timestamps
104
107
  end
105
- add_index table_name, [:entity_id, :hydra_attribute_id], unique: true, name: "#{table_name}_index"
108
+ add_index table_name, [:entity_id, :hydra_attribute_id], unique: true, name: "#{table_name}_idx"
106
109
  end
107
110
  end
108
111
 
@@ -0,0 +1,47 @@
1
+ require 'active_record/errors'
2
+ require 'hydra_attribute/model/validations'
3
+ require 'hydra_attribute/model/persistence'
4
+ require 'hydra_attribute/model/mediator'
5
+ require 'hydra_attribute/model/notifiable'
6
+ require 'hydra_attribute/model/identity_map'
7
+ require 'hydra_attribute/model/cacheable'
8
+ require 'hydra_attribute/model/dirty'
9
+ require 'hydra_attribute/model/has_many_through'
10
+
11
+ module HydraAttribute
12
+ module Model
13
+ extend ActiveSupport::Concern
14
+
15
+ include Validations
16
+ include Persistence
17
+ include Mediator
18
+ include Notifiable
19
+ include IdentityMap
20
+ include Cacheable
21
+ include Dirty
22
+ include HasManyThrough
23
+
24
+ module ClassMethods
25
+ # Find first model
26
+ #
27
+ # @return [HydraAttribute::Model]
28
+ def first
29
+ all.first
30
+ end
31
+
32
+ # Find last model
33
+ #
34
+ # @return [HydraAttribute::Model]
35
+ def last
36
+ all.last
37
+ end
38
+
39
+ # Returns number of models
40
+ #
41
+ # @return [Fixnum]
42
+ def count
43
+ all.count
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,207 @@
1
+ module HydraAttribute
2
+ module Model
3
+ module Cacheable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ register_nested_cache :model
8
+ end
9
+
10
+ module ClassMethods
11
+ # Finds all records and store them into the cache
12
+ #
13
+ # @return [Array<HydraAttribute::Cacheable>]
14
+ def all
15
+ return identity_map[:all] if identity_map.has_key?(:all)
16
+
17
+ ids = nested_identity_map(:model).keys
18
+ ids.present? ? where_not(id: ids) : where
19
+
20
+ identity_map[:all] = []
21
+ nested_identity_map(:model).each do |_, model|
22
+ add_to_cache(model)
23
+ end
24
+ identity_map[:all]
25
+ end
26
+
27
+ # Find record by ID and store it into the cache
28
+ #
29
+ # @return [HydraAttribute::Cacheable]
30
+ def find(id)
31
+ model = get_from_nested_cache_or_load_all_models(:model, id.to_i)
32
+ raise RecordNotFound, "Couldn't find #{name} with id=#{id}" unless model
33
+ model
34
+ end
35
+
36
+ # Defines singleton method
37
+ #
38
+ # @example
39
+ # ClassName.define_cached_singleton_method :method_name, cache_key: :method_key, cache_value: :method_value, cache_key_cast: :to_i
40
+ # ClassName.method_method
41
+ #
42
+ # @param [Symbol] method_name
43
+ # @param [Hash] options
44
+ # @return [NilClass]
45
+ def define_cached_singleton_method(method_name, options = {})
46
+ register_nested_cache(method_name)
47
+
48
+ cache_key = options[:cache_key] ? ".#{options[:cache_key]}" : ''
49
+ cache_value = options[:cache_value] != :self ? ".#{options[:cache_value]}" : ''
50
+ cache_key_was = options[:cache_key] ? "#{cache_key}_was" : ''
51
+ cache_value_was = options[:cache_value] != :self ? "#{cache_value}_was" : ''
52
+ type_cast_key = options[:cache_key_cast] ? ".#{options[:cache_key_cast]}" : ''
53
+
54
+ instance_eval <<-OES, __FILE__, __LINE__ + 1
55
+ def #{method_name}(param)
56
+ get_from_nested_cache_or_load_all_models(:#{method_name}, param#{type_cast_key}) || []
57
+ end
58
+
59
+ private
60
+ def add_to_#{method_name}_cache(model)
61
+ add_value_to_nested_cache(:#{method_name}, key: model#{cache_key}, value: model#{cache_value})
62
+ end
63
+
64
+ def update_#{method_name}_cache(model)
65
+ delete_value_from_nested_cache(:#{method_name}, key: model#{cache_key_was}, value: model#{cache_value_was})
66
+ add_to_#{method_name}_cache(model)
67
+ end
68
+
69
+ def delete_from_#{method_name}_cache(model)
70
+ delete_value_from_nested_cache(:#{method_name}, key: model#{cache_key}, value: model#{cache_value})
71
+ end
72
+ OES
73
+ end
74
+
75
+ # Gets data from cache or load all models and repeats the operation
76
+ #
77
+ # @param [Symbol] nested_cache_key
78
+ # @param [Object] identifier
79
+ # @return [Object]
80
+ def get_from_nested_cache_or_load_all_models(nested_cache_key, identifier)
81
+ return nested_identity_map(nested_cache_key)[identifier] if nested_identity_map(nested_cache_key).has_key?(identifier)
82
+ all # preload all models
83
+ nested_identity_map(nested_cache_key)[identifier]
84
+ end
85
+
86
+ # Add model to all cache objects
87
+ # This method should not be used outside the model
88
+ #
89
+ # @param [HydraAttribute::Model::Cacheable]
90
+ # @return [NilClass]
91
+ def add_to_cache(model)
92
+ notify_cache_callbacks('add_to', model)
93
+ end
94
+
95
+ # Update model in all registered caches
96
+ # This method should not be used outside the model
97
+ #
98
+ # @param [HydraAttribute::Model::Cacheable]
99
+ # @return [NilClass]
100
+ def update_cache(model)
101
+ notify_cache_callbacks('update', model)
102
+ end
103
+
104
+ # Delete model from all cache objects
105
+ # This method should not be used outside the model
106
+ #
107
+ # @param [HydraAttribute::Model::Cacheable]
108
+ # @return [NilClass]
109
+ def delete_from_cache(model)
110
+ notify_cache_callbacks('delete_from', model)
111
+ end
112
+
113
+ private
114
+ # helper method
115
+ def notify_cache_callbacks(method_prefix, model)
116
+ ([:all] + nested_cache_keys).each do |nested_cache_key|
117
+ method = "#{method_prefix}_#{nested_cache_key}_cache"
118
+ send(method, model) if respond_to?(method, true)
119
+ end
120
+ end
121
+
122
+ # helper method
123
+ def add_value_to_nested_cache(cache_key, options = {})
124
+ return unless identity_map.has_key?(:all)
125
+ nested_identity_map(cache_key)[options[:key]] ||= []
126
+ nested_identity_map(cache_key)[options[:key]] << options[:value]
127
+ end
128
+
129
+ # helper method
130
+ def delete_value_from_nested_cache(cache_key, options = {})
131
+ return unless nested_identity_map(cache_key)[options[:key]]
132
+ nested_identity_map(cache_key)[options[:key]].delete(options[:value])
133
+ end
134
+
135
+ # helper method
136
+ def add_value_to_nested_hash_cache(cache_key, options = {})
137
+ return unless identity_map.has_key?(:all)
138
+ nested_identity_map(cache_key)[options[:key]] ||= {}
139
+ nested_identity_map(cache_key)[options[:key]][options[:value]] = nil
140
+ end
141
+
142
+ # helper method
143
+ def delete_value_from_nested_hash_cache(cache_key, options = {})
144
+ return unless nested_identity_map(cache_key)[options[:key]]
145
+ nested_identity_map(cache_key)[options[:key]].delete(options[:value])
146
+ end
147
+
148
+ # cache callback
149
+ def add_to_all_cache(model)
150
+ return unless identity_map[:all]
151
+ identity_map[:all].push(model)
152
+ end
153
+
154
+ # cache callback
155
+ def delete_from_all_cache(model)
156
+ return unless identity_map[:all]
157
+ identity_map[:all].delete(model)
158
+ end
159
+
160
+ # cache callback
161
+ def add_to_model_cache(model)
162
+ nested_identity_map(:model)[model.id] = model
163
+ end
164
+
165
+ # cache callback
166
+ def delete_from_model_cache(model)
167
+ nested_identity_map(:model).delete(model.id)
168
+ end
169
+ end
170
+
171
+ # Initialize a model
172
+ # Save it into the cache if it is persisted
173
+ def initialize(attributes = {})
174
+ super(attributes)
175
+ self.class.add_to_cache(self) if persisted?
176
+ end
177
+
178
+ private
179
+ # Create new model and store it into the cache
180
+ #
181
+ # @return [Fixnum]
182
+ def create
183
+ id = super
184
+ self.class.add_to_cache(self)
185
+ id
186
+ end
187
+
188
+ # Update the model and its cache
189
+ #
190
+ # @return [TrueClass, FalseClass]
191
+ def update
192
+ result = super
193
+ self.class.update_cache(self)
194
+ result
195
+ end
196
+
197
+ # Delete model and remove it from the cache
198
+ #
199
+ # @return [TrueClass]
200
+ def delete
201
+ result = super
202
+ self.class.delete_from_cache(self)
203
+ result
204
+ end
205
+ end
206
+ end
207
+ end