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.
- data/.gitignore +2 -1
- data/.travis.yml +6 -5
- data/CHANGELOG.md +6 -0
- data/Gemfile +1 -1
- data/README.md +3 -3
- data/Rakefile +2 -7
- data/gemfiles/activerecord-3.2.gemfile +5 -0
- data/hydra_attribute.gemspec +6 -7
- data/lib/hydra_attribute.rb +17 -18
- data/lib/hydra_attribute/active_record.rb +34 -13
- data/lib/hydra_attribute/active_record/association_preloader.rb +47 -28
- data/lib/hydra_attribute/active_record/attribute_methods.rb +29 -140
- data/lib/hydra_attribute/active_record/mass_assignment_security.rb +39 -0
- data/lib/hydra_attribute/active_record/migration.rb +4 -4
- data/lib/hydra_attribute/active_record/relation.rb +6 -7
- data/lib/hydra_attribute/active_record/relation/query_methods.rb +28 -18
- data/lib/hydra_attribute/hydra_attribute.rb +12 -49
- data/lib/hydra_attribute/hydra_attribute_set.rb +67 -0
- data/lib/hydra_attribute/hydra_entity.rb +110 -0
- data/lib/hydra_attribute/hydra_entity_attribute_association.rb +155 -0
- data/lib/hydra_attribute/hydra_set.rb +24 -26
- data/lib/hydra_attribute/hydra_value.rb +210 -0
- data/lib/hydra_attribute/identity_map.rb +18 -0
- data/lib/hydra_attribute/middleware/identity_map.rb +15 -0
- data/lib/hydra_attribute/migrator.rb +24 -21
- data/lib/hydra_attribute/model.rb +47 -0
- data/lib/hydra_attribute/model/cacheable.rb +207 -0
- data/lib/hydra_attribute/model/dirty.rb +39 -0
- data/lib/hydra_attribute/model/has_many_through.rb +168 -0
- data/lib/hydra_attribute/model/identity_map.rb +59 -0
- data/lib/hydra_attribute/model/mediator.rb +89 -0
- data/lib/hydra_attribute/model/notifiable.rb +23 -0
- data/lib/hydra_attribute/model/persistence.rb +424 -0
- data/lib/hydra_attribute/model/validations.rb +40 -0
- data/lib/hydra_attribute/version.rb +1 -1
- data/spec/environments/mysql.rb +23 -0
- data/spec/environments/postgresql.rb +23 -0
- data/spec/environments/sqlite.rb +12 -0
- data/spec/fixtures/category.rb +8 -0
- data/spec/fixtures/product.rb +8 -0
- data/spec/fixtures/product_black_list.rb +13 -0
- data/spec/fixtures/product_white_list.rb +13 -0
- data/spec/hydra_attribute/active_record/attribute_methods_spec.rb +23 -28
- data/spec/hydra_attribute/active_record/mass_assignment_security_spec.rb +41 -0
- data/spec/hydra_attribute/active_record_spec.rb +577 -0
- data/spec/hydra_attribute/hydra_attribute_set_spec.rb +651 -0
- data/spec/hydra_attribute/hydra_attribute_spec.rb +208 -10
- data/spec/hydra_attribute/hydra_entity_attribute_association_spec.rb +216 -0
- data/spec/hydra_attribute/hydra_entity_spec.rb +71 -0
- data/spec/hydra_attribute/hydra_set_spec.rb +51 -10
- data/spec/hydra_attribute/hydra_value_spec.rb +286 -0
- data/spec/hydra_attribute/identity_map_spec.rb +47 -0
- data/spec/hydra_attribute/migrator_spec.rb +411 -0
- data/spec/hydra_attribute/model/cacheable_spec.rb +106 -0
- data/spec/hydra_attribute/model/has_many_through_spec.rb +132 -0
- data/spec/hydra_attribute/model/identity_map_spec.rb +39 -0
- data/spec/hydra_attribute/model/mediator_spec.rb +62 -0
- data/spec/hydra_attribute/model/persistence_spec.rb +550 -0
- data/spec/hydra_attribute/model_spec.rb +39 -0
- data/spec/hydra_attribute_spec.rb +36 -0
- data/spec/spec_helper.rb +10 -42
- metadata +76 -100
- data/Appraisals +0 -7
- data/cucumber.yml +0 -1
- data/features/entity/create.feature +0 -145
- data/features/entity/destroy.feature +0 -111
- data/features/entity/new.feature +0 -121
- data/features/entity/update.feature +0 -147
- data/features/hydra_attribute/create.feature +0 -30
- data/features/hydra_attribute/destroy.feature +0 -26
- data/features/hydra_attribute/update.feature +0 -36
- data/features/hydra_set/destroy.feature +0 -31
- data/features/migrations/create_and_drop.feature +0 -165
- data/features/migrations/migrate_and_rollback.feature +0 -211
- data/features/relation/query_methods/group.feature +0 -42
- data/features/relation/query_methods/order.feature +0 -67
- data/features/relation/query_methods/reorder.feature +0 -29
- data/features/relation/query_methods/reverse_order.feature +0 -29
- data/features/relation/query_methods/select.feature +0 -50
- data/features/relation/query_methods/where.feature +0 -115
- data/features/step_definitions/connections.rb +0 -65
- data/features/step_definitions/model_steps.rb +0 -136
- data/features/step_definitions/query_methods.rb +0 -48
- data/features/step_definitions/record_steps.rb +0 -93
- data/features/support/env.rb +0 -38
- data/features/support/world.rb +0 -61
- data/lib/hydra_attribute/active_record/association.rb +0 -113
- data/lib/hydra_attribute/active_record/reflection.rb +0 -16
- data/lib/hydra_attribute/association_builder.rb +0 -69
- data/lib/hydra_attribute/builder.rb +0 -37
- data/lib/hydra_attribute/entity_callbacks.rb +0 -26
- data/lib/hydra_attribute/hydra_attribute_methods.rb +0 -226
- data/lib/hydra_attribute/hydra_methods.rb +0 -528
- data/lib/hydra_attribute/hydra_set_methods.rb +0 -95
- data/lib/hydra_attribute/hydra_value_methods.rb +0 -21
- data/lib/hydra_attribute/memoizable.rb +0 -37
- data/spec/hydra_attribute/active_record/relation/query_methods_spec.rb +0 -31
- data/spec/hydra_attribute/hydra_attribute_methods_spec.rb +0 -458
- data/spec/hydra_attribute/hydra_methods_spec.rb +0 -456
- data/spec/hydra_attribute/hydra_set_methods_spec.rb +0 -203
- 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
|
-
|
5
|
-
|
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
|
-
|
20
|
+
include ::HydraAttribute::Model
|
8
21
|
|
9
|
-
|
22
|
+
define_cached_singleton_method :all_by_entity_type, cache_key: :entity_type, cache_value: :self, cache_key_cast: :to_s
|
10
23
|
|
11
|
-
|
12
|
-
|
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
|
-
|
17
|
-
after_commit :clear_entity_cache
|
27
|
+
has_many :hydra_attributes, through: :hydra_attribute_set, copy_attribute: :entity_type
|
18
28
|
|
19
|
-
|
20
|
-
|
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
|
@@ -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}
|
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,
|
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
|
69
|
-
t.string
|
70
|
-
t.string
|
71
|
-
t.string
|
72
|
-
t.boolean
|
73
|
-
t.
|
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: '
|
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
|
82
|
-
t.string
|
83
|
-
t.
|
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: '
|
84
|
+
add_index :hydra_sets, [:entity_type, :name], unique: true, name: 'hydra_sets_idx'
|
87
85
|
|
88
|
-
create_table :hydra_attribute_sets
|
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: '
|
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
|
-
|
102
|
-
|
103
|
-
|
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}
|
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
|