semantic_attributes 1.0.2

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