hydra_attribute 0.2.0 → 0.3.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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