hydra_attribute 0.2.0 → 0.3.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. data/README.md +74 -68
  2. data/cucumber.yml +1 -0
  3. data/features/attributes/create.feature +22 -0
  4. data/features/attributes/destroy.feature +18 -0
  5. data/features/attributes/update.feature +19 -0
  6. data/features/create.feature +47 -0
  7. data/features/define.feature +33 -0
  8. data/features/query_methods/group.feature +13 -14
  9. data/features/query_methods/order.feature +63 -52
  10. data/features/query_methods/select.feature +36 -37
  11. data/features/query_methods/where.feature +36 -38
  12. data/features/step_definitions/model_steps.rb +23 -19
  13. data/features/step_definitions/query_methods.rb +6 -2
  14. data/features/step_definitions/record_steps.rb +28 -10
  15. data/features/support/env.rb +12 -6
  16. data/features/support/schema.rb +62 -35
  17. data/features/support/world.rb +14 -5
  18. data/features/update.feature +114 -0
  19. data/gemfiles/3.1.gemfile.lock +10 -10
  20. data/gemfiles/3.2.gemfile.lock +10 -10
  21. data/lib/hydra_attribute/active_record/association.rb +77 -0
  22. data/lib/hydra_attribute/active_record/association_preloader.rb +82 -0
  23. data/lib/hydra_attribute/active_record/attribute_methods.rb +145 -37
  24. data/lib/hydra_attribute/active_record/migration.rb +21 -0
  25. data/lib/hydra_attribute/active_record/reflection.rb +16 -0
  26. data/lib/hydra_attribute/active_record/relation/query_methods.rb +73 -71
  27. data/lib/hydra_attribute/active_record/relation.rb +1 -24
  28. data/lib/hydra_attribute/active_record.rb +16 -2
  29. data/lib/hydra_attribute/association_builder.rb +44 -20
  30. data/lib/hydra_attribute/builder.rb +15 -13
  31. data/lib/hydra_attribute/configuration.rb +9 -30
  32. data/lib/hydra_attribute/entity_callbacks.rb +46 -0
  33. data/lib/hydra_attribute/hydra_attribute.rb +27 -0
  34. data/lib/hydra_attribute/migrator.rb +106 -0
  35. data/lib/hydra_attribute/railtie.rb +2 -0
  36. data/lib/hydra_attribute/version.rb +2 -2
  37. data/lib/hydra_attribute.rb +8 -6
  38. data/lib/rails/generators/hydra_attribute/install/templates/hydra_attribute.txt +4 -0
  39. data/spec/spec_helper.rb +1 -2
  40. metadata +42 -60
  41. data/features/attribute_methods.feature +0 -146
  42. data/features/define_attributes.feature +0 -56
  43. data/features/load_associations.feature +0 -40
  44. data/features/step_definitions/class_steps.rb +0 -32
  45. data/features/typecast_attributes.feature +0 -24
  46. data/lib/generators/hydra_attribute/install/templates/hydra_attribute.txt +0 -11
  47. data/lib/hydra_attribute/active_record/attribute_methods/before_type_cast.rb +0 -16
  48. data/lib/hydra_attribute/active_record/attribute_methods/read.rb +0 -13
  49. data/lib/hydra_attribute/attribute_builder.rb +0 -57
  50. data/lib/hydra_attribute/attribute_proxy.rb +0 -16
  51. data/lib/hydra_attribute/migration.rb +0 -27
  52. data/spec/hydra_attribute/active_record/relation/query_methods_spec.rb +0 -334
  53. data/spec/hydra_attribute/active_record/relation_spec.rb +0 -67
  54. data/spec/hydra_attribute/active_record/scoping_spec.rb +0 -23
  55. data/spec/hydra_attribute/active_record_spec.rb +0 -18
  56. data/spec/hydra_attribute/association_builder_spec.rb +0 -95
  57. data/spec/hydra_attribute/attribute_builder_spec.rb +0 -70
  58. data/spec/hydra_attribute/attribute_helpers_spec.rb +0 -70
  59. data/spec/hydra_attribute/builder_spec.rb +0 -39
  60. data/spec/hydra_attribute/configuration_spec.rb +0 -65
  61. data/spec/hydra_attribute_spec.rb +0 -20
  62. /data/lib/{generators → rails/generators}/hydra_attribute/install/USAGE +0 -0
  63. /data/lib/{generators → rails/generators}/hydra_attribute/install/install_generator.rb +0 -0
@@ -0,0 +1,77 @@
1
+ module HydraAttribute
2
+ module ActiveRecord
3
+ class Association < ::ActiveRecord::Associations::HasManyAssociation
4
+
5
+ def find_model(attributes = {})
6
+ load_target unless loaded?
7
+ target.detect do |model|
8
+ model.hydra_attribute_id == attributes[:hydra_attribute_id]
9
+ end
10
+ end
11
+
12
+ def find_model_or_build(attributes = {})
13
+ find_model(attributes) || build(attributes)
14
+ end
15
+
16
+ def build(attributes = {}, options = {}, &block)
17
+ return if locked_for_build? and white_list_for_build.exclude?(attributes[:hydra_attribute_id])
18
+ super
19
+ end
20
+
21
+ def all_models
22
+ unless @full_loaded
23
+ (all_attribute_ids - target.map(&:hydra_attribute_id)).each do |hydra_attribute_id|
24
+ build(hydra_attribute_id: hydra_attribute_id)
25
+ end
26
+ @full_loaded = true
27
+ end
28
+ target
29
+ end
30
+
31
+ def locked_for_build
32
+ @locked_for_build ||= false
33
+ end
34
+ alias_method :locked_for_build?, :locked_for_build
35
+
36
+ def lock_for_build!(white_list_for_build = [])
37
+ @locked_for_build = true
38
+ @white_list_for_build = Array(white_list_for_build)
39
+ loaded!
40
+ end
41
+
42
+ def white_list_for_build
43
+ @white_list_for_build ||= []
44
+ end
45
+
46
+ def all_attribute_ids
47
+ hydra_attribute_ids = owner.class.grouped_hydra_attribute_ids(reflection.backend_type)
48
+ hydra_attribute_ids &= white_list_for_build if locked_for_build?
49
+ hydra_attribute_ids
50
+ end
51
+
52
+ private
53
+
54
+ # Optimized method
55
+ # Remove unnecessary callbacks
56
+ def add_to_target(record)
57
+ @target << record
58
+ record
59
+ end
60
+
61
+ # Optimized method
62
+ # Attributes are written via low level function without additional checks
63
+ def build_record(attributes, _)
64
+ reflection.klass.new do |record|
65
+ unless attributes.has_key?(:value)
66
+ attributes[:value] = owner.class.hydra_attribute(attributes[:hydra_attribute_id]).default_value
67
+ end
68
+
69
+ record.send :write_attribute, 'id', attributes[:id]
70
+ record.send :write_attribute, 'entity_id', owner.id
71
+ record.send :write_attribute, 'hydra_attribute_id', attributes[:hydra_attribute_id]
72
+ record.send :write_attribute, 'value', attributes[:value]
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,82 @@
1
+ module HydraAttribute
2
+ module ActiveRecord
3
+ class AssociationPreloader
4
+ attr_reader :relation, :records, :hashed_records
5
+
6
+ def initialize(relation, records = [])
7
+ @relation = relation
8
+ @records = records
9
+ @hashed_records = records.each_with_object({}) { |record, hash| hash[record.id] = record }
10
+ end
11
+
12
+ def self.run(relation, records)
13
+ new(relation, records).run
14
+ end
15
+
16
+ def run
17
+ grouped_values do |association, values, white_list_attribute_ids|
18
+ association.target.concat(values)
19
+ association.lock_for_build!(white_list_attribute_ids)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def grouped_values
26
+ grouped_attribute_ids do |type, attribute_ids|
27
+ association_name = AssociationBuilder.association_name(type)
28
+ load_values(type, attribute_ids) do |record, values|
29
+ yield(record.association(association_name), values, attribute_ids)
30
+ end
31
+ end
32
+ end
33
+
34
+ def load_values(type, attribute_ids)
35
+ return records.each { |record| yield(record, []) } if attribute_ids.empty?
36
+
37
+ attribute_ids.each_slice(in_clause_length || attribute_ids.length) do |sliced_attribute_ids|
38
+ records.each_slice(in_clause_length || records.length) do |sliced_records|
39
+ values = AssociationBuilder.class_name(klass, type).constantize.select([:id, :entity_id, :hydra_attribute_id, :value]).where(entity_id: sliced_records, hydra_attribute_id: sliced_attribute_ids)
40
+ group_records_with_values(sliced_records, values).each do |record_id, grouped_values|
41
+ yield(hashed_records[record_id], grouped_values)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def group_records_with_values(records, values)
48
+ hash = records.each_with_object({}) { |record, hash| hash[record.id] = [] }
49
+ values.each { |value| hash[value.entity_id] << value }
50
+ hash
51
+ end
52
+
53
+ def grouped_attribute_ids
54
+ map = klass.hydra_attribute_backend_types.each_with_object({}) { |type, object| object[type] = [] }
55
+ mapping = hydra_attributes.each_with_object(map) do |hydra_attribute, mapping|
56
+ mapping[hydra_attribute.backend_type] << hydra_attribute.id
57
+ end
58
+ mapping.each { |type, ids| yield(type, ids) }
59
+ end
60
+
61
+ def hydra_attributes
62
+ if attribute_limit?
63
+ relation.hydra_select_values.map { |name| klass.hydra_attribute(name) }
64
+ else
65
+ klass.hydra_attributes
66
+ end
67
+ end
68
+
69
+ def attribute_limit?
70
+ relation.select_values.any? or relation.hydra_select_values.any?
71
+ end
72
+
73
+ def klass
74
+ relation.klass
75
+ end
76
+
77
+ def in_clause_length
78
+ klass.connection.in_clause_length
79
+ end
80
+ end
81
+ end
82
+ end
@@ -3,69 +3,177 @@ module HydraAttribute
3
3
  module AttributeMethods
4
4
  extend ActiveSupport::Concern
5
5
 
6
- included do
7
- @hydra_attributes = {}
6
+ NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
7
+ CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
8
8
 
9
- include Read
10
- include BeforeTypeCast
9
+ included do
10
+ @hydra_attribute_methods_mutex = Mutex.new
11
11
  end
12
12
 
13
13
  module ClassMethods
14
- def inherited(base)
15
- base.instance_variable_set(:@hydra_attributes, hydra_attributes)
16
- super
14
+ def hydra_attribute_methods_generated?
15
+ @hydra_attribute_methods_generated ||= false
16
+ end
17
+
18
+ def generated_hydra_attribute_methods
19
+ @generated_hydra_attribute_methods ||= begin
20
+ mod = Module.new
21
+ include mod
22
+ mod
23
+ end
17
24
  end
18
25
 
19
26
  def hydra_attributes
20
- @hydra_attributes.dup
27
+ @hydra_attributes ||= HydraAttribute.where(entity_type: base_class.model_name)
28
+ end
29
+
30
+ def grouped_hydra_attributes
31
+ @grouped_hydra_attributes ||= hydra_attributes.group_by(&:backend_type)
32
+ end
33
+
34
+ %w(id name backend_type).each do |prefix|
35
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
36
+ def hydra_attribute_#{prefix}s
37
+ @hydra_attribute_#{prefix}s ||= hydra_attributes.map(&:#{prefix}).uniq
38
+ end
39
+
40
+ def grouped_hydra_attribute_#{prefix}s(type)
41
+ instance = "@grouped_hydra_attribute_\#{type}_#{prefix}s"
42
+ instance_variable_get(instance) || instance_variable_set(instance, grouped_hydra_attributes[type].map(&:#{prefix}).uniq)
43
+ end
44
+ EOS
21
45
  end
22
46
 
23
- def hydra_attribute_names
24
- hydra_attributes.keys
47
+ def hydra_attribute(identifier)
48
+ @hydra_attribute_cache ||= {}
49
+ @hydra_attribute_cache[identifier] ||= hydra_attributes.find do |hydra_attribute|
50
+ hydra_attribute.id == identifier || hydra_attribute.name == identifier
51
+ end
25
52
  end
26
53
 
27
- def hydra_attribute_types
28
- hydra_attributes.values.uniq
54
+ def define_hydra_attribute_methods
55
+ @hydra_attribute_methods_mutex.synchronize do
56
+ return if hydra_attribute_methods_generated?
57
+ hydra_attributes.each { |hydra_attribute| define_hydra_attribute_method(hydra_attribute) }
58
+ @hydra_attribute_methods_generated = true
59
+ end
60
+ end
61
+
62
+ def define_hydra_attribute_method(hydra_attribute)
63
+ attribute_method_matchers.each do |matcher|
64
+ current = matcher.method_name(hydra_attribute.name)
65
+ target = matcher.method_name(:value)
66
+
67
+ if current =~ NAME_COMPILABLE_REGEXP
68
+ defn = "def #{current}(*args)"
69
+ else
70
+ defn = "define_method(:'#{current}') do |*args|"
71
+ end
72
+
73
+ if target =~ CALL_COMPILABLE_REGEXP
74
+ send = "#{target}(*args)"
75
+ else
76
+ send = "send(:'#{target}', *args)"
77
+ end
78
+
79
+ generated_hydra_attribute_methods.module_eval <<-EOS, __FILE__, __LINE__ + 1
80
+ #{defn}
81
+ if value_model = hydra_value_model(#{hydra_attribute.id})
82
+ value_model.#{send}
83
+ else
84
+ missing_attribute('#{hydra_attribute.name}', caller)
85
+ end
86
+ end
87
+ EOS
88
+ end
89
+ end
90
+
91
+ def reset_hydra_attribute_methods
92
+ generated_hydra_attribute_methods.module_eval do
93
+ instance_methods.each { |m| undef_method(m) }
94
+ end
95
+
96
+ @hydra_attributes = nil
97
+ @hydra_attribute_methods_generated = false
98
+ @hydra_attribute_cache = {}
99
+ @grouped_hydra_attributes = nil
100
+
101
+ %w(id name backend_type).each do |prefix|
102
+ instance_variable_set("@hydra_attribute_#{prefix}s", nil)
103
+
104
+ SUPPORT_TYPES.each do |type|
105
+ instance_variable_set("@grouped_hydra_attribute_#{type}_#{prefix}s", nil)
106
+ end
107
+ end
108
+ end
109
+
110
+ def undefine_attribute_methods
111
+ reset_hydra_attribute_methods
112
+ super
29
113
  end
30
114
  end
31
115
 
32
- def initialize(attributes = nil, options = {})
33
- @hydra_attribute_names = self.class.hydra_attribute_names
116
+ def respond_to?(name, include_private = false)
117
+ self.class.define_hydra_attribute_methods unless self.class.hydra_attribute_methods_generated?
34
118
  super
35
119
  end
36
120
 
37
- def init_with(coder)
38
- @hydra_attribute_names = self.class.hydra_attribute_names
39
- super
121
+ %w(attributes attributes_before_type_cast).each do |method|
122
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
123
+ def #{method}
124
+ self.class.hydra_attribute_backend_types.each_with_object(super) do |type, attributes|
125
+ hydra_value_association(type).all_models.each do |model|
126
+ hydra_attribute = self.class.hydra_attribute(model.hydra_attribute_id)
127
+ attributes[hydra_attribute.name] = model.#{method}['value']
128
+ end
129
+ end
130
+ end
131
+ EOS
40
132
  end
41
133
 
42
- def initialize_dup(other)
43
- if other.instance_variable_defined?(:@hydra_attribute_names)
44
- @hydra_attribute_names = other.instance_variable_get(:@hydra_attribute_names)
45
- else
46
- @hydra_attribute_names = self.class.hydra_attribute_names
47
- end
48
- super
134
+ %w(read_attribute read_attribute_before_type_cast).each do |method|
135
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
136
+ def #{method}(attr_name)
137
+ identifier = attr_name.to_s
138
+ if self.class.hydra_attribute_names.include?(identifier)
139
+ if value_model = hydra_value_model(identifier)
140
+ value_model.#{method}('value')
141
+ else
142
+ missing_attribute(identifier, caller)
143
+ end
144
+ else
145
+ super
146
+ end
147
+ end
148
+ EOS
49
149
  end
50
150
 
51
- def hydra_attribute_model(name, type)
52
- collection = send(HydraAttribute.config.association(type))
53
- collection.detect { |model| model.name == name } || collection.build(name: name)
151
+ private
152
+
153
+ def hydra_value_model(identifier)
154
+ @hydra_value_model_cache ||= {}
155
+ @hydra_value_model_cache[identifier] ||= begin
156
+ hydra_attribute = self.class.hydra_attribute(identifier)
157
+ association = hydra_value_association(hydra_attribute.backend_type)
158
+ association.find_model_or_build(hydra_attribute_id: hydra_attribute.id)
159
+ end
54
160
  end
55
161
 
56
- def attributes
57
- super.merge(hydra_attributes)
162
+ def hydra_value_association(backend_type)
163
+ association(::HydraAttribute::AssociationBuilder.association_name(backend_type))
58
164
  end
59
165
 
60
- %w(attributes attributes_before_type_cast).each do |attr_method|
61
- module_eval <<-EOS, __FILE__, __LINE__ + 1
62
- def hydra_#{attr_method}
63
- @hydra_attribute_names.each_with_object({}) do |name, attributes|
64
- type = self.class.hydra_attributes[name]
65
- attributes[name] = hydra_attribute_model(name, type).#{attr_method}['value']
66
- end
166
+ def method_missing(method, *args, &block)
167
+ if self.class.hydra_attribute_methods_generated?
168
+ super
169
+ else
170
+ self.class.define_hydra_attribute_methods
171
+ if respond_to_without_attributes?(method)
172
+ send(method, *args, &block)
173
+ else
174
+ super
67
175
  end
68
- EOS
176
+ end
69
177
  end
70
178
  end
71
179
  end
@@ -0,0 +1,21 @@
1
+ module HydraAttribute
2
+ module ActiveRecord
3
+ module Migration
4
+ def create_hydra_entity(name, options = {}, &block)
5
+ Migrator.create(self, name, options, &block)
6
+ end
7
+
8
+ def drop_hydra_entity(name)
9
+ Migrator.drop(self, name)
10
+ end
11
+
12
+ def migrate_to_hydra_entity(name)
13
+ Migrator.migrate(self, name)
14
+ end
15
+
16
+ def rollback_from_hydra_entity(name)
17
+ Migrator.rollback(self, name)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ module HydraAttribute
2
+ module ActiveRecord
3
+ class Reflection < ::ActiveRecord::Reflection::AssociationReflection
4
+
5
+ # Return custom association class
6
+ # which is optimized to load hydra attributes
7
+ def association_class
8
+ Association
9
+ end
10
+
11
+ def backend_type
12
+ @backend_type ||= SUPPORT_TYPES.find { |type| type == klass.model_name.demodulize.underscore.split('_')[1] }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -25,9 +25,9 @@ module HydraAttribute
25
25
  opts.inject(self) do |relation, (name, value)|
26
26
  if klass.hydra_attribute_names.include?(name.to_s)
27
27
  relation, name = relation.clone, name.to_s
28
- relation.hydra_joins_aliases << hydra_ref_alias(name, value)
29
- relation.joins_values += build_hydra_joins_values(name, value)
30
- relation.where_values += build_where(build_hydra_where_options(name, value))
28
+ relation.hydra_joins_aliases << hydra_helper.ref_alias(name, value)
29
+ relation.joins_values += hydra_helper.build_joins(name, value)
30
+ relation.where_values += build_where(hydra_helper.where_options(name, value))
31
31
  relation
32
32
  else
33
33
  relation.where_without_hydra_attribute(name => value)
@@ -39,21 +39,21 @@ module HydraAttribute
39
39
  end
40
40
 
41
41
  def build_arel
42
- @group_values = build_hydra_values_for_arel(@group_values.uniq.reject(&:blank?))
43
- @order_values = build_hydra_values_for_arel(@order_values.uniq.reject(&:blank?))
42
+ @group_values = hydra_helper.quote_columns(@group_values.uniq.reject(&:blank?))
43
+ @order_values = hydra_helper.quote_columns(@order_values.uniq.reject(&:blank?))
44
44
 
45
45
  if instance_variable_defined?(:@reorder_value) and instance_variable_get(:@reorder_value).present? # for compatibility with 3.1.x
46
- @reorder_value = build_hydra_values_for_arel(@reorder_value.uniq.reject(&:blank?))
46
+ @reorder_value = hydra_helper.quote_columns(@reorder_value.uniq.reject(&:blank?))
47
47
  end
48
48
 
49
+ # detect hydra attributes from select list
49
50
  @hydra_select_values, @select_values = @select_values.partition { |value| klass.hydra_attribute_names.include?(value.to_s) }
50
51
  @hydra_select_values.map!(&:to_s)
51
- @select_values.map!{ |value| hydra_attr_helper.prepend_table_name(value) }
52
+ @select_values.map!{ |value| hydra_helper.prepend_table_name(value) }
52
53
 
53
54
  # force add ID for preloading hydra attributes
54
- if @hydra_select_values.any? && @select_values.none? { |v| hydra_attr_helper.attr_eq_column?(v, klass.primary_key) }
55
- @select_values << hydra_attr_helper.prepend_table_name(klass.primary_key)
56
- @id_for_hydra_attributes = true
55
+ if @hydra_select_values.any? && @select_values.none? { |v| hydra_helper.attr_eq_column?(v, klass.primary_key) }
56
+ @select_values << hydra_helper.prepend_table_name(klass.primary_key)
57
57
  end
58
58
 
59
59
  super
@@ -77,83 +77,85 @@ module HydraAttribute
77
77
  ].include?(attr)
78
78
  end
79
79
 
80
- def prepend_table_name(column)
81
- return column unless column.is_a?(Symbol) or column.is_a?(String)
82
-
83
- copy = column.to_s.strip
84
- if copy =~ /^\w+$/
85
- klass.quoted_table_name + '.' + connection.quote_column_name(copy)
80
+ def prepend_table_name(column, table = klass.table_name)
81
+ case column
82
+ when String, Symbol
83
+ copy = column.to_s.strip
84
+ if copy =~ /^\w+$/
85
+ connection.quote_table_name(table) + '.' + connection.quote_column_name(copy)
86
+ else
87
+ column
88
+ end
86
89
  else
87
90
  column
88
91
  end
89
92
  end
90
- end
91
93
 
92
- private
94
+ def build_joins(name, value)
95
+ conn = klass.connection
96
+ quoted_alias = conn.quote_table_name(ref_alias(name, value))
97
+
98
+ [[
99
+ "#{join_type(value)} JOIN",
100
+ conn.quote_table_name(ref_table(name)),
101
+ 'AS',
102
+ quoted_alias,
103
+ 'ON',
104
+ "#{klass.quoted_table_name}.#{klass.quoted_primary_key}",
105
+ '=',
106
+ "#{quoted_alias}.#{conn.quote_column_name(:entity_id)}",
107
+ 'AND',
108
+ "#{quoted_alias}.#{conn.quote_column_name(:hydra_attribute_id)}",
109
+ '=',
110
+ hydra_attribute_id(name)
111
+ ].join(' ')]
112
+ end
93
113
 
94
- def hydra_attr_helper
95
- @hydra_attr_helper ||= Helper.new(self)
96
- end
114
+ def where_options(name, value)
115
+ {ref_alias(name, value) => {value: value}}
116
+ end
97
117
 
98
- def build_hydra_values_for_arel(collection)
99
- collection.map do |attribute|
100
- attribute = attribute.respond_to?(:to_sql) ? attribute.to_sql : attribute.to_s
101
- if klass.hydra_attribute_names.include?(attribute)
102
- join_alias = hydra_ref_alias(attribute, 'inner') # alias for inner join
103
- join_alias = hydra_ref_alias(attribute, nil) unless hydra_joins_aliases.include?(join_alias) # alias for left join
118
+ def ref_class(name)
119
+ type = klass.hydra_attribute(name).backend_type
120
+ AssociationBuilder.class_name(klass, type).constantize
121
+ end
104
122
 
105
- @joins_values += build_hydra_joins_values(attribute, nil) unless hydra_joins_aliases.include?(join_alias)
106
- klass.connection.quote_table_name(join_alias) + '.' + klass.connection.quote_column_name('value')
107
- else
108
- hydra_attr_helper.prepend_table_name(attribute)
109
- end
123
+ def ref_table(name)
124
+ ref_class(name).table_name
110
125
  end
111
- end
112
126
 
113
- def build_hydra_joins_values(name, value)
114
- ref_alias = hydra_ref_alias(name, value)
115
- conn = klass.connection
116
- quoted_ref_alias = conn.quote_table_name(ref_alias)
117
-
118
- [[
119
- "#{hydra_join_type(value)} JOIN",
120
- conn.quote_table_name(hydra_ref_table(name)),
121
- 'AS',
122
- quoted_ref_alias,
123
- 'ON',
124
- "#{klass.quoted_table_name}.#{klass.quoted_primary_key}",
125
- '=',
126
- "#{quoted_ref_alias}.#{conn.quote_column_name(:entity_id)}",
127
- 'AND',
128
- "#{quoted_ref_alias}.#{conn.quote_column_name(:entity_type)}",
129
- '=',
130
- conn.quote(klass.base_class.name),
131
- 'AND',
132
- "#{quoted_ref_alias}.#{conn.quote_column_name(:name)}",
133
- '=',
134
- conn.quote(name)
135
- ].join(' ')]
136
- end
127
+ def ref_alias(name, value)
128
+ ref_table(name) + '_' + join_type(value).downcase + '_' + name
129
+ end
137
130
 
138
- def build_hydra_where_options(name, value)
139
- {hydra_ref_alias(name, value) => {value: value}}
140
- end
131
+ def join_type(value)
132
+ value.nil? ? 'LEFT' : 'INNER'
133
+ end
141
134
 
142
- def hydra_ref_class(name)
143
- type = klass.hydra_attributes[name]
144
- HydraAttribute.config.associated_model_name(type).constantize
145
- end
135
+ def hydra_attribute_id(name)
136
+ klass.hydra_attribute(name).id
137
+ end
146
138
 
147
- def hydra_ref_table(name)
148
- hydra_ref_class(name).table_name
149
- end
139
+ def quote_columns(columns)
140
+ columns.map do |column|
141
+ column = column.respond_to?(:to_sql) ? column.to_sql : column.to_s
142
+ if klass.hydra_attribute_names.include?(column)
143
+ join_alias = ref_alias(column, 'inner') # alias for inner join
144
+ join_alias = ref_alias(column, nil) unless relation.hydra_joins_aliases.include?(join_alias) # alias for left join
150
145
 
151
- def hydra_ref_alias(name, value)
152
- hydra_ref_table(name) + '_' + hydra_join_type(value).downcase + '_' + name
146
+ relation.joins_values += build_joins(column, nil) unless relation.hydra_joins_aliases.include?(join_alias)
147
+ prepend_table_name('value', join_alias)
148
+ else
149
+ prepend_table_name(column)
150
+ end
151
+ end
152
+ end
153
153
  end
154
154
 
155
- def hydra_join_type(value)
156
- value.nil? ? 'LEFT' : 'INNER'
155
+ private
156
+
157
+ def hydra_helper
158
+ @hydra_helper ||= Helper.new(self)
157
159
  end
158
160
  end
159
161
  end
@@ -16,30 +16,7 @@ module HydraAttribute
16
16
  records = __old_exec_queries__
17
17
  return records if records.empty?
18
18
 
19
- limit_values = select_values.any? || hydra_select_values.any?
20
-
21
- if records.many?
22
- if limit_values
23
- hydra_attribute_types = hydra_select_values.map { |value| records.first.class.hydra_attributes[value] }.uniq
24
- else
25
- hydra_attribute_types = records.first.class.hydra_attribute_types
26
- end
27
-
28
- hydra_attribute_types.each do |type|
29
- association = HydraAttribute.config.association(type)
30
- unless records.first.association(association).loaded?
31
- ::ActiveRecord::Associations::Preloader.new(records, association).run
32
- end
33
- end
34
- end
35
-
36
- if limit_values
37
- records.each do |record| # force limit getter methods for hydra attributes
38
- record.instance_variable_set(:@hydra_attribute_names, hydra_select_values)
39
- record.instance_variable_get(:@attributes).delete('id') if @id_for_hydra_attributes
40
- end
41
- end
42
-
19
+ AssociationPreloader.run(self, records)
43
20
  records
44
21
  end
45
22
 
@@ -1,7 +1,21 @@
1
1
  module HydraAttribute
2
+
3
+ # ActiveRecord::Base extends this module.
2
4
  module ActiveRecord
3
- def define_hydra_attributes(&block)
4
- Builder.new(self).instance_eval(&block)
5
+
6
+ # Add EAV behavior to this model.
7
+ # Generate attribute and value associations.
8
+ def use_hydra_attributes
9
+ Builder.build(self)
10
+ end
11
+
12
+ # Create reflection for hydra association
13
+ def create_reflection(macro, name, options, active_record)
14
+ if name.to_s.start_with?('hydra_')
15
+ reflections[name] = Reflection.new(macro, name, options, active_record)
16
+ else
17
+ super
18
+ end
5
19
  end
6
20
  end
7
21
  end