toystore 0.5

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