hydra_attribute 0.4.0.rc1 → 0.4.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
- # hydra_attribute
2
- [![Build Status](https://secure.travis-ci.org/kostyantyn/hydra_attribute.png)](http://travis-ci.org/kostyantyn/hydra_attribute)
1
+ # hydra_attribute [![Build Status](https://secure.travis-ci.org/kostyantyn/hydra_attribute.png)](http://travis-ci.org/kostyantyn/hydra_attribute)
2
+
3
+ [Wiki](https://github.com/kostyantyn/hydra_attribute/wiki) | [RDoc](http://rdoc.info/github/kostyantyn/hydra_attribute)
3
4
 
4
5
  hydra_attribute is an implementation of
5
- [EAV pattern](http://en.wikipedia.org/wiki/Entity–attribute–value_model) for ActiveRecord models.
6
+ [EAV (Entity-Attribute-Value) pattern](http://en.wikipedia.org/wiki/Entity–attribute–value_model) for ActiveRecord models. It allows to create or remove attributes in runtime. Also each record may have different sets of attributes, for example: Product with ID 1 can have different set of attributes than Product with ID 2.
6
7
 
7
8
  ## Requirements
8
9
  * ruby >= 1.9.2
@@ -36,7 +37,8 @@ class CreateHydraAttributeTables < ActiveRecord::Migration
36
37
  end
37
38
  ```
38
39
 
39
- ##### or if we have already the entity table
40
+ **or if we have already the entity table**
41
+
40
42
  ```ruby
41
43
  class CreateHydraAttributeTables < ActiveRecord::Migration
42
44
  def up
@@ -51,7 +53,7 @@ end
51
53
 
52
54
  ## Usage
53
55
 
54
- ##### Create model
56
+ ### Create model
55
57
  ```shell
56
58
  rails generate model Product type:string name:string --migration=false
57
59
  rake db:migrate
@@ -71,7 +73,7 @@ class Product < ActiveRecord::Base
71
73
  end
72
74
  ```
73
75
 
74
- ##### Create some hydra attributes from `rails console`
76
+ ### Create hydra attributes
75
77
  ```ruby
76
78
  Product.hydra_attributes.create(name: 'color', backend_type: 'string', default_value: 'green')
77
79
  Product.hydra_attributes.create(name: 'title', backend_type: 'string')
@@ -88,57 +90,85 @@ Creating method accepts the following options:
88
90
  Product.hydra_attributes.create({name: 'title', backend_type: 'string', white_list: true}, as: :admin)
89
91
  ```
90
92
 
91
- ##### Create several objects
92
-
93
+ ### Create records
93
94
  ```ruby
94
- Product.create.attributes
95
- # {"id"=>1, created_at"=>..., "updated_at"=>..., "color"=>"green", "title"=>nil, "total"=>1}
96
- Product.create(color: 'red', title: 'toy').attributes
97
- # {"id"=>1, "created_at"=>..., "updated_at"=>..., "color"=>"red", "title"=>"toy", "total"=>1}
98
- Product.create(title: 'book', total: 2).attributes
99
- # {"id"=>1, "created_at"=>..., "updated_at"=>..., "color"=>"green", "title"=>"book", "total"=>2}
95
+ Product.create
96
+ #<Product id: 1, hydra_set_id: nil, created_at: ..., updated_at: ..., color: "green", title: nil, total: 1>
97
+ Product.create(color: 'red', title: 'toy')
98
+ #<Product id: 2, hydra_set_id: nil, created_at: ..., updated_at: ..., color: "red", title: "toy", total: 1>
99
+ Product.create(title: 'book', total: 2)
100
+ #<Product id: 3, hydra_set_id: nil, created_at: ..., updated_at: ..., color: "green", title: "book", total: 2>
100
101
  ```
101
102
 
102
- ##### Add the new attribute in runtime
103
+ ### Add new hydra attribute in runtime
103
104
  ```ruby
104
105
  Product.hydra_attributes.create(name: 'price', backend_type: 'float', default_value: 0.0)
105
- Product.create(title: 'car', price: 2.50).attributes
106
- # {"id"=>4, "created_at"=>..., "updated_at"=>..., "color"=>"green", "title"=>"car", "price"=>2.5, "total"=>1}
106
+ Product.create(title: 'car', price: 2.50)
107
+ #<Product id: 4, hydra_set_id: nil, created_at: ..., updated_at: ..., color: "green", title: "car", total: 2, price: 2.5>
108
+ ```
109
+
110
+ ### Create hydra set
111
+ **Hydra set** allows set unique attribute list for each entity.
112
+
113
+ ```ruby
114
+ hydra_set = Product.hydra_sets.create(name: 'Default')
115
+ hydra_set.hydra_attributes = Product.hydra_attributes.where(name: %w(color title price))
116
+
117
+ Product.create(color: 'black', title: 'ipod', price: 49.95, total: 5) do |product|
118
+ product.hydra_set_id = hydra_set.id
119
+ end
120
+ #<Product id: 5, hydra_set_id: 1, created_at: ..., updated_at: ..., color: "black", title: "ipod", price: 49.95>
107
121
  ```
122
+ **Notice:** the `total` attribute was skipped because it doesn't exist in hydra set.
108
123
 
109
- ##### Obtain data
124
+ ### Obtain data
110
125
  ```ruby
111
- Product.where(color: 'red').map(&:attributes)
112
- # [{"id"=>2, "created_at"=>..., "updated_at"=>..., "color"=>"red", "title"=>"toy", "price"=>0.0, "total"=>1}]
113
- Product.where(color: 'green', price: nil).map(&:attributes)
114
- # [{"id"=>1, "created_at"=>..., "updated_at"=>..., "color"=>"green", "title"=>nil, "price"=>0.0, "total"=>1},
115
- # {"id"=>3, "created_at"=>..., "updated_at"=>..., "color"=>"green", "title"=>"book", "price"=>0.0, "total"=>2}]
126
+ Product.where(color: 'red')
127
+ # [#<Product id: 2, hydra_set_id: nil, created_at: ..., updated_at: ..., color: "red", title: "toy", price: 0.0, total: 1>]
128
+ Product.where(color: 'green', price: nil)
129
+ # [
130
+ #<Product id: 1, hydra_set_id: nil, created_at: ..., updated_at: ..., color: "green", title: nil, price: 0.0, total: 1>,
131
+ #<Product id: 3, hydra_set_id: nil, created_at: ..., updated_at: ..., color: "green", title: "book", price: 0.0, total: 2>
132
+ # ]
116
133
  ```
117
134
  **Notice**: the attribute `price` was added in runtime and records that were created before have not this attribute
118
135
  so they matched this condition `where(price: nil)`
119
136
 
120
- ##### Order data
137
+ ### Order data
121
138
  ```ruby
122
- Product.order(:color).first.attributes
123
- # {"id"=>1, "created_at"=>..., "updated_at"=>..., "color"=>"green", "title"=>nil, "price"=>0.0, "total"=>1}
124
- Product.order(:color).reverse_order.first.attributes
125
- # {"id"=>2, "created_at"=>..., "updated_at"=>..., "color"=>"red", "title"=>"toy", "price"=>0.0, "total"=>1}
139
+ Product.order(:color, :title).first
140
+ #<Product id: 5, hydra_set_id: 1, created_at: ..., updated_at: ..., color: "black", title: "ipod", price: 49.95>
141
+ Product.order(:color, :title).reverse_order.first
142
+ #<Product id: 2, hydra_set_id: nil, created_at: ..., updated_at: ..., color: "red", title: "toy", price: 0.0, total: 1>
126
143
  ```
127
144
 
128
- ##### Select concrete attributes
145
+ ### Select concrete attributes
129
146
  ```ruby
130
- Product.select([:color, :title]).map(&:attributes)
131
- # [{"id"=>1, "color"=>"green", "title"=>nil}, {"id"=>2, "color"=>"red", "title"=>"toy"},
132
- # {"id"=>3, "color"=>"green", "title"=>"book"}, {"id"=>4, "color"=>"green", "title"=>"car"}]
147
+ Product.select([:color, :title])
148
+ # [
149
+ #<Product id: 1, hydra_set_id: nil, color: "green", title: nil>,
150
+ #<Product id: 2, hydra_set_id: nil, color: "red", title: "toy">,
151
+ #<Product id: 3, hydra_set_id: nil, color: "green", title: "book">,
152
+ #<Product id: 4, hydra_set_id: nil, color: "green", title: "car">,
153
+ #<Product id: 5, hydra_set_id: 1, color: "black", title: "ipod">
154
+ # ]
133
155
  ```
134
- **Notice**: `id` attribute will be added if we want to select hydra attribute
156
+ **Notice:** `id` and `hydra_set_id` attributes are forced added because they are important for correct work.
135
157
 
136
- ##### Group by attribute
158
+ ### Group by attribute
137
159
  ```ruby
138
160
  Product.group(:color).count
139
- # {"green"=>3, "red"=>1}
161
+ # {"black"=>1, "green"=>3, "red"=>1}
140
162
  ```
141
163
 
164
+ ## Wiki Docs
165
+ * [Create migration](https://github.com/kostyantyn/hydra_attribute/wiki/Create-migration)
166
+ * [Create attributes in runtime](https://github.com/kostyantyn/hydra_attribute/wiki/Create-attributes-in-runtime)
167
+ * [Create sets of attributes](https://github.com/kostyantyn/hydra_attribute/wiki/Create-sets-of-attributes)
168
+ * [Query methods](https://github.com/kostyantyn/hydra_attribute/wiki/Query-methods)
169
+ * [Database schema](https://github.com/kostyantyn/hydra_attribute/wiki/Database-schema)
170
+ * [Helper methods](https://github.com/kostyantyn/hydra_attribute/wiki/Helper-methods)
171
+
142
172
  ## Notice
143
173
 
144
174
  The each new minor version doesn't guarantee back compatibility with previous one
@@ -53,6 +53,23 @@ Feature: create models with hydra attributes
53
53
  | info | b |
54
54
  | started | [date:2012-05-05] |
55
55
 
56
+ Scenario: create hydra attribute in runtime
57
+ # create product to cache all hydra attributes
58
+ Given create "Product" model
59
+ And create hydra attributes for "Product" with role "admin" as "hashes":
60
+ | name | backend_type | default_value | white_list |
61
+ | quantity | integer | [nil:] | [bool:t] |
62
+ And create "Product" model with attributes as "rows_hash":
63
+ | quantity | 5 |
64
+ Then last created "Product" should have the following attributes:
65
+ | code | [nil:] |
66
+ | info | [str:] |
67
+ | price | [float:0] |
68
+ | total | [int:0] |
69
+ | active | [bool:f] |
70
+ | started | [date:2012-01-01] |
71
+ | quantity | [int:5] |
72
+
56
73
  Scenario: pass only hydra_set_id
57
74
  Given create hydra sets for "Product" as "hashes":
58
75
  | name |
@@ -67,4 +67,49 @@ Feature: hydra attribute where conditions
67
67
  And records should have the following attributes:
68
68
  | field | value |
69
69
  | code | 2 |
70
- | code | 3 |
70
+ | code | 3 |
71
+
72
+ Scenario: select entity if it has attribute in attribute set
73
+ Given create hydra set "Default" for "Product"
74
+ And set hydra attributes "[array:code,title]" to hydra set "Default" for entity "Product"
75
+ And create "Product" models with attributes as "hashes":
76
+ | hydra_set_id | code | title |
77
+ | | abc1 | book |
78
+ | [eval:Product.hydra_set('Default').id] | abc2 | book |
79
+ When filter "Product" by:
80
+ | field | value |
81
+ | title | book |
82
+ Then total records should be "2"
83
+ And records should have the following attributes:
84
+ | field | value |
85
+ | code | abc1 |
86
+ | code | abc2 |
87
+
88
+ Scenario: when filter attribute by nil value then entities without this attribute in attribute set should not be selected
89
+ Given create hydra set "Default" for "Product"
90
+ And create "Product" model
91
+ And create "Product" model with attributes as "rows_hash":
92
+ | hydra_set_id | [eval:Product.hydra_set('Default').id] |
93
+ When filter "Product" by:
94
+ | field | value |
95
+ | code | [nil:] |
96
+ Then total records should be "1"
97
+ And records should have the following attributes:
98
+ | field | value |
99
+ | hydra_set_id | [nil:] |
100
+
101
+ Scenario: when filter attribute by value then entities which don't have this attribute in attribute set any more should not be selected
102
+ Given create hydra set "Default" for "Product"
103
+ And add hydra attribute "code" to hydra set "Default" for entity "Product"
104
+ And create "Product" models with attributes as "hashes":
105
+ | hydra_set_id | code |
106
+ | | abc |
107
+ | [eval:Product.hydra_set('Default').id] | abc |
108
+ And set hydra attributes "title" to hydra set "Default" for entity "Product"
109
+ When filter "Product" by:
110
+ | field | value |
111
+ | code | abc |
112
+ Then total records should be "1"
113
+ And records should have the following attributes:
114
+ | field | value |
115
+ | hydra_set_id | [nil:] |
@@ -66,6 +66,19 @@ Given /^add "([^"]+)" hydra attributes to hydra set:$/ do |klass, table|
66
66
  end
67
67
  end
68
68
 
69
+ Given /^add hydra attribute "([^"]+)" to hydra set "([^"]+)" for entity "([^"]+)"$/ do |hydra_attribute, hydra_set, entity_class|
70
+ entity = entity_class.constantize
71
+ entity.hydra_set(type_cast_value(hydra_set)).hydra_attributes << entity.hydra_attribute(type_cast_value(hydra_attribute))
72
+ end
73
+
74
+ Given /^set hydra attributes? "([^"]+)" to hydra set "([^"]+)" for entity "([^"]+)"$/ do |hydra_attributes, hydra_set, entity_class|
75
+ entity = entity_class.constantize
76
+ hydra_set = entity.hydra_set(type_cast_value(hydra_set))
77
+
78
+ hydra_attributes = Array(type_cast_value(hydra_attributes)).map { |attr| entity.hydra_attribute(attr) }
79
+ hydra_set.hydra_attributes = hydra_attributes
80
+ end
81
+
69
82
  Given /^(load and )?(save|create|update(?: all| attributes)?|destroy(?: all)?|delete(?: all)?)(?: for)? "([^"]+)" models? with attributes as "([^"]+)":$/ do |load, action, klass, format, table|
70
83
  action = action.gsub(' ', '_')
71
84
  klass = klass.constantize
@@ -1,28 +1,28 @@
1
1
  PATH
2
2
  remote: /Users/kostyantyn/Sites/github/gems/hydra_attribute
3
3
  specs:
4
- hydra_attribute (0.4.0.beta)
4
+ hydra_attribute (0.4.0.rc1)
5
5
  activerecord (>= 3.1.0)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- activemodel (3.1.7)
11
- activesupport (= 3.1.7)
10
+ activemodel (3.1.8)
11
+ activesupport (= 3.1.8)
12
12
  builder (~> 3.0.0)
13
13
  i18n (~> 0.6)
14
- activerecord (3.1.7)
15
- activemodel (= 3.1.7)
16
- activesupport (= 3.1.7)
14
+ activerecord (3.1.8)
15
+ activemodel (= 3.1.8)
16
+ activesupport (= 3.1.8)
17
17
  arel (~> 2.2.3)
18
18
  tzinfo (~> 0.3.29)
19
- activesupport (3.1.7)
19
+ activesupport (3.1.8)
20
20
  multi_json (>= 1.0, < 1.3)
21
21
  appraisal (0.4.1)
22
22
  bundler
23
23
  rake
24
24
  arel (2.2.3)
25
- builder (3.0.0)
25
+ builder (3.0.3)
26
26
  cucumber (1.2.1)
27
27
  builder (>= 2.1.2)
28
28
  diff-lcs (>= 1.1.3)
@@ -30,10 +30,10 @@ GEM
30
30
  json (>= 1.4.6)
31
31
  database_cleaner (0.8.0)
32
32
  diff-lcs (1.1.3)
33
- gherkin (2.11.1)
33
+ gherkin (2.11.2)
34
34
  json (>= 1.4.6)
35
- i18n (0.6.0)
36
- json (1.7.4)
35
+ i18n (0.6.1)
36
+ json (1.7.5)
37
37
  multi_json (1.2.0)
38
38
  rake (0.9.2.2)
39
39
  rspec (2.11.0)
@@ -41,9 +41,9 @@ GEM
41
41
  rspec-expectations (~> 2.11.0)
42
42
  rspec-mocks (~> 2.11.0)
43
43
  rspec-core (2.11.1)
44
- rspec-expectations (2.11.2)
44
+ rspec-expectations (2.11.3)
45
45
  diff-lcs (~> 1.1.3)
46
- rspec-mocks (2.11.1)
46
+ rspec-mocks (2.11.2)
47
47
  sqlite3 (1.3.6)
48
48
  tzinfo (0.3.33)
49
49
 
@@ -1,28 +1,28 @@
1
1
  PATH
2
2
  remote: /Users/kostyantyn/Sites/github/gems/hydra_attribute
3
3
  specs:
4
- hydra_attribute (0.4.0.beta)
4
+ hydra_attribute (0.4.0.rc1)
5
5
  activerecord (>= 3.1.0)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- activemodel (3.2.7)
11
- activesupport (= 3.2.7)
10
+ activemodel (3.2.8)
11
+ activesupport (= 3.2.8)
12
12
  builder (~> 3.0.0)
13
- activerecord (3.2.7)
14
- activemodel (= 3.2.7)
15
- activesupport (= 3.2.7)
13
+ activerecord (3.2.8)
14
+ activemodel (= 3.2.8)
15
+ activesupport (= 3.2.8)
16
16
  arel (~> 3.0.2)
17
17
  tzinfo (~> 0.3.29)
18
- activesupport (3.2.7)
18
+ activesupport (3.2.8)
19
19
  i18n (~> 0.6)
20
20
  multi_json (~> 1.0)
21
21
  appraisal (0.4.1)
22
22
  bundler
23
23
  rake
24
24
  arel (3.0.2)
25
- builder (3.0.0)
25
+ builder (3.0.3)
26
26
  cucumber (1.2.1)
27
27
  builder (>= 2.1.2)
28
28
  diff-lcs (>= 1.1.3)
@@ -30,10 +30,10 @@ GEM
30
30
  json (>= 1.4.6)
31
31
  database_cleaner (0.8.0)
32
32
  diff-lcs (1.1.3)
33
- gherkin (2.11.1)
33
+ gherkin (2.11.2)
34
34
  json (>= 1.4.6)
35
- i18n (0.6.0)
36
- json (1.7.4)
35
+ i18n (0.6.1)
36
+ json (1.7.5)
37
37
  multi_json (1.3.6)
38
38
  rake (0.9.2.2)
39
39
  rspec (2.11.0)
@@ -41,9 +41,9 @@ GEM
41
41
  rspec-expectations (~> 2.11.0)
42
42
  rspec-mocks (~> 2.11.0)
43
43
  rspec-core (2.11.1)
44
- rspec-expectations (2.11.2)
44
+ rspec-expectations (2.11.3)
45
45
  diff-lcs (~> 1.1.3)
46
- rspec-mocks (2.11.1)
46
+ rspec-mocks (2.11.2)
47
47
  sqlite3 (1.3.6)
48
48
  tzinfo (0.3.33)
49
49
 
@@ -53,7 +53,7 @@ module HydraAttribute
53
53
  generated_hydra_attribute_methods.module_eval <<-EOS, __FILE__, __LINE__ + 1
54
54
  #{defn}
55
55
  if hydra_set_id? and self.class.hydra_set_attribute_ids(hydra_set_id).exclude?(#{hydra_attribute.id})
56
- raise MissingAttributeInHydraSetError, 'Attribute "#{hydra_attribute.name}" does not exist in hydra set "\#{hydra_set(hydra_set_id).name}"'
56
+ raise MissingAttributeInHydraSetError, %(Hydra attribute "#{hydra_attribute.name}" does not exist in hydra set "\#{self.class.hydra_set(hydra_set_id).name}")
57
57
  end
58
58
 
59
59
  if value_model = hydra_value_model(#{hydra_attribute.id})
@@ -99,7 +99,7 @@ module HydraAttribute
99
99
  attr_name = self.class.send(:attribute_method_matcher, name).attr_name
100
100
  end
101
101
 
102
- if hydra_attribute?(attr_name)
102
+ if self.class.hydra_attribute_names.include?(attr_name)
103
103
  self.class.hydra_set_attribute_names(hydra_set_id).include?(attr_name)
104
104
  else
105
105
  super
@@ -136,9 +136,8 @@ module HydraAttribute
136
136
  end
137
137
 
138
138
  def inspect
139
- attrs = self.class.column_names.map { |name| "#{name}: #{attribute_for_inspect(name)}" }
140
- attrs += hydra_value_models.map { |model| "#{model.hydra_attribute_name}: #{model.attribute_for_inspect('value')}" }
141
- "#<#{self.class} #{attrs.join(', ')}>"
139
+ attrs = hydra_value_models.map { |model| "#{model.hydra_attribute_name}: #{model.attribute_for_inspect('value')}" }
140
+ super.gsub(/>$/, ", #{attrs.join(', ')}>")
142
141
  end
143
142
 
144
143
  private
@@ -4,7 +4,7 @@ module HydraAttribute
4
4
  module QueryMethods
5
5
  extend ActiveSupport::Concern
6
6
 
7
- MULTI_VALUE_METHODS = [:hydra_joins_aliases, :hydra_select_values]
7
+ MULTI_VALUE_METHODS = [:hydra_joins_aliases, :hydra_select_values, :hydra_attributes]
8
8
 
9
9
  included do
10
10
  attr_writer *MULTI_VALUE_METHODS
@@ -25,6 +25,7 @@ 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_attributes << name
28
29
  relation.hydra_joins_aliases << hydra_helper.ref_alias(name, value)
29
30
  relation.joins_values += hydra_helper.build_joins(name, value)
30
31
  relation.where_values += build_where(hydra_helper.where_options(name, value))
@@ -58,6 +59,11 @@ module HydraAttribute
58
59
  @select_values << hydra_helper.prepend_table_name('hydra_set_id')
59
60
  end
60
61
 
62
+ if hydra_attributes.any?
63
+ hydra_sets = klass.hydra_sets.select { |hydra_set| hydra_set.hydra_attributes.any? { |attr| attr.name.in?(hydra_attributes) } }
64
+ @where_values << table[:hydra_set_id].in(hydra_sets.map(&:id)).or(table[:hydra_set_id].eq(nil))
65
+ end
66
+
61
67
  super
62
68
  end
63
69
 
@@ -20,7 +20,7 @@ module HydraAttribute
20
20
 
21
21
  def build
22
22
  build_associations
23
- build_white_list
23
+ update_mass_assignment_security
24
24
  end
25
25
 
26
26
  private
@@ -30,8 +30,8 @@ module HydraAttribute
30
30
  end
31
31
  end
32
32
 
33
- def build_white_list
34
- klass.hydra_attributes.each(&:toggle_white_list!)
33
+ def update_mass_assignment_security
34
+ klass.hydra_attributes.each(&:update_mass_assignment_security)
35
35
  end
36
36
  end
37
37
  end
@@ -19,17 +19,17 @@ module HydraAttribute
19
19
 
20
20
  before_destroy :delete_dependent_values
21
21
  after_commit :clear_entity_cache
22
- after_commit :toggle_white_list!
22
+ after_commit :update_mass_assignment_security
23
23
 
24
24
  # @COMPATIBILITY with 3.1.x association module is directly added to the class instead of including module
25
25
  def hydra_sets_with_clearing_cache=(value)
26
26
  self.hydra_sets_without_clearing_cache = value
27
- clear_entity_cache
27
+ entity_type.constantize.clear_hydra_method_cache!
28
28
  value
29
29
  end
30
30
  alias_method_chain :hydra_sets=, :clearing_cache
31
31
 
32
- def toggle_white_list!
32
+ def update_mass_assignment_security
33
33
  if destroyed? or !white_list?
34
34
  remove_from_white_list
35
35
  else
@@ -44,7 +44,7 @@ module HydraAttribute
44
44
  end
45
45
 
46
46
  def clear_entity_cache
47
- entity_type.constantize.clear_hydra_method_cache!
47
+ entity_type.constantize.reset_hydra_attribute_methods!
48
48
  end
49
49
 
50
50
  def add_to_white_list