fastaccess 0.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 (54) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +137 -0
  3. data/Rakefile +38 -0
  4. data/lib/fastaccess.rb +13 -0
  5. data/lib/fastaccess/acts_with_fastaccess_on.rb +54 -0
  6. data/lib/fastaccess/fastaccess.rb +204 -0
  7. data/lib/fastaccess/mixins.rb +13 -0
  8. data/lib/fastaccess/version.rb +4 -0
  9. data/lib/generators/fastaccess/initialize_generator.rb +12 -0
  10. data/lib/generators/fastaccess/templates/initializer.rb +3 -0
  11. data/lib/tasks/fastaccess_tasks.rake +4 -0
  12. data/test/dummy/README.rdoc +261 -0
  13. data/test/dummy/Rakefile +7 -0
  14. data/test/dummy/app/assets/javascripts/application.js +15 -0
  15. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  16. data/test/dummy/app/controllers/application_controller.rb +3 -0
  17. data/test/dummy/app/helpers/application_helper.rb +2 -0
  18. data/test/dummy/app/models/simple_string.rb +42 -0
  19. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  20. data/test/dummy/config.ru +4 -0
  21. data/test/dummy/config/application.rb +59 -0
  22. data/test/dummy/config/boot.rb +10 -0
  23. data/test/dummy/config/database.yml +25 -0
  24. data/test/dummy/config/environment.rb +5 -0
  25. data/test/dummy/config/environments/development.rb +37 -0
  26. data/test/dummy/config/environments/production.rb +67 -0
  27. data/test/dummy/config/environments/test.rb +37 -0
  28. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  29. data/test/dummy/config/initializers/fastaccess.rb +3 -0
  30. data/test/dummy/config/initializers/inflections.rb +15 -0
  31. data/test/dummy/config/initializers/mime_types.rb +5 -0
  32. data/test/dummy/config/initializers/redis.rb +1 -0
  33. data/test/dummy/config/initializers/secret_token.rb +7 -0
  34. data/test/dummy/config/initializers/session_store.rb +8 -0
  35. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  36. data/test/dummy/config/locales/en.yml +5 -0
  37. data/test/dummy/config/routes.rb +58 -0
  38. data/test/dummy/db/development.sqlite3 +0 -0
  39. data/test/dummy/db/migrate/20130123183844_create_simple_strings.rb +8 -0
  40. data/test/dummy/db/migrate/20130124180635_add_some_string_to_simple_string.rb +5 -0
  41. data/test/dummy/db/schema.rb +22 -0
  42. data/test/dummy/db/test.sqlite3 +0 -0
  43. data/test/dummy/log/development.log +58 -0
  44. data/test/dummy/log/test.log +1575 -0
  45. data/test/dummy/public/404.html +26 -0
  46. data/test/dummy/public/422.html +26 -0
  47. data/test/dummy/public/500.html +25 -0
  48. data/test/dummy/public/favicon.ico +0 -0
  49. data/test/dummy/script/rails +6 -0
  50. data/test/dummy/test/fixtures/simple_strings.yml +11 -0
  51. data/test/dummy/test/unit/simple_string_test.rb +7 -0
  52. data/test/fastaccess_test.rb +76 -0
  53. data/test/test_helper.rb +15 -0
  54. metadata +174 -0
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,137 @@
1
+ # fastaccess - redisfast access on generated content
2
+
3
+ Many web applications which provide textual content to the user
4
+ have much more reads than writes. This can mean, that on
5
+ every 10k reads there is one write (see some popular
6
+ blogs or newspages).
7
+
8
+ Often this content is generated, because the input source
9
+ is some markup format, like markdown. This means, that
10
+ there is always the need to generate this content into
11
+ html.
12
+
13
+ And this is why fastaccess exists.
14
+ It modifies any given method, which generates content,
15
+ and stores it in a [redis][1] database.
16
+ If this content solely depends on a model attribute,
17
+ for example like the *body* in a blog post, it will
18
+ be auto updated, if the underlying model attribute
19
+ changes. Otherwise you can trigger the update manually.
20
+
21
+ Now lets see how this works:
22
+
23
+ ## Using fastaccess
24
+
25
+ First, of course, you'll need to include this gem in your
26
+ projects *Gemfile*.
27
+
28
+ Since this gem utilizes redis, make sure that this is installed
29
+ and generate the default initializer with
30
+
31
+ rails generate fastaccess:initialize
32
+
33
+ This will create a file in *config/initializers/fastaccess.rb* which
34
+ will create an instance of a redis database-connection. If you already
35
+ have one of those, feel free to replace the new instance with your version.
36
+
37
+ Now, in your project you will have to use the gem-provided
38
+ `acts_with_fastaccess_on` method to mark certain methods
39
+ as registered with fastaccess for caching.
40
+
41
+ ```ruby
42
+ class Post < ActiveRecord::Base
43
+ attr_accessible :title, :body
44
+ acts_with_fastaccess_on :markdown_body
45
+
46
+ def markdown_body
47
+ markdown(self.body)
48
+ end
49
+ end
50
+ ```
51
+
52
+ In this example a Model named *Post* is defined which has title and
53
+ body attributes. The `markdown_body` method converts the markdown-body to
54
+ html. Since this is needed often, it cached via redis.
55
+
56
+ The previous example utilizes a method, which operates on one of the models
57
+ attributes. So the redis-content will be updated if the pertaining Model
58
+ instance is updated. But what if you have a method which uses more input
59
+ than just specific attributes, which will update the models timestamp?
60
+
61
+ For this case there is an explicit update method, which allows you
62
+ to trigger an update.
63
+
64
+ ```ruby
65
+ # app/models/post.rb
66
+ class Post < ActiveRecord::Base
67
+ attr_accessible :title, :body, :tags
68
+ has_many :tags
69
+ acts_with_fastaccess_on :tag_list
70
+ include Fastaccess::Mixins
71
+
72
+ def tag_list
73
+ self.tags.map(&name)
74
+ end
75
+ end
76
+
77
+ # app/controllers/posts_controller.rb
78
+ class PostsController < ApplicationController
79
+ def update
80
+ @post = Post.find_by_id(params[:id])
81
+ @post.update_on :tag_list
82
+ respond_to do |format|
83
+ if @post.update_attributes(params[:post])
84
+ format.html { redirect_to @post, notice: 'Post was successfully updated.' }
85
+ format.json { head :no_content }
86
+ else
87
+ format.html { render action: "edit" }
88
+ format.json { render json: @post.errors, status: :unprocessable_entity }
89
+ end
90
+ end
91
+ end
92
+ ```
93
+
94
+ If you don't want to *pollute* your model with these mixin-methods you
95
+ can also call
96
+
97
+ ```ruby
98
+ Fastaccess::Fastaccess.update_content @post, :on => :tag_list, :arguments => []
99
+ ```
100
+
101
+ ## Features
102
+
103
+ ### planned features
104
+
105
+ - ~~disable auto-update via option setting (planned for *0.0.2*)~~ *implemented*
106
+ - more update flexibility
107
+ - e.g. custom update-constraints instead of calling `update_content` manually
108
+
109
+ ## License
110
+
111
+ ([The MIT License][mit])
112
+
113
+ Copyright © 2013:
114
+
115
+ - [Tim Reddehase][1]
116
+
117
+ Permission is hereby granted, free of charge, to any person obtaining
118
+ a copy of this software and associated documentation files (the
119
+ 'Software'), to deal in the Software without restriction, including
120
+ without limitation the rights to use, copy, modify, merge, publish,
121
+ distribute, sublicense, and/or sell copies of the Software, and to
122
+ permit persons to whom the Software is furnished to do so, subject to
123
+ the following conditions:
124
+
125
+ The above copyright notice and this permission notice shall be
126
+ included in all copies or substantial portions of the Software.
127
+
128
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
129
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
130
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
131
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
132
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
133
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
134
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
135
+
136
+ [mit]: http://opensource.org/licenses/MIT
137
+ [1]: http://rightsrestricted.com
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Fastaccess'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,13 @@
1
+ require 'redis'
2
+ require 'fastaccess/fastaccess.rb'
3
+ require 'fastaccess/mixins.rb'
4
+ require 'fastaccess/acts_with_fastaccess_on'
5
+
6
+ module Fastaccess
7
+
8
+ # convenience method to setup fastaccess
9
+ def self.setup(&block)
10
+ Fastaccess.setup &block
11
+ end
12
+
13
+ end
@@ -0,0 +1,54 @@
1
+ module Fastaccess
2
+ module ActsWithFastaccessOn
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ end
7
+
8
+ module ClassMethods
9
+ # registers the pertaining method as one, which return value
10
+ # is provided through redis.
11
+ # This is for fast access of otherwise generated content
12
+ # with a high reading/writing ratio.
13
+ #
14
+ # @param [Symbol] method_name denoting the pertaining method
15
+ # this method shouldn't be defined beforehand.
16
+ # @param [Hash] options is basic options hash.
17
+ # currently has no effect on execution.
18
+ def acts_with_fastaccess_on(method_name, options = {})
19
+ Fastaccess.register_on self, method_name, options
20
+ # options = Fastaccess.merge_defaults(options)
21
+ define_singleton_method :method_added do |on_method|
22
+ if Fastaccess.registered? self, on_method
23
+ method = on_method
24
+ alias_name = Fastaccess.alias_for method
25
+ if !method_defined?(alias_name)
26
+ alias_method alias_name, method
27
+ define_method method do |*args|
28
+ fastaccess_id = Fastaccess.id_for(self)
29
+ redis_id = "#{method}_#{fastaccess_id}"
30
+ opts = Fastaccess.options_for(self, method)
31
+ content_current = opts[:auto_update] ? Fastaccess.update_check(self) : true
32
+ if Fastaccess.redis.exists(redis_id) && content_current
33
+ response = Fastaccess.redis.get(redis_id)
34
+ begin
35
+ return JSON.parse response
36
+ rescue JSON::ParserError
37
+ return response
38
+ end
39
+ else
40
+ response = method(alias_name).call(*args)
41
+ Fastaccess.update_info self
42
+ Fastaccess.redis.set(redis_id, (response.is_a? String) ? response : response.to_json)
43
+ return response
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ ActiveRecord::Base.send :include, Fastaccess::ActsWithFastaccessOn
@@ -0,0 +1,204 @@
1
+ require 'set'
2
+
3
+ module Fastaccess
4
+ # This class contains the most
5
+ # relevant helpers methods, which
6
+ # doesn't need to be located elsewhere.
7
+ # (e.g. like mixins)
8
+ class Fastaccess
9
+
10
+ # the default options for the
11
+ # acts_with_fastaccess_on method.
12
+ ACTS_OPTIONS_DEFAULTS = {
13
+ :auto_update => true
14
+ }
15
+
16
+ @@fastaccess_on = Hash.new Set.new
17
+ @@last_updated = Hash.new
18
+ @@registered_options = Hash.new ACTS_OPTIONS_DEFAULTS
19
+
20
+ # accessor for the actual registration hash.
21
+ cattr_accessor :fastaccess_on
22
+
23
+ # hash for monitoring updates on registered objects.
24
+ cattr_accessor :last_updated
25
+
26
+ # hash for options of registered methods
27
+ cattr_accessor :registered_options
28
+
29
+ # registers a method, defined on a certain class,
30
+ # as being handled by fastaccess.
31
+ # @param [Class] class_name is the actual Class.
32
+ # @param [Symbol] method_name is the symbol
33
+ # denoting the actual method
34
+ def self.register_on(class_name, method_name, options={})
35
+ self.fastaccess_on[class_name] << method_name
36
+ id = options_id_for(class_name, method_name)
37
+ self.registered_options[id] = self.registered_options[id].merge(options)
38
+ end
39
+
40
+ # inquires if a certain method, which is
41
+ # defined on a given class, is registered
42
+ # with fastaccess.
43
+ # @param [Class] class_name is the actual Class.
44
+ # @param [Symbol] method_name is the symbol
45
+ # denoting the actual method
46
+ def self.registered?(class_name, method_name)
47
+ self.fastaccess_on[class_name].include? method_name
48
+ end
49
+
50
+ # gets the options for a class_name, method_name
51
+ # pair
52
+ # @param [Class] class_name is the actual Class.
53
+ # @param [Symbol] method_name is the symbol
54
+ # denoting the actual method
55
+ # @return [Hash] the options for the pair
56
+ def self.options_for(class_name, method_name)
57
+ id = options_id_for(class_name, method_name)
58
+ self.registered_options[id]
59
+ end
60
+
61
+ # checks if a class_instance seems to be
62
+ # up to date according to updated_at timestamp.
63
+ # This only works if the registered method
64
+ # is actually dependent (and only dependent)
65
+ # on model attributes.
66
+ # @param [Object] class_instance any Object,
67
+ # preferably a decendent of
68
+ # an actual Rails Model.
69
+ # @return [Boolean] is true if everything is up to date.
70
+ def self.update_check(class_instance)
71
+ id = self.id_for(class_instance)
72
+ return true if self.last_updated[id] == false
73
+ class_instance.updated_at == self.last_updated[id]
74
+ end
75
+
76
+ # updates the timestamp in the redis
77
+ # database with the one from class_instance.
78
+ # usually called after there was new content
79
+ # pushed into redis (or content was updated)
80
+ # @param [Object] class_instance any Object,
81
+ # preferably a decendent of
82
+ # an actual Rails Model.
83
+ def self.update_info(class_instance)
84
+ id = self.id_for(class_instance)
85
+ unless self.last_updated[id] == false
86
+ self.last_updated[id] = class_instance.updated_at
87
+ end
88
+ end
89
+
90
+ # creates a fastaccess id for a class_instance
91
+ # @param [Object] class_instance any Object,
92
+ # preferably a decendent of
93
+ # an actual Rails Model.
94
+ # @return [String] the identifier
95
+ def self.id_for(class_instance)
96
+ "#{class_instance.class}-#{class_instance.id}"
97
+ end
98
+
99
+ # creates the id for the registered_options hash
100
+ # @param [Class] class_name a class singleton object
101
+ # @param [Symbol] method_name is the identifying symbol of a method
102
+ # @return [String] the identifier
103
+ def self.options_id_for(class_name, method_name)
104
+ "#{class_name}-#{method_name}"
105
+ end
106
+
107
+ # returns the aliased name for
108
+ # any given method.
109
+ # @param [Symbol] method a symbol denoting the method.
110
+ # @return [Symbol] aliased method symbol.
111
+ def self.alias_for(method)
112
+ :"aliased_#{method}"
113
+ end
114
+
115
+ # setting content in redis
116
+ # @param [String] redis_id the id
117
+ # @param [Object] content should be basic content
118
+ # e.g. String, Hash, Array or Numbers.
119
+ def self.set(redis_id, content)
120
+ redis.set(redis_id, (content.is_a? String) ? content : content.to_json)
121
+ end
122
+
123
+ # getting redis content
124
+ # @param [String] redis_id the id
125
+ # @return [Object] stored content
126
+ def self.get(redis_id)
127
+ response = redis.get(redis_id)
128
+ begin
129
+ return JSON.parse response
130
+ rescue JSON::ParserError
131
+ return response
132
+ end
133
+ end
134
+
135
+ # manually update content in redis
136
+ # for a given object.
137
+ # @param [Object] obj any Object,
138
+ # preferably a decendent of
139
+ # an actual Rails Model.
140
+ # @param [Hash] options a simple hash
141
+ # @option options [Symbol] :on a certain registered
142
+ # method.
143
+ # @option options [Array] :arguments an array of
144
+ # arguments passed to
145
+ # the :on method or, if
146
+ # :on is not present, every
147
+ # method registered with fastaccess
148
+ # on the pertaining class.
149
+ def self.update_content(obj, options={})
150
+ class_name = obj.is_a?(Class) ? obj : obj.class
151
+ methods = if method = options[:on]
152
+ if registered? class_name, method
153
+ [method]
154
+ else
155
+ []
156
+ end
157
+ else
158
+ fastaccess_on[class_name]
159
+ end
160
+ methods.each do |method|
161
+ callable = obj.method( alias_for(method) )
162
+ content = if options[:arguments]
163
+ callable.call(*options[:arguments])
164
+ else
165
+ callable.call
166
+ end
167
+ self.set("#{method}_#{id_for(obj)}", content)
168
+ end
169
+ end
170
+
171
+ # setting up the environment for fastaccess
172
+ def self.setup(&block)
173
+ instance_eval &block if block_given?
174
+ end
175
+
176
+ # setting the global redis instance for fastaccess.
177
+ # @param [Redis] redis_instance The Connection to a redis server
178
+ def self.set_redis(redis_instance)
179
+ if redis_instance
180
+ @@redis = redis_instance
181
+ else
182
+ @@redis = $redis if $redis
183
+ end
184
+ end
185
+
186
+ # getting the redis instance
187
+ # @return [Redis] the instance
188
+ def self.redis
189
+ @@redis
190
+ end
191
+
192
+ # merges the, hopefully, reasonable
193
+ # defaults with the supplied options
194
+ # hash
195
+ # @param [Hash] options the pertaining options
196
+ # supplied by the user
197
+ # @return [Hash] the merged options hash
198
+ def self.merge_defaults(options)
199
+ ACTS_OPTIONS_DEFAULTS.merge(options)
200
+ end
201
+
202
+ end
203
+
204
+ end