semantic_attributes 1.0.2

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 (75) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +99 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README +54 -0
  6. data/Rakefile +37 -0
  7. data/gist.rdoc +208 -0
  8. data/lib/active_record/validation_recursion_control.rb +33 -0
  9. data/lib/core_ext/class.rb +14 -0
  10. data/lib/predicates/aliased.rb +22 -0
  11. data/lib/predicates/association.rb +43 -0
  12. data/lib/predicates/base.rb +93 -0
  13. data/lib/predicates/blacklisted.rb +23 -0
  14. data/lib/predicates/domain.rb +31 -0
  15. data/lib/predicates/email.rb +42 -0
  16. data/lib/predicates/enumerated.rb +23 -0
  17. data/lib/predicates/hex_color.rb +24 -0
  18. data/lib/predicates/length.rb +71 -0
  19. data/lib/predicates/number.rb +104 -0
  20. data/lib/predicates/pattern.rb +22 -0
  21. data/lib/predicates/phone_number.rb +62 -0
  22. data/lib/predicates/required.rb +22 -0
  23. data/lib/predicates/same_as.rb +17 -0
  24. data/lib/predicates/size.rb +2 -0
  25. data/lib/predicates/time.rb +43 -0
  26. data/lib/predicates/unique.rb +71 -0
  27. data/lib/predicates/url.rb +62 -0
  28. data/lib/predicates/usa_state.rb +87 -0
  29. data/lib/predicates/usa_zip_code.rb +25 -0
  30. data/lib/predicates/whitelisted.rb +2 -0
  31. data/lib/predicates.rb +3 -0
  32. data/lib/semantic_attributes/attribute.rb +46 -0
  33. data/lib/semantic_attributes/attribute_formats.rb +67 -0
  34. data/lib/semantic_attributes/locale/en.yml +31 -0
  35. data/lib/semantic_attributes/predicates.rb +170 -0
  36. data/lib/semantic_attributes/set.rb +40 -0
  37. data/lib/semantic_attributes/version.rb +3 -0
  38. data/lib/semantic_attributes.rb +37 -0
  39. data/semantic_attributes.gemspec +29 -0
  40. data/test/db/database.yml +3 -0
  41. data/test/db/models.rb +38 -0
  42. data/test/db/schema.rb +33 -0
  43. data/test/fixtures/addresses.yml +15 -0
  44. data/test/fixtures/roles.yml +4 -0
  45. data/test/fixtures/roles_users.yml +6 -0
  46. data/test/fixtures/services.yml +6 -0
  47. data/test/fixtures/subscriptions.yml +16 -0
  48. data/test/fixtures/users.yml +20 -0
  49. data/test/test_helper.rb +67 -0
  50. data/test/unit/active_record_predicates_test.rb +88 -0
  51. data/test/unit/attribute_formats_test.rb +40 -0
  52. data/test/unit/inheritance_test.rb +23 -0
  53. data/test/unit/predicates/aliased_test.rb +17 -0
  54. data/test/unit/predicates/association_predicate_test.rb +51 -0
  55. data/test/unit/predicates/base_test.rb +53 -0
  56. data/test/unit/predicates/blacklisted_predicate_test.rb +28 -0
  57. data/test/unit/predicates/domain_predicate_test.rb +27 -0
  58. data/test/unit/predicates/email_test.rb +82 -0
  59. data/test/unit/predicates/enumerated_predicate_test.rb +22 -0
  60. data/test/unit/predicates/hex_color_predicate_test.rb +29 -0
  61. data/test/unit/predicates/length_predicate_test.rb +85 -0
  62. data/test/unit/predicates/number_test.rb +109 -0
  63. data/test/unit/predicates/pattern_predicate_test.rb +29 -0
  64. data/test/unit/predicates/phone_number_predicate_test.rb +41 -0
  65. data/test/unit/predicates/required_predicate_test.rb +13 -0
  66. data/test/unit/predicates/same_as_predicate_test.rb +19 -0
  67. data/test/unit/predicates/time_test.rb +49 -0
  68. data/test/unit/predicates/unique_test.rb +58 -0
  69. data/test/unit/predicates/url_test.rb +86 -0
  70. data/test/unit/predicates/usa_state_test.rb +31 -0
  71. data/test/unit/predicates/usa_zip_code_test.rb +42 -0
  72. data/test/unit/semantic_attribute_test.rb +18 -0
  73. data/test/unit/semantic_attributes_test.rb +29 -0
  74. data/test/unit/validations_test.rb +121 -0
  75. metadata +235 -0
@@ -0,0 +1,2 @@
1
+ # Whitelisted is just an alias for Enumerated
2
+ class Predicates::Whitelisted < Predicates::Enumerated; end
data/lib/predicates.rb ADDED
@@ -0,0 +1,3 @@
1
+ # A namespace for predicates.
2
+ module Predicates
3
+ end
@@ -0,0 +1,46 @@
1
+ # a field with its assocatied predicates
2
+ class SemanticAttributes::Attribute
3
+ attr_reader :field
4
+ def initialize(field)
5
+ @field = field.to_sym
6
+ @set = []
7
+ end
8
+
9
+ # whether this attribute has the given predicate
10
+ # accepts the 'short name' of a predicate (e.g. "phone_number" instead of Predicates::PhoneNumber)
11
+ def has?(predicate)
12
+ predicate = class_of predicate
13
+ @set.any? {|item| item.is_a? predicate}
14
+ end
15
+
16
+ # retrieves a predicate by name from the attribute
17
+ # accepts the 'short name' of a predicate (e.g. "phone_number" instead of Predicates::PhoneNumber)
18
+ def get(predicate)
19
+ predicate = class_of predicate
20
+ @set.find {|item| item.is_a? predicate}
21
+ end
22
+
23
+ # adds a predicate to the attribute, with options
24
+ # accepts the 'short name' of a predicate (e.g. "phone_number" instead of Predicates::PhoneNumber)
25
+ def add(predicate, options = {})
26
+ predicate = class_of predicate
27
+ @set << predicate.new(self.field, options)
28
+ end
29
+
30
+ # The list of predicates, without any shortcuts
31
+ def predicates
32
+ @set
33
+ end
34
+
35
+ protected
36
+
37
+ # fully-qualified-name of a predicate
38
+ def fqn(short_name)
39
+ "Predicates::#{short_name.to_s.camelize}"
40
+ end
41
+
42
+ # the actual predicate class for the given name
43
+ def class_of(short_name)
44
+ fqn(short_name).constantize
45
+ end
46
+ end
@@ -0,0 +1,67 @@
1
+ module SemanticAttributes #:nodoc:
2
+ class MissingAttribute < NameError
3
+ def initialize(attr)
4
+ super("#{attr} is not defined. You may have a typo or need to run migrations.")
5
+ end
6
+ end
7
+
8
+ module AttributeFormats
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+ end
12
+
13
+ def respond_to?(method_name, *args)
14
+ if md = method_name.to_s.match(/_for_human$/) and semantic_attributes.include?(md.pre_match)
15
+ true
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def method_missing(method_name, *args, &block)
24
+ if md = method_name.to_s.match(/_for_human$/) and semantic_attributes.include?(md.pre_match)
25
+ self.class.humanize(md.pre_match, self.send(md.pre_match))
26
+ else
27
+ begin
28
+ super
29
+ rescue
30
+ raise $!, $!.to_s, caller
31
+ end
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ # converts the object into human format according to the predicates for the attribute.
37
+ # the object may really be anything: string, integer, date, etc.
38
+ def humanize(attr, obj)
39
+ self.semantic_attributes[attr].predicates.inject(obj) { |val, predicate| val = predicate.to_human(val) }
40
+ end
41
+
42
+ # converts the object into machine format according to the predicates for the attribute.
43
+ # the object should be a simple object like a string, array, or hash. but really, it depends on the the predicates.
44
+ def normalize(attr, obj)
45
+ self.semantic_attributes[attr].predicates.inject(obj) { |val, predicate| val = predicate.normalize(val) }
46
+ end
47
+
48
+ protected
49
+
50
+ def define_normalization_method_for(attr)
51
+ self.define_attribute_methods if self.respond_to? :attribute_methods_generated? and !self.attribute_methods_generated?
52
+
53
+ writer = "#{attr}_with_normalization="
54
+ old_writer = "#{attr}_without_normalization=".to_sym
55
+ unless method_defined? writer
56
+ define_method writer do |val|
57
+ send(old_writer, self.class.normalize(attr, val))
58
+ end
59
+ alias_method_chain "#{attr}=", :normalization
60
+ end
61
+ rescue NameError
62
+ raise SemanticAttributes::MissingAttribute.new(attr)
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,31 @@
1
+ # based on ActiveRecord's locale/en.yml file
2
+ en:
3
+ semantic-attributes:
4
+ errors:
5
+ messages:
6
+ us_zip_code: 'must be a US zip code.'
7
+ required: 'is required.'
8
+ exclusion: "is not an allowed option."
9
+ inclusion: "is not an allowed option."
10
+ domain: "must be a simple domain."
11
+ email: 'must be an email address.'
12
+ hex: "must be a hex color."
13
+ invalid: "is invalid."
14
+ phone: "must be a phone number."
15
+ time: "must be a point in time."
16
+ taken: "has already been taken."
17
+ url: 'must be a valid URL.'
18
+ same_as: "must match."
19
+ us_state_or_territory: "must be a US state or territory."
20
+ us_state: "must be a US state."
21
+ inexact_length: "must be %{count} characters long"
22
+ wrong_length: "must be %{min} to %{max} characters long"
23
+ too_short: "must be more than %{min} characters long"
24
+ too_long: "must be less than %{max} characters long"
25
+ not_a_number: "must be a number."
26
+ between: "must be a number from %{min} to %{max}."
27
+ greater_than_or_equal_to: "must be a number at least %{min}."
28
+ less_than_or_equal_to: "must be a number no more than %{max}."
29
+ greater_than: "must be a number greater than %{min}."
30
+ less_than: "must be a number less than %{max}."
31
+
@@ -0,0 +1,170 @@
1
+ # active record tie-ins
2
+ module SemanticAttributes
3
+ module Predicates
4
+ def self.included(base)
5
+ base.class_eval do
6
+ extend ClassMethods
7
+ base.semantic_attributes = SemanticAttributes::Set.new
8
+ attribute_method_suffix '_valid?'
9
+ validate :validate_predicates
10
+ end
11
+ end
12
+
13
+ def semantic_attributes
14
+ self.class.semantic_attributes
15
+ end
16
+
17
+ # the validation hook that checks all predicates
18
+ def validate_predicates
19
+ semantic_attributes.each do |attribute|
20
+ applicable_predicates = attribute.predicates.select{|p| validate_predicate?(p)}
21
+
22
+ next if applicable_predicates.empty?
23
+
24
+ # skip validations on associations that aren't already loaded
25
+ # note: it's not worth skipping a has_one, since there's no way to distinguish
26
+ # between loaded and absent without a query.
27
+ if reflection = self.class.reflect_on_association(attribute.field.to_sym)
28
+ if reflection.collection?
29
+ next unless self.association(attribute.field).loaded?
30
+ elsif reflection.macro == :belongs_to and self[reflection.foreign_key]
31
+ next unless self.association(attribute.field).loaded?
32
+ end
33
+ end
34
+
35
+ value = self.send(attribute.field)
36
+ applicable_predicates.each do |predicate|
37
+ if value.blank?
38
+ # it's empty, so add an error or not but either way move along
39
+ self.errors.add(attribute.field, predicate.error) unless predicate.allow_empty?
40
+ next
41
+ end
42
+
43
+ unless predicate.validate(value, self)
44
+ self.errors.add(attribute.field, predicate.error)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ protected
51
+ # Returns true if this attribute would pass validation during the next save.
52
+ # Intended to be called via attribute suffix, like:
53
+ # User#login_valid?
54
+ def attribute_valid?(attr)
55
+ semantic_attributes[attr.to_sym].predicates.all? do |p|
56
+ !validate_predicate?(p) or (self.send(attr).blank? and p.allow_empty?) or p.validate(self.send(attr), self)
57
+ end
58
+ end
59
+
60
+ # Returns true if the given predicate (for a specific attribute) should be validated.
61
+ def validate_predicate?(predicate)
62
+ case predicate.validate_if
63
+ when Symbol
64
+ return false unless send(predicate.validate_if)
65
+
66
+ when Proc
67
+ return false unless predicate.validate_if.call(self)
68
+ end
69
+
70
+ case predicate.validate_on
71
+ when :create
72
+ return false unless new_record?
73
+
74
+ when :update
75
+ return false if new_record?
76
+ end
77
+
78
+ true
79
+ end
80
+
81
+ module ClassMethods
82
+ def semantic_attributes
83
+ @semantic_attributes ||= SemanticAttributes::Set.new
84
+ end
85
+
86
+ def semantic_attributes=(val)
87
+ @semantic_attributes = val || SemanticAttributes::Set.new
88
+ end
89
+
90
+ def inherited(klass)
91
+ klass.semantic_attributes = self.semantic_attributes.dup
92
+ super
93
+ end
94
+
95
+ # Provides sugary syntax for adding and querying predicates
96
+ #
97
+ # The syntax supports the following forms:
98
+ # #{attribute}_is_#{predicate}(options = {})
99
+ # #{attribute}_is_a_#{predicate}(options = {})
100
+ # #{attribute}_is_an_#{predicate}(options = {})
101
+ # #{attribute}_has_#{predicate}(options = {})
102
+ # #{attribute}_has_a_#{predicate}(options = {})
103
+ # #{attribute}_has_an_#{predicate}(options = {})
104
+ #
105
+ # You may also add required-ness into the declaration:
106
+ # #{attribute}_is_a_required_#{predicate}(options = {})
107
+ #
108
+ # If you want to assign a predicate to multiple fields, you may replace the attribute component with the word 'fields', and pass a field list as the first argument, like this:
109
+ # fields_are_#{predicate}(fields = [], options = {})
110
+ #
111
+ # Each form may also have a question mark at the end, to query whether the attribute has the predicate.
112
+ #
113
+ # In order to avoid clashing with other method_missing setups, this syntax is checked *last*, after all other method_missing metaprogramming attempts have failed.
114
+ def method_missing(name, *args)
115
+ begin
116
+ super
117
+ rescue NameError
118
+ if /^(.*)_(is|has|are)_(an?_)?(required_)?([^?]*)(\?)?$/.match(name.to_s)
119
+ options = args.last.is_a?(Hash) ? args.pop : {}
120
+ options[:or_empty] = false if !$4.nil?
121
+ fields = ($1 == 'fields') ? args.map(&:to_s) : [$1]
122
+
123
+ predicate = $5
124
+ if $6 == '?'
125
+ self.semantic_attributes[fields.first].has? predicate
126
+ else
127
+ args = [predicate]
128
+ args << options if options
129
+ fields.each do |field|
130
+ # TODO: create a less sugary method that may be used programmatically and takes care of defining the normalization method properly
131
+ define_normalization_method_for field
132
+ self.semantic_attributes[field].add *args
133
+ end
134
+ end
135
+ else
136
+ raise
137
+ end
138
+ end
139
+ end
140
+
141
+ # Provides a way to pre-validate a single value out of context of
142
+ # an entire record. This is helpful for validating parts of a form
143
+ # before it has been submitted.
144
+ #
145
+ # For values that are (in)valid only in context, such as the common
146
+ # :password_confirmation (which is only valid with a matching :password),
147
+ # additional values may be specified.
148
+ #
149
+ # WARNING: I still have not figured out what to do about differences in
150
+ # validation for new/existing records.
151
+ #
152
+ # Returns first error message if value is expected invalid.
153
+ #
154
+ # Example:
155
+ # User.expected_error_for(:username, "bob")
156
+ # => "has already been taken."
157
+ # User.expected_error_for(:username, "bob2392")
158
+ # => nil
159
+ # User.expected_error_for(:password_confirmation, "mismatched", :password => "opensesame")
160
+ # => "must be the same as password."
161
+ def expected_error_for(attribute, value, extra_values = {})
162
+ @record = self.new(extra_values)
163
+ semantic_attributes[attribute.to_sym].predicates.each do |predicate|
164
+ return predicate.error_message unless predicate.validate(value, @record)
165
+ end
166
+ nil
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,40 @@
1
+ # a set of SemanticAttributes::Attribute objects, which themselves contain Predicates
2
+ class SemanticAttributes::Set
3
+ include Enumerable
4
+
5
+ def initialize
6
+ @set = []
7
+ end
8
+
9
+ def add(semantic_attribute)
10
+ unless semantic_attribute.is_a? SemanticAttributes::Attribute
11
+ raise ArgumentError, 'Must pass a SemanticAttributes::Attribute object'
12
+ end
13
+ @set << semantic_attribute
14
+ end
15
+
16
+ # method for field lookups. creates the field if it doesn't exist.
17
+ def [](field)
18
+ field = field.to_sym
19
+ semantic_attribute = @set.find {|i| i.field == field}
20
+ self.add(semantic_attribute = SemanticAttributes::Attribute.new(field)) unless semantic_attribute
21
+ semantic_attribute
22
+ end
23
+
24
+ # method for field lookups which does *not* create the field if it doesn't exist
25
+ def include?(field)
26
+ field = field.to_sym
27
+ @set.find {|i| i.field == field} ? true : false
28
+ end
29
+
30
+ def each
31
+ @set.each do |i|
32
+ yield i
33
+ end
34
+ end
35
+
36
+ private
37
+ def initialize_copy(from)
38
+ @set = from.instance_variable_get('@set').clone
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module SemanticAttributes
2
+ VERSION = "1.0.2"
3
+ end
@@ -0,0 +1,37 @@
1
+ I18n.load_path << File.dirname(__FILE__) + '/semantic_attributes/locale/en.yml'
2
+
3
+ module SemanticAttributes; end
4
+
5
+ require 'core_ext/class'
6
+ require 'active_record/validation_recursion_control'
7
+ require 'predicates'
8
+ require 'predicates/base'
9
+ require 'predicates/enumerated'
10
+ require 'predicates/pattern'
11
+ require 'predicates/aliased'
12
+ require 'predicates/association'
13
+ require 'predicates/blacklisted'
14
+ require 'predicates/domain'
15
+ require 'predicates/email'
16
+ require 'predicates/hex_color'
17
+ require 'predicates/length'
18
+ require 'predicates/number'
19
+ require 'predicates/phone_number'
20
+ require 'predicates/required'
21
+ require 'predicates/same_as'
22
+ require 'predicates/size'
23
+ require 'predicates/time'
24
+ require 'predicates/unique'
25
+ require 'predicates/url'
26
+ require 'predicates/usa_state'
27
+ require 'predicates/usa_zip_code'
28
+ require 'predicates/aliased'
29
+ require 'semantic_attributes/attribute'
30
+ require 'semantic_attributes/attribute_formats'
31
+ require 'semantic_attributes/predicates'
32
+ require 'semantic_attributes/set'
33
+ require 'semantic_attributes/version'
34
+
35
+ ActiveRecord::Base.send(:include, SemanticAttributes::Predicates)
36
+ ActiveRecord::Base.send(:include, SemanticAttributes::AttributeFormats)
37
+ ActiveRecord::Base.send(:include, ActiveRecord::ValidationRecursionControl)
@@ -0,0 +1,29 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ # Maintain your gem's version:
4
+ require "semantic_attributes/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'semantic_attributes'
8
+ s.version = SemanticAttributes::VERSION
9
+ s.authors = ["Lance Ivy"]
10
+ s.email = 'lance@kickstarter.com'
11
+ s.platform = Gem::Platform::RUBY
12
+ s.homepage = %q{http://github.com/kickstarter/semantic-attributes}
13
+ s.require_paths = ["lib"]
14
+ s.summary = 'A validation library for ActiveRecord models.'
15
+ s.description = 'A validation library for ActiveRecord models that allows ' +
16
+ 'introspection (User.name_is_required?) and supports database' +
17
+ ' normalization (aka "form input cleaning").'
18
+
19
+ # s.add_dependency "activerecord", ">= 3.0.12"
20
+ s.add_dependency "rails", "~> 3.2.2"
21
+
22
+ s.add_development_dependency "bundler", ">= 1.0.0"
23
+ s.add_development_dependency "sqlite3"
24
+ s.add_development_dependency "rake", "0.8.7"
25
+ s.add_development_dependency "mocha", ">= 0.10.5"
26
+
27
+ s.files = `git ls-files`.split("\n")
28
+ s.test_files = `git ls-files -- test/*`.split("\n")
29
+ end
@@ -0,0 +1,3 @@
1
+ semantic_attributes_test:
2
+ adapter: sqlite3
3
+ database: ':memory:'
data/test/db/models.rb ADDED
@@ -0,0 +1,38 @@
1
+ # A set of classes design to offer not only every association, but every pair of associations.
2
+ # For example, there's a has_one paired with a normal belongs_to, and another has_one paired with a polymorphic belongs_to.
3
+ # To use, mix the PluginTestModels module into the TestCase
4
+ module PluginTestModels
5
+ def self.included(base)
6
+ base.set_fixture_class({
7
+ :addresses => PluginTestModels::Address,
8
+ :users => PluginTestModels::User,
9
+ :services => PluginTestModels::Service,
10
+ :subscriptions => PluginTestModels::Subscription,
11
+ :roles => PluginTestModels::Role
12
+ })
13
+ end
14
+
15
+ class Address < ActiveRecord::Base
16
+ belongs_to :addressable, :polymorphic => true
17
+ end
18
+
19
+ class User < ActiveRecord::Base
20
+ has_and_belongs_to_many :roles
21
+ has_one :subscription
22
+ has_one :address, :as => :addressable
23
+ end
24
+
25
+ class Service < ActiveRecord::Base
26
+ has_many :subscriptions
27
+ has_many :users, :through => :subscriptions
28
+ end
29
+
30
+ class Subscription < ActiveRecord::Base
31
+ belongs_to :service
32
+ belongs_to :user
33
+ end
34
+
35
+ class Role < ActiveRecord::Base
36
+ has_and_belongs_to_many :users
37
+ end
38
+ end
data/test/db/schema.rb ADDED
@@ -0,0 +1,33 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+
3
+ create_table :addresses, :force => true do |t|
4
+ t.column :name, :string
5
+ t.column :addressable_id, :integer
6
+ t.column :addressable_type, :string
7
+ end
8
+
9
+ create_table :users, :force => true do |t|
10
+ t.column :login, :string
11
+ t.column :first_name, :string
12
+ t.column :last_name, :string
13
+ t.column :cell, :string
14
+ end
15
+
16
+ create_table :roles_users, :force => true, :id => false do |t|
17
+ t.column :user_id, :integer
18
+ t.column :role_id, :integer
19
+ end
20
+
21
+ create_table :services, :force => true do |t|
22
+ t.column :name, :string
23
+ end
24
+
25
+ create_table :subscriptions, :force => true do |t|
26
+ t.column :service_id, :integer
27
+ t.column :user_id, :integer
28
+ end
29
+
30
+ create_table :roles, :force => true do |t|
31
+ t.column :name, :string
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ bobs_home:
2
+ id: 1
3
+ name: Home
4
+ addressable_id: 1
5
+ addressable_type: 'PluginTestModels::User'
6
+ bobs_work:
7
+ id: 2
8
+ name: Work
9
+ addressable_id: 1
10
+ addressable_type: 'PluginTestModels::User'
11
+ sues_home:
12
+ id: 3
13
+ name: Home
14
+ addressable_id: 2
15
+ addressable_type: 'PluginTestModels::User'
@@ -0,0 +1,4 @@
1
+ cook:
2
+ name: Cook
3
+ gardener:
4
+ name: Gardener
@@ -0,0 +1,6 @@
1
+ bob_is_a_cook:
2
+ role_id: 1
3
+ user_id: 1
4
+ sue_is_a_gardener:
5
+ role_id: 2
6
+ user_id: 2
@@ -0,0 +1,6 @@
1
+ free:
2
+ id: 1
3
+ name: Free
4
+ premium:
5
+ id: 2
6
+ name: Premium
@@ -0,0 +1,16 @@
1
+ bob_is_free:
2
+ id: 1
3
+ service_id: 1
4
+ user_id: 1
5
+ sue_is_premium:
6
+ id: 2
7
+ service_id: 2
8
+ user_id: 2
9
+ george_is_free:
10
+ id: 3
11
+ service_id: 1
12
+ user_id: 3
13
+ michael_is_free:
14
+ id: 4
15
+ service_id: 1
16
+ user_id: 4
@@ -0,0 +1,20 @@
1
+ bob:
2
+ id: 1
3
+ login: bob
4
+ first_name: Bobby
5
+ last_name: Billiards
6
+ sue:
7
+ id: 2
8
+ login: sue
9
+ first_name: Sue
10
+ last_name: Swagger
11
+ george:
12
+ id: 3
13
+ login: george
14
+ first_name: George
15
+ last_name: Ginormous
16
+ michael:
17
+ id: 4
18
+ login: michael
19
+ first_name: Michael
20
+ last_name: Mindful
@@ -0,0 +1,67 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+
3
+ # load the support libraries
4
+ require 'test/unit'
5
+ require 'rubygems'
6
+ require 'active_record'
7
+ require 'active_record/fixtures'
8
+ require 'active_support/time'
9
+
10
+ # load the code-to-be-tested
11
+ require 'semantic_attributes'
12
+
13
+ Time.zone = 'UTC'
14
+
15
+ # establish the database connection
16
+ ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/db/database.yml'))
17
+ ActiveRecord::Base.establish_connection('semantic_attributes_test')
18
+
19
+ # capture the logging
20
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/test.log")
21
+
22
+ # load the schema ... silently
23
+ ActiveRecord::Migration.verbose = false
24
+ load(File.dirname(__FILE__) + "/db/schema.rb")
25
+
26
+ # load the ActiveRecord models
27
+ require File.dirname(__FILE__) + '/db/models'
28
+
29
+ # configure the TestCase settings
30
+ class SemanticAttributes::TestCase < ActiveSupport::TestCase
31
+ include ActiveRecord::TestFixtures
32
+ include PluginTestModels
33
+
34
+ self.use_transactional_fixtures = true
35
+ self.use_instantiated_fixtures = false
36
+ self.fixture_path = File.dirname(__FILE__) + '/fixtures/'
37
+
38
+ fixtures :all
39
+ end
40
+
41
+ class ActiveRecord::Base
42
+ # Aids the management of per-test semantics.
43
+ #
44
+ # Examples:
45
+ #
46
+ # User.stub_semantics_with(:email => :email)
47
+ # User.stub_semantics_with(:email => [:email, :unique])
48
+ # User.stub_semantics_with(:email => {:length => {:above => 5}})
49
+ # User.stub_semantics_with(:email => [:email, {:length => {:above => 5}}])
50
+ def self.stub_semantics_with(attr_predicates = {})
51
+ semantics = SemanticAttributes::Set.new
52
+ attr_predicates.each do |attr, predicates|
53
+ [predicates].flatten.each do |predicate|
54
+ case predicate
55
+ when String, Symbol
56
+ semantics[attr].add(predicate)
57
+ when Hash
58
+ semantics[attr].add(predicate.keys.first, predicate.values.first)
59
+ else
60
+ raise '???'
61
+ end
62
+ end
63
+ end
64
+
65
+ self.stubs(:semantic_attributes).returns(semantics)
66
+ end
67
+ end