hydra_attribute 0.1.0

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 (57) hide show
  1. data/.gitignore +20 -0
  2. data/.rspec +2 -0
  3. data/Appraisals +7 -0
  4. data/CHANGELOG.md +6 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +22 -0
  7. data/README.md +122 -0
  8. data/Rakefile +12 -0
  9. data/features/attribute_methods.feature +151 -0
  10. data/features/define_attributes.feature +61 -0
  11. data/features/load_associations.feature +45 -0
  12. data/features/order_conditions.feature +93 -0
  13. data/features/select_attributes.feature +56 -0
  14. data/features/step_definitions/class_steps.rb +32 -0
  15. data/features/step_definitions/model_steps.rb +31 -0
  16. data/features/step_definitions/record_steps.rb +103 -0
  17. data/features/support/env.rb +24 -0
  18. data/features/support/schema.rb +51 -0
  19. data/features/support/world.rb +31 -0
  20. data/features/typecast_attributes.feature +29 -0
  21. data/features/where_conditions.feature +77 -0
  22. data/gemfiles/3.1.gemfile +7 -0
  23. data/gemfiles/3.1.gemfile.lock +60 -0
  24. data/gemfiles/3.2.gemfile +7 -0
  25. data/gemfiles/3.2.gemfile.lock +60 -0
  26. data/hydra_attribute.gemspec +24 -0
  27. data/lib/generators/hydra_attribute/install/USAGE +8 -0
  28. data/lib/generators/hydra_attribute/install/install_generator.rb +11 -0
  29. data/lib/generators/hydra_attribute/install/templates/hydra_attribute.txt +11 -0
  30. data/lib/hydra_attribute.rb +31 -0
  31. data/lib/hydra_attribute/active_record.rb +7 -0
  32. data/lib/hydra_attribute/active_record/attribute_methods.rb +72 -0
  33. data/lib/hydra_attribute/active_record/attribute_methods/before_type_cast.rb +16 -0
  34. data/lib/hydra_attribute/active_record/attribute_methods/read.rb +13 -0
  35. data/lib/hydra_attribute/active_record/relation.rb +44 -0
  36. data/lib/hydra_attribute/active_record/relation/query_methods.rb +162 -0
  37. data/lib/hydra_attribute/active_record/scoping.rb +15 -0
  38. data/lib/hydra_attribute/association_builder.rb +40 -0
  39. data/lib/hydra_attribute/attribute_builder.rb +57 -0
  40. data/lib/hydra_attribute/attribute_proxy.rb +16 -0
  41. data/lib/hydra_attribute/builder.rb +25 -0
  42. data/lib/hydra_attribute/configuration.rb +47 -0
  43. data/lib/hydra_attribute/migration.rb +27 -0
  44. data/lib/hydra_attribute/railtie.rb +9 -0
  45. data/lib/hydra_attribute/version.rb +3 -0
  46. data/spec/hydra_attribute/active_record/relation/query_methods_spec.rb +286 -0
  47. data/spec/hydra_attribute/active_record/relation_spec.rb +93 -0
  48. data/spec/hydra_attribute/active_record/scoping_spec.rb +19 -0
  49. data/spec/hydra_attribute/active_record_spec.rb +20 -0
  50. data/spec/hydra_attribute/association_builder_spec.rb +95 -0
  51. data/spec/hydra_attribute/attribute_builder_spec.rb +70 -0
  52. data/spec/hydra_attribute/attribute_helpers_spec.rb +70 -0
  53. data/spec/hydra_attribute/builder_spec.rb +39 -0
  54. data/spec/hydra_attribute/configuration_spec.rb +96 -0
  55. data/spec/hydra_attribute_spec.rb +20 -0
  56. data/spec/spec_helper.rb +17 -0
  57. metadata +196 -0
@@ -0,0 +1,93 @@
1
+ Feature: order conditions by hydra attributes
2
+ When order records by hydra attribute
3
+ Then correct table should be joined and order by value should be added
4
+
5
+ When correct table is already joined
6
+ Then only order condition should be added
7
+
8
+ When order by several attributes
9
+ Then order all of them by ascending
10
+
11
+ When reorder by attributes
12
+ Then old hydra attributes should be removed and new should be added
13
+
14
+ Background: create models and describe hydra attributes
15
+ Given removed constants if they exist:
16
+ | name |
17
+ | GroupProduct |
18
+ | SimpleProduct |
19
+ | Product |
20
+ And create model class "Product"
21
+ And create model class "SimpleProduct" as "Product" with hydra attributes:
22
+ | type | name |
23
+ | integer | code |
24
+ | integer | state |
25
+ | string | title |
26
+
27
+ Scenario Outline: order by one field
28
+ Given create models:
29
+ | model | attributes |
30
+ | SimpleProduct | name=[string:c] code=[integer:1] state=[integer:1] |
31
+ | SimpleProduct | name=[string:b] code=[integer:2] state=[integer:2] |
32
+ | SimpleProduct | name=[string:a] code=[integer:3] state=[integer:3] |
33
+ When order "SimpleProduct" records by "<attributes>"
34
+ Then "first" record should have "<first>"
35
+ And "last" record should have "<last>"
36
+
37
+ Scenarios: order conditions
38
+ | attributes | first | last |
39
+ | state=asc | code=[integer:1] | code=[integer:3] |
40
+ | state=desc | code=[integer:3] | code=[integer:1] |
41
+ | name=asc | code=[integer:3] | code=[integer:1] |
42
+ | name=desc | code=[integer:1] | code=[integer:3] |
43
+
44
+ Scenario Outline: order by several fields
45
+ Given create models:
46
+ | model | attributes |
47
+ | SimpleProduct | name=[string:c] code=[integer:1] state=[integer:1] title=[string:b] |
48
+ | SimpleProduct | name=[string:b] code=[integer:2] state=[integer:2] title=[string:a] |
49
+ | SimpleProduct | name=[string:a] code=[integer:3] state=[integer:3] title=[string:c] |
50
+ When order "SimpleProduct" records by "<attributes>"
51
+ Then "first" record should have "<first>"
52
+ And "last" record should have "<last>"
53
+
54
+ Scenarios: order conditions
55
+ | attributes | first | last |
56
+ | name state | code=[integer:3] | code=[integer:1] |
57
+ | state title | code=[integer:1] | code=[integer:3] |
58
+ | title state | code=[integer:2] | code=[integer:3] |
59
+
60
+ Scenario Outline: order by already joined field
61
+ Given create models:
62
+ | model | attributes |
63
+ | SimpleProduct | code=[integer:1] state=[integer:1] |
64
+ | SimpleProduct | code=[integer:2] title=[nil:] |
65
+ | SimpleProduct | code=[integer:3] state=[integer:1] title=[string:a] |
66
+ When filter "SimpleProduct" records by "<filter>"
67
+ And order records by "<order>"
68
+ Then total records should be "<count>"
69
+ And "first" record should have "<first>"
70
+ And "last" record should have "<last>"
71
+
72
+ Scenarios:
73
+ | filter | order | count | first | last |
74
+ | state=[integer:1] | state code | 2 | code=[integer:1] | code=[integer:3] |
75
+ | state=[nil:] | state code | 1 | code=[integer:2] | code=[integer:2] |
76
+ | title=[nil:] | title code | 2 | code=[integer:1] | code=[integer:2] |
77
+
78
+ Scenario Outline: reorder by hydra attributes
79
+ Given create models:
80
+ | model | attributes |
81
+ | SimpleProduct | code=[integer:1] name=[string:a] title=[string:c] |
82
+ | SimpleProduct | code=[integer:2] name=[string:b] title=[string:b] |
83
+ | SimpleProduct | code=[integer:3] name=[string:c] title=[string:a] |
84
+ When order "SimpleProduct" records by "<order>"
85
+ And reorder records by "<reorder>"
86
+ Then total records should be "<count>"
87
+ And "first" record should have "<first>"
88
+ And "last" record should have "<last>"
89
+
90
+ Scenarios:
91
+ | order | reorder | count | first | last |
92
+ | title | name title | 3 | code=[integer:1] | code=[integer:3] |
93
+ | name | title name | 3 | code=[integer:3] | code=[integer:1] |
@@ -0,0 +1,56 @@
1
+ Feature: select concrete attributes
2
+ When select hydra attribute
3
+ Then hydra attribute table should be joined and concrete attribute should be selected
4
+
5
+ Background: create models and describe hydra attributes
6
+ Given removed constants if they exist:
7
+ | name |
8
+ | GroupProduct |
9
+ | SimpleProduct |
10
+ | Product |
11
+ And create model class "Product"
12
+ And create model class "SimpleProduct" as "Product" with hydra attributes:
13
+ | type | name |
14
+ | integer | code |
15
+ | float | price |
16
+ | string | title |
17
+ | text | note |
18
+ | boolean | active |
19
+ | datetime | schedule |
20
+ And create models:
21
+ | model | attributes |
22
+ | SimpleProduct | name=[string:a] code=[integer:1] price=[float:4] title=[string:q] note=[string:z] active=[boolean:true] schedule=[datetime:2012-06-01] |
23
+ | SimpleProduct | name=[string:b] code=[integer:2] price=[float:5] title=[string:w] note=[string:x] active=[boolean:false] schedule=[datetime:2012-06-02] |
24
+ | SimpleProduct | name=[string:c] code=[integer:3] price=[float:6] note=[string:c] active=[boolean:true] schedule=[datetime:2012-06-03] |
25
+ | SimpleProduct | name=[string:d] code=[nil:] price=[float:7] note=[string:v] active=[boolean:false] schedule=[datetime:2012-06-04] |
26
+
27
+ Scenario Outline: select concrete attributes
28
+ When "SimpleProduct" select only the following columns "<columns>"
29
+ Then records should have only the following "<columns>" names
30
+ And records should raise "ActiveModel::MissingAttributeError" when call the following "<methods>"
31
+ And total records should be "4"
32
+
33
+ Scenarios: select attributes
34
+ | columns | methods |
35
+ | name | code price title note active schedule |
36
+ | name code | price title note active schedule |
37
+ | name code price | title note active schedule |
38
+ | code price title | name note active schedule |
39
+ | title note active | name code price schedule |
40
+ | schedule | name code price title note active |
41
+ | id schedule | name code price title note active |
42
+
43
+ Scenario Outline: filter collection and select concrete attributes
44
+ When "SimpleProduct" select only the following columns "<columns>"
45
+ And filter records by "<filter attributes>"
46
+ Then records should have only the following "<columns>" names
47
+ And records should raise "ActiveModel::MissingAttributeError" when call the following "<methods>"
48
+ And total records should be "<total>"
49
+
50
+ Scenarios: filter and select attributes
51
+ | columns | filter attributes | methods | total |
52
+ | name code | name=[string:a] | price title note active schedule | 1 |
53
+ | code | code=[integer:1] | name price title note active schedule | 1 |
54
+ | name code | code=[integer:1] | price title note active schedule | 1 |
55
+ | code title | title=[nil:] | name price note active schedule | 2 |
56
+ | code note | title=[nil:] | name price title active schedule | 2 |
@@ -0,0 +1,32 @@
1
+ Given /^removed constants if they exist:$/ do |table|
2
+ table.hashes.each do |hash|
3
+ Object.send(:remove_const, hash[:name]) if Object.const_defined?(hash[:name])
4
+ end
5
+ end
6
+
7
+ Given /^create model class "([^"]+)"$/ do |klass|
8
+ Object.const_set(klass, Class.new(ActiveRecord::Base))
9
+ end
10
+
11
+ Given /^create model class "([^"]+)" as "([^"]+)" with hydra attributes:$/ do |klass, sti_class, table|
12
+ Object.const_set klass, Class.new(Object.const_get(sti_class)) {
13
+ define_hydra_attributes do |hydra|
14
+ table.hashes.each do |hash|
15
+ hydra.send(hash[:type], hash[:name].to_sym)
16
+ end
17
+ end
18
+ }
19
+ end
20
+
21
+ Then /^class "([^"]+)"::"([^"]+)" "(should|should_not)" have (string|symbol) "([^"]+)" in array$/ do |klass, method, behavior, type, params|
22
+ klass = Object.const_get(klass)
23
+ params = params.split
24
+ params.map!(&:to_sym) if type == 'symbol'
25
+ klass.send(method).send(behavior) =~ params
26
+ end
27
+
28
+ Then /^class "([^"]+)"::"([^"]+)" "(should|should_not)" have "([^"]+)" hash$/ do |klass, method, behavior, params|
29
+ klass = Object.const_get(klass)
30
+ array = params.split.map{ |item| item.split('=').map(&:to_sym) }
31
+ klass.send(method).send(behavior) == Hash[array].stringify_keys
32
+ end
@@ -0,0 +1,31 @@
1
+ Given /^create models:$/ do |table|
2
+ table.hashes.each do |hash|
3
+ attributes = typecast_attributes(hash[:attributes])
4
+ Object.const_get(hash[:model]).create!(attributes)
5
+ end
6
+ end
7
+
8
+ Given /^create model "([^"]+)" with attributes "([^"]+)"$/ do |klass, attributes|
9
+ attrs = typecast_attributes(attributes)
10
+ @model = Object.const_get(klass).create!(attrs)
11
+ end
12
+
13
+ Then /^model "([^"]+)" should "(should|should_not)" to "([^"]+)"$/ do |klass, method, attributes|
14
+ model = Object.const_get(klass).new
15
+ attributes.split.each do |attribute|
16
+ model.send(method, respond_to(attribute))
17
+ end
18
+ end
19
+
20
+ Then /^it should have typecast attributes "([^"]+)"$/ do |attributes|
21
+ @model.reload # ensure that all attributes have correct type
22
+ typecast_attributes(attributes).each do |name, value|
23
+ @model.send(name).should == value
24
+ end
25
+ end
26
+
27
+ Then /^model "([^"]+)" should have only the following ((?:hydra )?attributes(?: before type cast)?) "([^"]+)"$/ do |klass, method, attributes|
28
+ model = Object.const_get(klass).new
29
+ method = method.gsub(/\s+/, '_')
30
+ model.send(method).keys.should =~ attributes.split(/\s+/)
31
+ end
@@ -0,0 +1,103 @@
1
+ When /^load all "([^"]+)" records$/ do |klass|
2
+ @records = Object.const_get(klass).all
3
+ end
4
+
5
+ When /^select "(first|last)" "([^"]+)" record$/ do |method, klass|
6
+ @record = Object.const_get(klass).send(method)
7
+ end
8
+
9
+ When /^filter "([^"]+)" by:$/ do |klass, table|
10
+ condition = table.hashes.each_with_object({}) { |item, hash| hash[item[:field].to_sym] = typecast_value(item[:value]) }
11
+ @records = Object.const_get(klass).where(condition)
12
+ end
13
+
14
+ When /^filter "([^"]+)" records by "([^"]+)"$/ do |klass, attribute|
15
+ @records = Object.const_get(klass)
16
+ step %Q(filter records by "#{attribute}")
17
+ end
18
+
19
+ When /^filter records by "([^"]+)"$/ do |attribute|
20
+ name, value = typecast_attribute(attribute)
21
+ @records = @records.where(name => value)
22
+ end
23
+
24
+ When /^(order|reorder) "([^"]+)" records by "([^"]+)"$/ do |sort_method, klass, attributes|
25
+ @records = Object.const_get(klass)
26
+ step %Q(#{sort_method} records by "#{attributes}")
27
+ end
28
+
29
+ When /^(order|reorder) records by "([^"]+)"$/ do |sort_method, attributes|
30
+ reverse = false
31
+ fields = attributes.split.inject([]) do |items, attribute|
32
+ name, direction = attribute.split('=')
33
+ reverse = true if direction == 'desc'
34
+ items << name.to_sym
35
+ end
36
+
37
+ @records = @records.send(sort_method, fields)
38
+ @records = @records.reverse_order if reverse
39
+ end
40
+
41
+ When /^"([^"]+)" select only the following columns "([^"]+)"$/ do |klass, columns|
42
+ @records = Object.const_get(klass).select(columns.split(/\s+/).map(&:to_sym))
43
+ end
44
+
45
+ Then /^record should have the following ((?:hydra )?attributes(?: before type cast)?) "([^"]+)" in attribute hash$/ do |method, attributes|
46
+ method = method.gsub(/\s+/, '_')
47
+ typecast_attributes(attributes).each do |(name, value)|
48
+ @record.send(method)[name.to_s].should == value
49
+ end
50
+ end
51
+
52
+ Then /^record (read attribute(?: before type cast)?) "([^"]+)" and value should be "([^"]+)"$/ do |method, attribute, value|
53
+ method = method.gsub(/\s+/, '_')
54
+ @record.send(method, attribute).should == typecast_value(value)
55
+ end
56
+
57
+ Then /^"(first|last)" record should have "([^"]+)"$/ do |method, attribute|
58
+ name, value = typecast_attribute(attribute)
59
+ @records.send(method).send(name).should == value
60
+ end
61
+
62
+ Then /^records should have the following attributes:$/ do |table|
63
+ table.hashes.each do |hash|
64
+ record = @records.detect { |r| r.send(hash[:field]) == typecast_value(hash[:value]) }
65
+ record.should_not be_nil
66
+ end
67
+ end
68
+
69
+ Then /^records should have only the following "([^"]+)" names$/ do |attributes|
70
+ @records.each do |record|
71
+ record.attributes.keys.should == attributes.split(/\s+/)
72
+ end
73
+ end
74
+
75
+ Then /^records should raise "([^"]+)" when call the following "([^"]+)"$/ do |error, methods|
76
+ error_class = error.constantize
77
+ @records.each do |record|
78
+ methods.split(/\s+/).each do |method|
79
+ lambda { record.send(method) }.should raise_error(error_class)
80
+ end
81
+ end
82
+ end
83
+
84
+ Then /^total records should be "([^"]+)"$/ do |count|
85
+ @records.should have(count.to_i).items
86
+ end
87
+
88
+ Then /^records "(should|should_not)" have loaded associations:$/ do |should, table|
89
+ table.hashes.each do |hash|
90
+ @records.each do |record|
91
+ record.association(hash[:association].to_sym).send(should, be_loaded)
92
+ end
93
+ end
94
+ end
95
+
96
+ Then /^"([^"]+)" records "(should|should_not)" have loaded associations:$/ do |klass, should, table|
97
+ table.hashes.each do |hash|
98
+ records = @records.select { |record| record.class.name == klass }
99
+ records.each do |record|
100
+ record.association(hash[:association].to_sym).send(should, be_loaded)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_record'
2
+ require 'hydra_attribute'
3
+ require 'database_cleaner'
4
+ require 'database_cleaner/cucumber'
5
+
6
+
7
+ ActiveRecord::Base.default_timezone = :utc
8
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
9
+ ActiveRecord::Base.extend(HydraAttribute::ActiveRecord)
10
+
11
+ DatabaseCleaner.strategy = :truncation
12
+
13
+ Before do
14
+ HydraAttribute::SUPPORT_TYPES.each do |type|
15
+ const = HydraAttribute.config.associated_const_name(type)
16
+ HydraAttribute.send(:remove_const, const) if HydraAttribute.const_defined?(const)
17
+ end
18
+ ActiveSupport::Dependencies::Reference.clear!
19
+ DatabaseCleaner.start
20
+ end
21
+
22
+ After do
23
+ DatabaseCleaner.clean
24
+ end
@@ -0,0 +1,51 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table "products", :force => true do |t|
3
+ t.string "type"
4
+ t.string "name"
5
+ t.string "info"
6
+ t.datetime "created_at", :null => false
7
+ t.datetime "updated_at", :null => false
8
+ end
9
+
10
+ create_table "hydra_string_attributes", :force => true do |t|
11
+ t.integer "entity_id"
12
+ t.string "entity_type"
13
+ t.string "name"
14
+ t.string "value"
15
+ end
16
+
17
+ create_table "hydra_float_attributes", :force => true do |t|
18
+ t.integer "entity_id"
19
+ t.string "entity_type"
20
+ t.string "name"
21
+ t.float "value"
22
+ end
23
+
24
+ create_table "hydra_boolean_attributes", :force => true do |t|
25
+ t.integer "entity_id"
26
+ t.string "entity_type"
27
+ t.string "name"
28
+ t.boolean "value"
29
+ end
30
+
31
+ create_table "hydra_integer_attributes", :force => true do |t|
32
+ t.integer "entity_id"
33
+ t.string "entity_type"
34
+ t.string "name"
35
+ t.integer "value"
36
+ end
37
+
38
+ create_table "hydra_text_attributes", :force => true do |t|
39
+ t.integer "entity_id"
40
+ t.string "entity_type"
41
+ t.string "name"
42
+ t.text "value"
43
+ end
44
+
45
+ create_table "hydra_datetime_attributes", :force => true do |t|
46
+ t.integer "entity_id"
47
+ t.string "entity_type"
48
+ t.string "name"
49
+ t.datetime "value"
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ module HydraAttribute
2
+ module Cucumber
3
+ module World
4
+ def typecast_value(schema)
5
+ type, value = schema.gsub(/\[|\]/, '').split(':', 2)
6
+ case type
7
+ when 'integer' then value.to_i
8
+ when 'float' then value.to_f
9
+ when 'boolean' then value == 'true' ? true : false
10
+ when 'nil' then nil
11
+ when 'datetime' then ActiveSupport::TimeZone.new('UTC').parse(value)
12
+ else value
13
+ end
14
+ end
15
+
16
+ def typecast_attribute(attribute)
17
+ name, schema = attribute.split('=')
18
+ [name.to_sym, typecast_value(schema)]
19
+ end
20
+
21
+ def typecast_attributes(attributes)
22
+ attributes.split(/(?<=\])\s+/).flatten.each_with_object({}) do |attribute, hash|
23
+ name, value = typecast_attribute(attribute)
24
+ hash[name] = value
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ World(HydraAttribute::Cucumber::World)
@@ -0,0 +1,29 @@
1
+ Feature: create model
2
+ Model should be created with typecast hydra attributes.
3
+
4
+ Background: create models and describe hydra attributes
5
+ Given removed constants if they exist:
6
+ | name |
7
+ | GroupProduct |
8
+ | SimpleProduct |
9
+ | Product |
10
+ And create model class "Product"
11
+ And create model class "SimpleProduct" as "Product" with hydra attributes:
12
+ | type | name |
13
+ | string | code |
14
+ | float | price |
15
+ And create model class "GroupProduct" as "Product" with hydra attributes:
16
+ | type | name |
17
+ | float | price |
18
+ | string | title |
19
+ | boolean | active |
20
+
21
+ Scenario Outline: create model with hydra attributes
22
+ Given create model "<model>" with attributes "<attributes>"
23
+ Then it should have typecast attributes "<typecast_attributes>"
24
+
25
+ Scenarios:
26
+ | model | attributes | typecast_attributes |
27
+ | SimpleProduct | code=[integer:1] price=[string:2.75] | code=[string:1] price=[float:2.75] |
28
+ | GroupProduct | price=[string:2] title=[integer:1] active=[integer:1] | price=[float:2] title=[string:1] active=[boolean:true] |
29
+ | GroupProduct | active=[integer:0] | price=[nil:] title=[nil:] active=[boolean:false] |