hydra_attribute 0.1.0

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