toystore 0.5

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 (113) hide show
  1. data/.autotest +11 -0
  2. data/.bundle/config +2 -0
  3. data/.gitignore +6 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +49 -0
  6. data/LICENSE +9 -0
  7. data/LOGGING.rdoc +16 -0
  8. data/README.rdoc +13 -0
  9. data/Rakefile +7 -0
  10. data/examples/memcached.rb +20 -0
  11. data/examples/memory.rb +20 -0
  12. data/examples/models.rb +51 -0
  13. data/examples/redis.rb +20 -0
  14. data/lib/toy.rb +81 -0
  15. data/lib/toy/attribute.rb +73 -0
  16. data/lib/toy/attributes.rb +137 -0
  17. data/lib/toy/caching.rb +20 -0
  18. data/lib/toy/callbacks.rb +48 -0
  19. data/lib/toy/collection.rb +55 -0
  20. data/lib/toy/connection.rb +28 -0
  21. data/lib/toy/dirty.rb +47 -0
  22. data/lib/toy/dolly.rb +30 -0
  23. data/lib/toy/embedded_list.rb +45 -0
  24. data/lib/toy/embedded_lists.rb +68 -0
  25. data/lib/toy/equality.rb +19 -0
  26. data/lib/toy/exceptions.rb +29 -0
  27. data/lib/toy/extensions/array.rb +22 -0
  28. data/lib/toy/extensions/boolean.rb +43 -0
  29. data/lib/toy/extensions/date.rb +24 -0
  30. data/lib/toy/extensions/float.rb +13 -0
  31. data/lib/toy/extensions/hash.rb +17 -0
  32. data/lib/toy/extensions/integer.rb +22 -0
  33. data/lib/toy/extensions/nil_class.rb +17 -0
  34. data/lib/toy/extensions/object.rb +26 -0
  35. data/lib/toy/extensions/set.rb +23 -0
  36. data/lib/toy/extensions/string.rb +17 -0
  37. data/lib/toy/extensions/time.rb +29 -0
  38. data/lib/toy/identity.rb +26 -0
  39. data/lib/toy/identity/abstract_key_factory.rb +10 -0
  40. data/lib/toy/identity/uuid_key_factory.rb +9 -0
  41. data/lib/toy/identity_map.rb +109 -0
  42. data/lib/toy/index.rb +74 -0
  43. data/lib/toy/indices.rb +56 -0
  44. data/lib/toy/inspect.rb +12 -0
  45. data/lib/toy/list.rb +46 -0
  46. data/lib/toy/lists.rb +37 -0
  47. data/lib/toy/logger.rb +26 -0
  48. data/lib/toy/mass_assignment_security.rb +16 -0
  49. data/lib/toy/persistence.rb +138 -0
  50. data/lib/toy/plugins.rb +23 -0
  51. data/lib/toy/proxies/embedded_list.rb +74 -0
  52. data/lib/toy/proxies/list.rb +97 -0
  53. data/lib/toy/proxies/proxy.rb +59 -0
  54. data/lib/toy/querying.rb +57 -0
  55. data/lib/toy/reference.rb +134 -0
  56. data/lib/toy/references.rb +19 -0
  57. data/lib/toy/serialization.rb +81 -0
  58. data/lib/toy/store.rb +36 -0
  59. data/lib/toy/timestamps.rb +22 -0
  60. data/lib/toy/validations.rb +45 -0
  61. data/lib/toy/version.rb +3 -0
  62. data/lib/toystore.rb +1 -0
  63. data/spec/helper.rb +35 -0
  64. data/spec/spec.opts +3 -0
  65. data/spec/support/constants.rb +41 -0
  66. data/spec/support/identity_map_matcher.rb +20 -0
  67. data/spec/support/name_and_number_key_factory.rb +5 -0
  68. data/spec/toy/attribute_spec.rb +176 -0
  69. data/spec/toy/attributes_spec.rb +394 -0
  70. data/spec/toy/caching_spec.rb +62 -0
  71. data/spec/toy/callbacks_spec.rb +97 -0
  72. data/spec/toy/connection_spec.rb +47 -0
  73. data/spec/toy/dirty_spec.rb +99 -0
  74. data/spec/toy/dolly_spec.rb +76 -0
  75. data/spec/toy/embedded_list_spec.rb +607 -0
  76. data/spec/toy/embedded_lists_spec.rb +172 -0
  77. data/spec/toy/equality_spec.rb +46 -0
  78. data/spec/toy/exceptions_spec.rb +18 -0
  79. data/spec/toy/extensions/array_spec.rb +25 -0
  80. data/spec/toy/extensions/boolean_spec.rb +41 -0
  81. data/spec/toy/extensions/date_spec.rb +48 -0
  82. data/spec/toy/extensions/float_spec.rb +14 -0
  83. data/spec/toy/extensions/hash_spec.rb +21 -0
  84. data/spec/toy/extensions/integer_spec.rb +29 -0
  85. data/spec/toy/extensions/nil_class_spec.rb +14 -0
  86. data/spec/toy/extensions/set_spec.rb +27 -0
  87. data/spec/toy/extensions/string_spec.rb +28 -0
  88. data/spec/toy/extensions/time_spec.rb +94 -0
  89. data/spec/toy/identity/abstract_key_factory_spec.rb +7 -0
  90. data/spec/toy/identity/uuid_key_factory_spec.rb +7 -0
  91. data/spec/toy/identity_map_spec.rb +150 -0
  92. data/spec/toy/identity_spec.rb +52 -0
  93. data/spec/toy/index_spec.rb +230 -0
  94. data/spec/toy/indices_spec.rb +141 -0
  95. data/spec/toy/inspect_spec.rb +15 -0
  96. data/spec/toy/list_spec.rb +576 -0
  97. data/spec/toy/lists_spec.rb +95 -0
  98. data/spec/toy/logger_spec.rb +33 -0
  99. data/spec/toy/mass_assignment_security_spec.rb +116 -0
  100. data/spec/toy/persistence_spec.rb +312 -0
  101. data/spec/toy/plugins_spec.rb +39 -0
  102. data/spec/toy/querying_spec.rb +162 -0
  103. data/spec/toy/reference_spec.rb +400 -0
  104. data/spec/toy/references_spec.rb +86 -0
  105. data/spec/toy/serialization_spec.rb +354 -0
  106. data/spec/toy/store_spec.rb +41 -0
  107. data/spec/toy/timestamps_spec.rb +63 -0
  108. data/spec/toy/validations_spec.rb +171 -0
  109. data/spec/toy_spec.rb +26 -0
  110. data/specs.watchr +52 -0
  111. data/test/lint_test.rb +40 -0
  112. data/toystore.gemspec +24 -0
  113. metadata +290 -0
@@ -0,0 +1,59 @@
1
+ module Toy
2
+ module Proxies
3
+ class Proxy
4
+ include Enumerable
5
+ extend Forwardable
6
+
7
+ def_delegator :@list, :type, :proxy_class
8
+ def_delegator :@list, :key, :proxy_key
9
+ def_delegator :@list, :options, :proxy_options
10
+
11
+ alias :proxy_respond_to? :respond_to?
12
+ alias :proxy_extend :extend
13
+
14
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ }
15
+
16
+ def initialize(list, owner)
17
+ @list, @owner = list, owner
18
+ list.extensions.each { |extension| proxy_extend(extension) }
19
+ end
20
+
21
+ def proxy_owner
22
+ @owner
23
+ end
24
+
25
+ def each
26
+ target.each { |i| yield(i) }
27
+ end
28
+
29
+ def eql?(other)
30
+ target == other
31
+ end
32
+ alias :== :eql?
33
+
34
+ def target
35
+ @target ||= find_target
36
+ end
37
+ alias :to_a :target
38
+
39
+ def assert_type(record)
40
+ unless record.is_a?(proxy_class)
41
+ raise(ArgumentError, "#{proxy_class} expected, but was #{record.class}")
42
+ end
43
+ end
44
+
45
+ def respond_to?(*args)
46
+ proxy_respond_to?(*args) || target.respond_to?(*args)
47
+ end
48
+
49
+ private
50
+ def find_target
51
+ raise('Not Implemented')
52
+ end
53
+
54
+ def method_missing(method, *args, &block)
55
+ target.send(method, *args, &block)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,57 @@
1
+ module Toy
2
+ module Querying
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def get(id)
7
+ key = store_key(id)
8
+
9
+ if has_cache?
10
+ value = cache.read(key)
11
+ log_operation('RTG', cache, key, value)
12
+ end
13
+
14
+ if value.nil?
15
+ value = store.read(key)
16
+ log_operation('GET', store, key, value)
17
+
18
+ if has_cache?
19
+ cache.write(key, value)
20
+ log_operation('RTS', cache, key, value)
21
+ end
22
+ end
23
+
24
+ load(value)
25
+ end
26
+
27
+ def get!(id)
28
+ get(id) || raise(Toy::NotFound.new(id))
29
+ end
30
+
31
+ def get_multi(*ids)
32
+ ids.flatten.map { |id| get(id) }
33
+ end
34
+
35
+ def get_or_new(id)
36
+ get(id) || new(:id => id)
37
+ end
38
+
39
+ def get_or_create(id)
40
+ get(id) || create(:id => id)
41
+ end
42
+
43
+ def key?(id)
44
+ key = store_key(id)
45
+ value = store.key?(key)
46
+ log_operation('KEY', store, key, value)
47
+ value
48
+ end
49
+ alias :has_key? :key?
50
+
51
+ def load(attrs)
52
+ return nil if attrs.nil?
53
+ allocate.initialize_from_database(attrs)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,134 @@
1
+ module Toy
2
+ class Reference
3
+ attr_accessor :model, :name, :options
4
+
5
+ def initialize(model, name, *args)
6
+ @model = model
7
+ @name = name.to_sym
8
+ @options = args.extract_options!
9
+ @type = args.shift
10
+
11
+ model.references[name] = self
12
+ model.attribute(key, String)
13
+ create_accessors
14
+ end
15
+
16
+ def type
17
+ @type ||= name.to_s.classify.constantize
18
+ end
19
+
20
+ def key
21
+ @key ||= :"#{name.to_s.singularize}_id"
22
+ end
23
+
24
+ def instance_variable
25
+ @instance_variable ||= :"@_#{name}"
26
+ end
27
+
28
+ def new_proxy(owner)
29
+ ReferenceProxy.new(self, owner)
30
+ end
31
+
32
+ def eql?(other)
33
+ self.class.eql?(other.class) &&
34
+ model == other.model &&
35
+ name == other.name
36
+ end
37
+ alias :== :eql?
38
+
39
+ class ReferenceProxy
40
+ extend Forwardable
41
+
42
+ def_delegator :@reference, :type, :proxy_class
43
+ def_delegator :@reference, :key, :proxy_key
44
+ alias_method :proxy_respond_to?, :respond_to?
45
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ }
46
+
47
+ def initialize(reference, owner)
48
+ @reference, @owner = reference, owner
49
+ end
50
+
51
+ def proxy_owner
52
+ @owner
53
+ end
54
+
55
+ def target
56
+ return nil if target_id.blank?
57
+ @target ||= proxy_class.get(target_id)
58
+ end
59
+
60
+ def reset
61
+ @target = nil
62
+ end
63
+
64
+ def replace(record)
65
+ if record.nil?
66
+ reset
67
+ self.target_id = nil
68
+ else
69
+ assert_type(record)
70
+ @target = record
71
+ self.target_id = record.id
72
+ end
73
+ end
74
+
75
+ def create(attrs={})
76
+ proxy_class.create(attrs).tap do |record|
77
+ if record.persisted?
78
+ self.target_id = record.id
79
+ proxy_owner.save
80
+ reset
81
+ end
82
+ end
83
+ end
84
+
85
+ def build(attrs={})
86
+ proxy_class.new(attrs).tap do |record|
87
+ self.target_id = record.id
88
+ reset
89
+ end
90
+ end
91
+
92
+ # Does the proxy or its \target respond to +symbol+?
93
+ def respond_to?(*args)
94
+ proxy_respond_to?(*args) || target.respond_to?(*args)
95
+ end
96
+
97
+ private
98
+ def assert_type(record)
99
+ unless record.instance_of?(proxy_class)
100
+ raise(ArgumentError, "#{proxy_class} expected, but was #{record.class}")
101
+ end
102
+ end
103
+
104
+ def target_id
105
+ proxy_owner.send(proxy_key)
106
+ end
107
+
108
+ def target_id=(value)
109
+ proxy_owner.send("#{proxy_key}=", value)
110
+ end
111
+
112
+ def method_missing(method, *args, &block)
113
+ target.send(method, *args, &block)
114
+ end
115
+ end
116
+
117
+ private
118
+ def create_accessors
119
+ model.class_eval """
120
+ def #{name}
121
+ #{instance_variable} ||= self.class.references[:#{name}].new_proxy(self)
122
+ end
123
+
124
+ def #{name}=(record)
125
+ #{name}.replace(record)
126
+ end
127
+
128
+ def #{name}?
129
+ #{name}.present?
130
+ end
131
+ """
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,19 @@
1
+ module Toy
2
+ module References
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def references
7
+ @references ||= {}
8
+ end
9
+
10
+ def reference?(key)
11
+ references.keys.include?(key.to_sym)
12
+ end
13
+
14
+ def reference(name, *args)
15
+ Reference.new(self, name, *args)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,81 @@
1
+ module Toy
2
+ module Serialization
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Serializers::JSON
5
+ include ActiveModel::Serializers::Xml
6
+
7
+ def serializable_attributes
8
+ attributes.keys.sort.map(&:to_sym)
9
+ end
10
+
11
+ def serializable_hash(options = nil)
12
+ hash = {}
13
+ options ||= {}
14
+ options[:only] = Array.wrap(options[:only]).map(&:to_sym)
15
+ options[:except] = Array.wrap(options[:except]).map(&:to_sym)
16
+
17
+ serializable_stuff = serializable_attributes.map(&:to_sym) + self.class.embedded_lists.keys.map(&:to_sym)
18
+
19
+ if options[:only].any?
20
+ serializable_stuff &= options[:only]
21
+ elsif options[:except].any?
22
+ serializable_stuff -= options[:except]
23
+ end
24
+
25
+ serializable_stuff += Array.wrap(options[:methods]).map(&:to_sym).select do |method|
26
+ respond_to?(method)
27
+ end
28
+
29
+ serializable_stuff.each do |name|
30
+ value = send(name)
31
+ hash[name.to_s] = if value.is_a?(Array)
32
+ value.map { |v| v.respond_to?(:serializable_hash) ? v.serializable_hash : v }
33
+ elsif value.respond_to?(:serializable_hash)
34
+ value.serializable_hash
35
+ else
36
+ value
37
+ end
38
+ end
39
+
40
+ serializable_add_includes(options) do |association, records, opts|
41
+ hash[association] = records.is_a?(Enumerable) ?
42
+ records.map { |r| r.serializable_hash(opts) } :
43
+ records.serializable_hash(opts)
44
+ end
45
+
46
+ hash
47
+ end
48
+
49
+ private
50
+ # Add associations specified via the <tt>:includes</tt> option.
51
+ # Expects a block that takes as arguments:
52
+ # +association+ - name of the association
53
+ # +records+ - the association record(s) to be serialized
54
+ # +opts+ - options for the association records
55
+ def serializable_add_includes(options = {})
56
+ return unless include_associations = options.delete(:include)
57
+
58
+ base_only_or_except = { :except => options[:except],
59
+ :only => options[:only] }
60
+
61
+ include_has_options = include_associations.is_a?(Hash)
62
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
63
+
64
+ for association in associations
65
+ records = if self.class.list?(association)
66
+ send(association).to_a
67
+ elsif self.class.reference?(association) || self.class.parent_reference?(association)
68
+ send(association)
69
+ end
70
+
71
+ unless records.nil?
72
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
73
+ opts = options.merge(association_options)
74
+ yield(association, records, opts)
75
+ end
76
+ end
77
+
78
+ options[:include] = include_associations
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,36 @@
1
+ module Toy
2
+ module Store
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ extend ActiveModel::Naming
7
+ include ActiveModel::Conversion
8
+ include Attributes
9
+ include Identity
10
+ include Persistence
11
+ include MassAssignmentSecurity
12
+ include Plugins
13
+ include Dolly
14
+ include Dirty
15
+ include Equality
16
+ include Inspect
17
+ include Querying
18
+
19
+ include Callbacks
20
+ include Validations
21
+ include Serialization
22
+ include Timestamps
23
+
24
+ include Lists
25
+ include EmbeddedLists
26
+ include References
27
+ include Indices
28
+ include Logger
29
+
30
+ include IdentityMap
31
+ include Caching
32
+ end
33
+ end
34
+
35
+ extend Connection
36
+ end
@@ -0,0 +1,22 @@
1
+ module Toy
2
+ module Timestamps
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def timestamps
7
+ attribute(:created_at, Time)
8
+ attribute(:updated_at, Time)
9
+
10
+ before_create do |record|
11
+ now = Time.now
12
+ record.created_at = now unless created_at?
13
+ record.updated_at = now
14
+ end
15
+
16
+ before_update do |record|
17
+ record.updated_at = Time.now
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ module Toy
2
+ module Validations
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Validations
5
+
6
+ included do
7
+ extend ActiveModel::Callbacks
8
+ define_model_callbacks :validation
9
+ end
10
+
11
+ module ClassMethods
12
+ def validates_embedded(*names)
13
+ validates_each(*names) do |record, name, value|
14
+ invalid = value.compact.select { |o| !o.valid? }
15
+ if invalid.any?
16
+ record.errors.add(name, 'is invalid')
17
+ logger.debug("ToyStore IEM")
18
+ invalid.each do |o|
19
+ logger.debug(" #{o.attributes.inspect} - #{o.errors.full_messages.inspect}")
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def create!(attrs={})
26
+ new(attrs).tap { |doc| doc.save! }
27
+ end
28
+ end
29
+
30
+ module InstanceMethods
31
+ def valid?
32
+ run_callbacks(:validation) { super }
33
+ end
34
+
35
+ def save(options={})
36
+ options.assert_valid_keys(:validate)
37
+ !options.fetch(:validate, true) || valid? ? super : false
38
+ end
39
+
40
+ def save!
41
+ save || raise(RecordInvalidError.new(self))
42
+ end
43
+ end
44
+ end
45
+ end