arunthampi-friendly 0.5.1

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 (137) hide show
  1. data/.document +2 -0
  2. data/.gitignore +26 -0
  3. data/APACHE-LICENSE +202 -0
  4. data/CHANGELOG.md +28 -0
  5. data/CONTRIBUTORS.md +7 -0
  6. data/LICENSE +20 -0
  7. data/README.md +288 -0
  8. data/Rakefile +68 -0
  9. data/TODO.md +5 -0
  10. data/VERSION +1 -0
  11. data/arunthampi-friendly.gemspec +241 -0
  12. data/examples/friendly.yml +7 -0
  13. data/friendly.gemspec +240 -0
  14. data/lib/friendly.rb +53 -0
  15. data/lib/friendly/associations.rb +7 -0
  16. data/lib/friendly/associations/association.rb +34 -0
  17. data/lib/friendly/associations/set.rb +37 -0
  18. data/lib/friendly/attribute.rb +98 -0
  19. data/lib/friendly/boolean.rb +10 -0
  20. data/lib/friendly/cache.rb +24 -0
  21. data/lib/friendly/cache/by_id.rb +33 -0
  22. data/lib/friendly/data_store.rb +73 -0
  23. data/lib/friendly/document.rb +70 -0
  24. data/lib/friendly/document/associations.rb +50 -0
  25. data/lib/friendly/document/attributes.rb +114 -0
  26. data/lib/friendly/document/convenience.rb +41 -0
  27. data/lib/friendly/document/mixin.rb +15 -0
  28. data/lib/friendly/document/scoping.rb +66 -0
  29. data/lib/friendly/document/storage.rb +63 -0
  30. data/lib/friendly/document_table.rb +56 -0
  31. data/lib/friendly/index.rb +73 -0
  32. data/lib/friendly/indexer.rb +50 -0
  33. data/lib/friendly/memcached.rb +48 -0
  34. data/lib/friendly/newrelic.rb +6 -0
  35. data/lib/friendly/query.rb +42 -0
  36. data/lib/friendly/scope.rb +100 -0
  37. data/lib/friendly/scope_proxy.rb +43 -0
  38. data/lib/friendly/sequel_monkey_patches.rb +34 -0
  39. data/lib/friendly/storage.rb +31 -0
  40. data/lib/friendly/storage_factory.rb +24 -0
  41. data/lib/friendly/storage_proxy.rb +111 -0
  42. data/lib/friendly/table.rb +15 -0
  43. data/lib/friendly/table_creator.rb +50 -0
  44. data/lib/friendly/time.rb +14 -0
  45. data/lib/friendly/translator.rb +33 -0
  46. data/lib/friendly/uuid.rb +148 -0
  47. data/lib/tasks/friendly.rake +7 -0
  48. data/rails/init.rb +3 -0
  49. data/spec/config.yml.example +7 -0
  50. data/spec/fakes/data_store_fake.rb +29 -0
  51. data/spec/fakes/database_fake.rb +12 -0
  52. data/spec/fakes/dataset_fake.rb +28 -0
  53. data/spec/fakes/document.rb +18 -0
  54. data/spec/fakes/serializer_fake.rb +12 -0
  55. data/spec/fakes/time_fake.rb +12 -0
  56. data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
  57. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  58. data/spec/integration/batch_insertion_spec.rb +29 -0
  59. data/spec/integration/convenience_api_spec.rb +25 -0
  60. data/spec/integration/count_spec.rb +12 -0
  61. data/spec/integration/default_value_spec.rb +30 -0
  62. data/spec/integration/dirty_tracking_spec.rb +43 -0
  63. data/spec/integration/find_via_cache_spec.rb +101 -0
  64. data/spec/integration/finder_spec.rb +71 -0
  65. data/spec/integration/has_many_spec.rb +18 -0
  66. data/spec/integration/index_spec.rb +57 -0
  67. data/spec/integration/named_scope_spec.rb +34 -0
  68. data/spec/integration/offline_indexing_spec.rb +53 -0
  69. data/spec/integration/pagination_spec.rb +63 -0
  70. data/spec/integration/scope_chaining_spec.rb +22 -0
  71. data/spec/integration/table_creator_spec.rb +69 -0
  72. data/spec/integration/write_through_cache_spec.rb +53 -0
  73. data/spec/spec.opts +1 -0
  74. data/spec/spec_helper.rb +105 -0
  75. data/spec/unit/associations/association_spec.rb +57 -0
  76. data/spec/unit/associations/set_spec.rb +43 -0
  77. data/spec/unit/attribute_spec.rb +125 -0
  78. data/spec/unit/cache_by_id_spec.rb +102 -0
  79. data/spec/unit/cache_spec.rb +21 -0
  80. data/spec/unit/data_store_spec.rb +201 -0
  81. data/spec/unit/document/attributes_spec.rb +130 -0
  82. data/spec/unit/document_spec.rb +318 -0
  83. data/spec/unit/document_table_spec.rb +126 -0
  84. data/spec/unit/friendly_spec.rb +25 -0
  85. data/spec/unit/index_spec.rb +196 -0
  86. data/spec/unit/memcached_spec.rb +114 -0
  87. data/spec/unit/query_spec.rb +104 -0
  88. data/spec/unit/scope_proxy_spec.rb +44 -0
  89. data/spec/unit/scope_spec.rb +113 -0
  90. data/spec/unit/storage_factory_spec.rb +59 -0
  91. data/spec/unit/storage_proxy_spec.rb +244 -0
  92. data/spec/unit/translator_spec.rb +91 -0
  93. data/website/index.html +210 -0
  94. data/website/scripts/clipboard.swf +0 -0
  95. data/website/scripts/shBrushAS3.js +61 -0
  96. data/website/scripts/shBrushBash.js +66 -0
  97. data/website/scripts/shBrushCSharp.js +67 -0
  98. data/website/scripts/shBrushColdFusion.js +102 -0
  99. data/website/scripts/shBrushCpp.js +99 -0
  100. data/website/scripts/shBrushCss.js +93 -0
  101. data/website/scripts/shBrushDelphi.js +57 -0
  102. data/website/scripts/shBrushDiff.js +43 -0
  103. data/website/scripts/shBrushErlang.js +54 -0
  104. data/website/scripts/shBrushGroovy.js +69 -0
  105. data/website/scripts/shBrushJScript.js +52 -0
  106. data/website/scripts/shBrushJava.js +59 -0
  107. data/website/scripts/shBrushJavaFX.js +60 -0
  108. data/website/scripts/shBrushPerl.js +74 -0
  109. data/website/scripts/shBrushPhp.js +91 -0
  110. data/website/scripts/shBrushPlain.js +35 -0
  111. data/website/scripts/shBrushPowerShell.js +76 -0
  112. data/website/scripts/shBrushPython.js +66 -0
  113. data/website/scripts/shBrushRuby.js +57 -0
  114. data/website/scripts/shBrushScala.js +53 -0
  115. data/website/scripts/shBrushSql.js +68 -0
  116. data/website/scripts/shBrushVb.js +58 -0
  117. data/website/scripts/shBrushXml.js +71 -0
  118. data/website/scripts/shCore.js +30 -0
  119. data/website/scripts/shLegacy.js +30 -0
  120. data/website/styles/friendly.css +103 -0
  121. data/website/styles/help.png +0 -0
  122. data/website/styles/ie.css +35 -0
  123. data/website/styles/magnifier.png +0 -0
  124. data/website/styles/page_white_code.png +0 -0
  125. data/website/styles/page_white_copy.png +0 -0
  126. data/website/styles/print.css +29 -0
  127. data/website/styles/printer.png +0 -0
  128. data/website/styles/screen.css +257 -0
  129. data/website/styles/shCore.css +330 -0
  130. data/website/styles/shThemeDefault.css +173 -0
  131. data/website/styles/shThemeDjango.css +176 -0
  132. data/website/styles/shThemeEclipse.css +190 -0
  133. data/website/styles/shThemeEmacs.css +175 -0
  134. data/website/styles/shThemeFadeToGrey.css +177 -0
  135. data/website/styles/shThemeMidnight.css +175 -0
  136. data/website/styles/shThemeRDark.css +175 -0
  137. metadata +311 -0
@@ -0,0 +1,53 @@
1
+ require 'friendly/associations'
2
+ require 'friendly/attribute'
3
+ require 'friendly/boolean'
4
+ require 'friendly/cache'
5
+ require 'friendly/cache/by_id'
6
+ require 'friendly/data_store'
7
+ require 'friendly/document'
8
+ require 'friendly/document_table'
9
+ require 'friendly/index'
10
+ require 'friendly/indexer'
11
+ require 'friendly/memcached'
12
+ require 'friendly/query'
13
+ require 'friendly/sequel_monkey_patches'
14
+ require 'friendly/scope'
15
+ require 'friendly/scope_proxy'
16
+ require 'friendly/storage_factory'
17
+ require 'friendly/storage_proxy'
18
+ require 'friendly/translator'
19
+ require 'friendly/uuid'
20
+
21
+ require 'json/ext'
22
+ require 'will_paginate/collection'
23
+
24
+ module Friendly
25
+ class << self
26
+ attr_accessor :datastore, :db, :cache
27
+
28
+ def configure(config)
29
+ self.db = Sequel.connect(config)
30
+ self.datastore = DataStore.new(db)
31
+ end
32
+
33
+ def batch
34
+ begin
35
+ datastore.start_batch
36
+ yield
37
+ datastore.flush_batch
38
+ ensure
39
+ datastore.reset_batch
40
+ end
41
+ end
42
+
43
+ def create_tables!
44
+ Document.create_tables!
45
+ end
46
+ end
47
+
48
+ class Error < RuntimeError; end
49
+ class RecordNotFound < Error; end
50
+ class MissingIndex < Error; end
51
+ class NoConverterExists < Friendly::Error; end
52
+ class NotSupported < Friendly::Error; end
53
+ end
@@ -0,0 +1,7 @@
1
+ require 'friendly/associations/association'
2
+ require 'friendly/associations/set'
3
+
4
+ module Friendly
5
+ module Associations
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ module Friendly
2
+ module Associations
3
+ class Association
4
+ attr_reader :owner_klass, :name
5
+
6
+ def initialize(owner_klass, name, options = {})
7
+ @owner_klass = owner_klass
8
+ @name = name
9
+ @class_name = options[:class_name]
10
+ @foreign_key = options[:foreign_key]
11
+ end
12
+
13
+ def klass
14
+ @klass ||= class_name.constantize
15
+ end
16
+
17
+ def foreign_key
18
+ @foreign_key ||= [owner_klass_name, :id].join("_").to_sym
19
+ end
20
+
21
+ def class_name
22
+ @class_name ||= name.to_s.classify
23
+ end
24
+
25
+ def owner_klass_name
26
+ owner_klass.name.to_s.underscore.singularize
27
+ end
28
+
29
+ def scope(document)
30
+ klass.scope(foreign_key => document.id)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ module Friendly
2
+ module Associations
3
+ class Set
4
+ attr_reader :klass, :association_klass, :associations
5
+
6
+ def initialize(klass, association_klass = Association)
7
+ @klass = klass
8
+ @association_klass = association_klass
9
+ @associations = {}
10
+ end
11
+
12
+ def add(name, options = {})
13
+ associations[name] = association_klass.new(klass, name, options)
14
+ add_association_accessor(name)
15
+ end
16
+
17
+ def get_scope(name, document)
18
+ get(name).scope(document)
19
+ end
20
+
21
+ def get(name)
22
+ associations[name]
23
+ end
24
+
25
+ protected
26
+ def add_association_accessor(name)
27
+ klass.class_eval do
28
+ eval <<-__END__
29
+ def #{name}
30
+ self.class.association_set.get_scope(:#{name}, self)
31
+ end
32
+ __END__
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,98 @@
1
+ module Friendly
2
+ class Attribute
3
+ class << self
4
+ def register_type(type, sql_type, &block)
5
+ sql_types[type.name] = sql_type
6
+ converters[type] = block
7
+ end
8
+
9
+ def deregister_type(type)
10
+ sql_types.delete(type.name)
11
+ converters.delete(type)
12
+ end
13
+
14
+ def sql_type(type)
15
+ sql_types[type.name]
16
+ end
17
+
18
+ def sql_types
19
+ @sql_types ||= {}
20
+ end
21
+
22
+ def converters
23
+ @converters ||= {}
24
+ end
25
+
26
+ def custom_type?(klass)
27
+ !sql_type(klass).nil?
28
+ end
29
+ end
30
+
31
+ converters[Integer] = lambda { |s| s.to_i }
32
+ converters[String] = lambda { |s| s.to_s }
33
+
34
+ attr_reader :klass, :name, :type, :default_value
35
+
36
+ def initialize(klass, name, type, options = {})
37
+ @klass = klass
38
+ @name = name
39
+ @type = type
40
+ @default_value = options[:default]
41
+ build_accessors
42
+ end
43
+
44
+ def typecast(value)
45
+ !type || value.is_a?(type) ? value : convert(value)
46
+ end
47
+
48
+ def convert(value)
49
+ assert_converter_exists(value)
50
+ converters[type].call(value)
51
+ end
52
+
53
+ def default
54
+ if !default_value.nil?
55
+ default_value
56
+ elsif type.respond_to?(:new)
57
+ type.new
58
+ else
59
+ nil
60
+ end
61
+ end
62
+
63
+ def assign_default_value(document)
64
+ document.send(:"#{name}=", default)
65
+ end
66
+
67
+ protected
68
+ def build_accessors
69
+ n = name
70
+ klass.class_eval do
71
+ attr_reader n, :"#{n}_was"
72
+
73
+ eval <<-__END__
74
+ def #{n}=(value)
75
+ will_change(:#{n})
76
+ @#{n} = self.class.attributes[:#{n}].typecast(value)
77
+ end
78
+
79
+ def #{n}_changed?
80
+ attribute_changed?(:#{n})
81
+ end
82
+ __END__
83
+ end
84
+ end
85
+
86
+ def assert_converter_exists(value)
87
+ unless converters.has_key?(type)
88
+ msg = "Can't convert #{value} to #{type}.
89
+ Add a custom converter to Friendly::Attribute::CONVERTERS."
90
+ raise NoConverterExists, msg
91
+ end
92
+ end
93
+
94
+ def converters
95
+ self.class.converters
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,10 @@
1
+ require 'friendly/attribute'
2
+
3
+ module Friendly
4
+ # placeholder that represents a boolean
5
+ # since ruby has no boolean superclass
6
+ module Boolean
7
+ end
8
+ end
9
+
10
+ Friendly::Attribute.register_type(Friendly::Boolean, 'boolean') { |s| s }
@@ -0,0 +1,24 @@
1
+ require 'friendly/storage'
2
+
3
+ module Friendly
4
+ class Cache < Storage
5
+ class << self
6
+ def cache_for(klass, fields, options)
7
+ unless fields == [:id]
8
+ raise NotSupported, "Caching is only possible by id at the moment."
9
+ end
10
+
11
+ ByID.new(klass, fields, options)
12
+ end
13
+ end
14
+
15
+ attr_reader :klass, :fields, :cache, :version
16
+
17
+ def initialize(klass, fields, options = {}, cache = Friendly.cache)
18
+ @klass = klass
19
+ @fields = fields
20
+ @cache = cache
21
+ @version = options[:version] || 0
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ module Friendly
2
+ class Cache
3
+ class ByID < Cache
4
+ def store(document)
5
+ cache.set(cache_key(document.id), document)
6
+ end
7
+ alias_method :create, :store
8
+ alias_method :update, :store
9
+
10
+ def destroy(document)
11
+ cache.delete(cache_key(document.id))
12
+ end
13
+
14
+ def first(query, &block)
15
+ cache.get(cache_key(query.conditions[:id]), &block)
16
+ end
17
+
18
+ def all(query, &block)
19
+ keys = query.conditions[:id].map { |k| cache_key(k) }
20
+ cache.multiget(keys, &block).values
21
+ end
22
+
23
+ def satisfies?(query)
24
+ query.conditions.keys == [:id]
25
+ end
26
+
27
+ protected
28
+ def cache_key(id)
29
+ [klass.name, version, id.to_guid].join("/")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,73 @@
1
+ module Friendly
2
+ class DataStore
3
+ attr_reader :database
4
+
5
+ def initialize(database)
6
+ @database = database
7
+ end
8
+
9
+ def insert(persistable, attributes)
10
+ batch? ? batch_insert(persistable, attributes) :
11
+ immediate_insert(persistable, attributes)
12
+ end
13
+
14
+ def all(persistable, query)
15
+ filtered = dataset(persistable)
16
+ filtered = filtered.where(query.conditions) unless query.conditions.empty?
17
+ if query.limit || query.offset
18
+ filtered = filtered.limit(query.limit, query.offset)
19
+ end
20
+ filtered = filtered.order(query.order) if query.order
21
+ filtered.map
22
+ end
23
+
24
+ def first(persistable, query)
25
+ dataset(persistable).first(query.conditions)
26
+ end
27
+
28
+ def update(persistable, id, attributes)
29
+ dataset(persistable).where(:id => id).update(attributes)
30
+ end
31
+
32
+ def delete(persistable, id)
33
+ dataset(persistable).where(:id => id).delete
34
+ end
35
+
36
+ def count(persistable, query)
37
+ dataset(persistable).where(query.conditions).count
38
+ end
39
+
40
+ def start_batch
41
+ Thread.current[:friendly_batch] = Hash.new { |h, k| h[k] = [] }
42
+ end
43
+
44
+ def reset_batch
45
+ Thread.current[:friendly_batch] = nil
46
+ end
47
+
48
+ def flush_batch
49
+ batch = Thread.current[:friendly_batch]
50
+ batch.keys.each do |k|
51
+ database.from(k).multi_insert(batch[k], :commit_every => 1000)
52
+ end
53
+ reset_batch
54
+ end
55
+
56
+ protected
57
+ def dataset(persistable)
58
+ database.from(persistable.table_name)
59
+ end
60
+
61
+ def immediate_insert(persistable, attributes)
62
+ dataset(persistable).insert(attributes)
63
+ end
64
+
65
+ def batch_insert(persistable, attributes)
66
+ Thread.current[:friendly_batch][persistable.table_name] << attributes
67
+ end
68
+
69
+ def batch?
70
+ Thread.current[:friendly_batch]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,70 @@
1
+ require 'active_support/inflector'
2
+ require 'friendly/document/associations'
3
+ require 'friendly/document/attributes'
4
+ require 'friendly/document/convenience'
5
+ require 'friendly/document/scoping'
6
+ require 'friendly/document/storage'
7
+
8
+ module Friendly
9
+ module Document
10
+ class << self
11
+ attr_writer :documents
12
+
13
+ def included(klass)
14
+ documents << klass
15
+ klass.class_eval do
16
+ extend ClassMethods
17
+ attribute :id, UUID
18
+ attribute :created_at, Time
19
+ attribute :updated_at, Time
20
+ end
21
+ end
22
+
23
+ def documents
24
+ @documents ||= []
25
+ end
26
+
27
+ def create_tables!
28
+ documents.each { |d| d.create_tables! }
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ attr_writer :table_name
34
+
35
+ def table_name
36
+ @table_name ||= name.pluralize.underscore
37
+ end
38
+ end
39
+
40
+ include Associations
41
+ include Convenience
42
+ include Scoping
43
+ include Storage
44
+ include Attributes
45
+
46
+ def table_name
47
+ self.class.table_name
48
+ end
49
+
50
+ def new_record?
51
+ new_record
52
+ end
53
+
54
+ def new_record
55
+ @new_record = true if @new_record.nil?
56
+ @new_record
57
+ end
58
+
59
+ def new_record=(value)
60
+ @new_record = value
61
+ end
62
+
63
+ def ==(comparison_object)
64
+ comparison_object.equal?(self) ||
65
+ (comparison_object.is_a?(self.class) &&
66
+ !comparison_object.new_record? &&
67
+ comparison_object.id == id)
68
+ end
69
+ end
70
+ end