/
params.json
1 lines (1 loc) · 17.7 KB
/
params.json
1
{"name":"Redis orm","body":"RedisOrm supposed to be *almost* drop-in replacement of ActiveRecord 2.x. It's based on the [Redis](http://redis.io) - advanced key-value store and is work in progress.\r\n\r\nHere's the standard model definition:\r\n\r\n```ruby\r\nclass User < RedisOrm::Base\r\n property :first_name, String\r\n property :last_name, String\r\n \r\n timestamps\r\n \r\n # OR\r\n # property :created_at, Time\r\n # property :modified_at, Time\r\n \r\n index :last_name\r\n index [:first_name, :last_name]\r\n \r\n has_many :photos\r\n has_one :profile\r\n \r\n after_create :create_mailboxes\r\n \r\n def create_mailboxes\r\n # ...\r\n end\r\nend\r\n```\r\n\r\n## Installing redis_orm\r\n\r\nstable release:\r\n\r\n```sh\r\ngem install redis_orm\r\n```\r\n\r\nor edge version:\r\n\r\n```sh\r\ngit clone git://github.com/german/redis_orm.git\r\ncd redis_orm\r\nbundle install\r\n```\r\n\r\nTo run the tests you should have redis installed already. Please check [Redis download/installation page](http://redis.io/download).\r\n\r\n```sh\r\nrake test\r\n```\r\n\r\n## Setting up a connection to the redis server\r\n\r\nIf you are using Rails you should initialize redis and set up global $redis variable in *config/initializers/redis.rb* file:\r\n\r\n```ruby\r\nrequire 'redis'\r\n$redis = Redis.new(:host => 'localhost', :port => 6379)\r\n```\r\n\r\n## Defining a model and specifing properties\r\n\r\nTo specify properties for your model you should use the following syntax:\r\n\r\n```ruby\r\nclass User < RedisOrm::Base\r\n property :first_name, String\r\n property :last_name, String\r\n property :created_at, Time\r\n property :modified_at, Time\r\nend\r\n```\r\n\r\nSupported property types:\r\n\r\n* **Integer**\r\n\r\n* **String**\r\n\r\n* **Float**\r\n\r\n* **RedisOrm::Boolean**\r\n there is no Boolean class in Ruby so it's a special class to store TrueClass or FalseClass objects\r\n\r\n* **Time**\r\n\r\nFollowing options are available in property declaration:\r\n\r\n* **:default**\r\n\r\n The default value of the attribute when it's getting saved w/o any.\r\n\r\n* **:sortable**\r\n\r\n if *true* is specified then you could sort records by this property later\r\n\r\n*Note* that when you're using :sortable option redis_orm maintains one additional list per attribute. Also note that the #create method could be 3 times slower in some cases (this will be improved in future releases), while the #find performance is basically the same (see the \"benchmarks/sortable_benchmark.rb\").\r\n\r\n## Searching records by the value\r\n\r\nUsually it's done via declaring an index and using *:conditions* hash or dynamic finders. For example:\r\n\r\n```ruby\r\nclass User < RedisOrm::Base\r\n property :name, String\r\n\r\n index :name\r\nend\r\n\r\nUser.create :name => \"germaninthetown\"\r\n\r\n# via dynamic finders:\r\nUser.find_by_name \"germaninthetown\" # => found 1 record\r\nUser.find_all_by_name \"germaninthetown\" # => array with 1 record\r\n\r\n# via *:conditions* hash:\r\nUser.find(:all, :conditions => {:name => \"germaninthetown\"}) # => array with 1 record\r\nUser.all(:conditions => {:name => \"germaninthetown\"}) # => array with 1 record\r\n```\r\n\r\nDynamic finders work mostly the way they work in ActiveRecord. The only difference is if you didn't specified index or compound index on the attributes you are searching on the exception will be raised. So you should make an initial analysis of model and determine properties that should be searchable.\r\n\r\n## Options for #find/#all\r\n\r\nTo extract all or part of the associated records you could use 4 options:\r\n\r\n* :limit\r\n\r\n* :offset\r\n\r\n* :order\r\n\r\n Either :desc or :asc (default), since records are stored with *Time.now.to_f* scores, by default they could be fetched only in that (or reversed) order. To order by different property you should:\r\n \r\n 1. specify *:sortable => true* as option in property declaration\r\n \r\n 2. specify the property by which you wish to order *:order => [:name, :desc]* or *:order => [:name]* (:asc order is default)\r\n \r\n* :conditions\r\n\r\n Hash where keys must be equal to the existing property name (there must be index for this property too).\r\n\r\n```ruby\r\n# for example we associate 2 photos with the album\r\n@album.photos << Photo.create(:image_type => \"image/png\", :image => \"boobs.png\")\r\n@album.photos << Photo.create(:image_type => \"image/jpeg\", :image => \"facepalm.jpg\")\r\n\r\n@album.photos.all(:limit => 0, :offset => 0) # => []\r\n@album.photos.all(:limit => 1, :offset => 0).size # => 1\r\n@album.photos.all(:limit => 2, :offset => 0) # [...]\r\n@album.photos.all(:limit => 1, :offset => 1, :conditions => {:image_type => \"image/png\"})\r\n@album.photos.find(:all, :order => \"asc\")\r\n\r\nPhoto.find(:first, :order => \"desc\")\r\nPhoto.all(:order => \"asc\", :limit => 5)\r\nPhoto.all(:order => \"desc\", :limit => 10, :offset => 50)\r\nPhoto.all(:order => \"desc\", :offset => 10, :conditions => {:image_type => \"image/jpeg\"})\r\n\r\nPhoto.find(:all, :conditions => {:image => \"facepalm.jpg\"}) # => [...]\r\nPhoto.find(:first, :conditions => {:image => \"boobs.png\"}) # => [...]\r\n```\r\n\r\n## Using UUID instead of numeric id\r\n\r\nYou could use universally unique identifiers (UUIDs) instead of a monotone increasing sequence of numbers as id/primary key for your models. \r\n\r\nExample of UUID: b57525b09a69012e8fbe001d61192f09. \r\n\r\nTo enable UUIDs you should invoke *use_uuid_as_id* class method:\r\n\r\n```ruby\r\nclass User < RedisOrm::Base\r\n use_uuid_as_id\r\n \r\n property :name, String\r\n\r\n property :created_at, Time\r\nend\r\n```\r\n\r\n[UUID](https://rubygems.org/gems/uuid) gem is installed as a dependency. \r\n\r\nAn excerpt from https://github.com/assaf/uuid :\r\n\r\nUUID (universally unique identifier) are guaranteed to be unique across time and space. \r\n\r\nA UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit sequence number and a 48-bit node identifier. \r\n\r\nNote: when using a forking server (Unicorn, Resque, Pipemaster, etc) you don’t want your forked processes using the same sequence number. Make sure to increment the sequence number each time a worker forks.\r\n\r\nFor example, in config/unicorn.rb:\r\n\r\n```ruby\r\nafter_fork do |server, worker|\r\n UUID.generator.next_sequence\r\nend\r\n```\r\n\r\n## Indices\r\n\r\nIndices are used in a different way then they are used in relational databases. In redis_orm they are used to find record by they value rather then to quick access them.\r\n\r\nYou could add index to any attribute of the model (index also could be compound):\r\n\r\n```ruby\r\nclass User < RedisOrm::Base\r\n property :first_name, String\r\n property :last_name, String\r\n\r\n index :first_name\r\n index [:first_name, :last_name]\r\nend\r\n```\r\n\r\nWith index defined for the property (or properties) the id of the saved object is stored in the sorted set with special name, so it could be found later by the value. For example with defined User model from the above code:\r\n\r\n```ruby\r\nuser = User.new :first_name => \"Robert\", :last_name => \"Pirsig\"\r\nuser.save\r\n\r\n# 2 redis keys are created \"user:first_name:Robert\" and \"user:first_name:Robert:last_name:Pirsig\" so we could search records like this:\r\n\r\nUser.find_by_first_name(\"Robert\") # => user\r\nUser.find_all_by_first_name(\"Robert\") # => [user]\r\nUser.find_by_first_name_and_last_name(\"Robert\", \"Pirsig\") # => user\r\nUser.find_all_by_first_name_and_last_name(\"Chris\", \"Pirsig\") # => []\r\n```\r\n\r\nIndices on associations are also created/deleted/updated when objects with has_many/belongs_to associations are created/deleted/updated (excerpt from association_indices_test.rb):\r\n\r\n```ruby\r\nclass Article < RedisOrm::Base\r\n property :title, String\r\n has_many :comments\r\nend\r\n\r\nclass Comment < RedisOrm::Base\r\n property :body, String\r\n property :moderated, RedisOrm::Boolean, :default => false\r\n index :moderated\r\n belongs_to :article\r\nend\r\n\r\narticle = Article.create :title => \"DHH drops OpenID on 37signals\"\r\ncomment1 = Comment.create :body => \"test\" \r\ncomment2 = Comment.create :body => \"test #2\", :moderated => true\r\n\r\narticle.comments << [comment1, comment2]\r\n\r\n# here besides usual indices for each comment, 2 association indices are created so #find with *:conditions* on comments should work\r\n\r\narticle.comments.find(:all, :conditions => {:moderated => true})\r\narticle.comments.find(:all, :conditions => {:moderated => false})\r\n```\r\n\r\nIndex definition supports following options:\r\n\r\n* **:unique** Boolean default: false\r\n\r\n If true is specified then value is stored in ordinary key-value structure with index as the key, otherwise the values are added to sorted set with index as the key and *Time.now.to_f* as a score.\r\n \r\n* **:case_insensitive** Boolean default: false\r\n\r\n If true is specified then property values are saved downcased (and then are transformed to downcase form when searching). Works for compound indices too.\r\n \r\n## Associations\r\n\r\nRedisOrm provides 3 association types:\r\n\r\n* has_one\r\n\r\n* has_many\r\n\r\n* belongs_to\r\n\r\nHABTM association could be emulated with 2 has_many declarations in related models.\r\n\r\n### has_many/belongs_to associations\r\n\r\n```ruby\r\nclass Article < RedisOrm::Base\r\n property :title, String\r\n has_many :comments\r\nend\r\n\r\nclass Comment < RedisOrm::Base\r\n property :body, String\r\n belongs_to :article\r\nend\r\n\r\narticle = Article.create :title => \"DHH drops OpenID support on 37signals\"\r\ncomment1 = Comment.create :body => \"test\"\r\ncomment2 = Comment.create :body => \"test #2\"\r\n\r\narticle.comments << [comment1, comment2]\r\n\r\n# or rewrite associations\r\narticle.comments = [comment1, comment2]\r\n\r\narticle.comments # => [comment1, comment2]\r\ncomment1.article # => article\r\ncomment2.article # => article\r\n```\r\n\r\nBacklinks are automatically created.\r\n\r\n### has_one/belongs_to associations\r\n\r\n```ruby\r\nclass User < RedisOrm::Base\r\n property :name, String\r\n has_one :profile \r\nend\r\n\r\nclass Profile < RedisOrm::Base\r\n property :age, Integer\r\n \r\n validates_presence_of :age \r\n belongs_to :user\r\nend\r\n\r\nuser = User.create :name => \"Haruki Murakami\"\r\nprofile = Profile.create :age => 26\r\nuser.profile = profile\r\n\r\nuser.profile # => profile\r\nprofile.user # => user\r\n```\r\n\r\nBacklink is automatically created.\r\n\r\n### has_many/has_many associations (HABTM)\r\n\r\n```ruby\r\nclass Article < RedisOrm::Base\r\n property :title, String\r\n has_many :categories\r\nend\r\n\r\nclass Category < RedisOrm::Base\r\n property :name, String\r\n has_many :articles\r\nend\r\n\r\narticle = Article.create :title => \"DHH drops OpenID support on 37signals\"\r\n\r\ncat1 = Category.create :name => \"Nature\"\r\ncat2 = Category.create :name => \"Art\"\r\ncat3 = Category.create :name => \"Web\"\r\n\r\narticle.categories << [cat1, cat2, cat3]\r\n\r\narticle.categories # => [cat1, cat2, cat3]\r\ncat1.articles # => [article]\r\ncat2.articles # => [article]\r\ncat3.articles # => [article]\r\n```\r\n\r\nBacklinks are automatically created.\r\n\r\n### Self-referencing association\r\n\r\n```ruby\r\nclass User < RedisOrm::Base\r\n property :name, String\r\n index :name\r\n has_many :users, :as => :friends\r\nend\r\n\r\nme = User.create :name => \"german\"\r\nfriend1 = User.create :name => \"friend1\"\r\nfriend2 = User.create :name => \"friend2\"\r\n\r\nme.friends << [friend1, friend2]\r\n\r\nme.friends # => [friend1, friend2]\r\nfriend1.friends # => []\r\nfriend2.friends # => []\r\n```\r\n\r\nAs an exception if *:as* option for the association is provided the backlinks aren't created.\r\n\r\n### Polymorphic associations\r\n\r\nPolymorphic associations work the same way they do in ActiveRecord (2 keys are created to store type and id of the record)\r\n\r\n```ruby\r\nclass CatalogItem < RedisOrm::Base\r\n property :title, String\r\n\r\n belongs_to :resource, :polymorphic => true\r\nend\r\n\r\nclass Book < RedisOrm::Base\r\n property :price, Integer\r\n property :title, String\r\n \r\n has_one :catalog_item\r\nend\r\n\r\nclass Giftcard < RedisOrm::Base\r\n property :price, Integer\r\n property :title, String\r\n\r\n has_one :catalog_item\r\nend\r\n\r\nbook = Book.create :title => \"Permutation City\", :author => \"Egan Greg\", :price => 1529\r\ngiftcard = Giftcard.create :title => \"Happy New Year!\"\r\n\r\nci1 = CatalogItem.create :title => giftcard.title\r\nci1.resource = giftcard\r\n \r\nci2 = CatalogItem.create :title => book.title\r\nci2.resource = book\r\n```\r\n\r\nAll associations supports following options:\r\n\r\n* *:as* \r\n\r\n Symbol Association could be accessed by provided name\r\n\r\n* *:dependent* \r\n\r\n Symbol could be either :destroy or :nullify (default value)\r\n\r\n### Clearing/reseting associations\r\n\r\nYou could clear/reset associations by assigning appropriately nil/[] to it:\r\n\r\n```ruby\r\n# has_many association\r\n@article.comments << [@comment1, @comment2]\r\n@article.comments.count # => 2\r\n@comment1.article # => @article\r\n\r\n# clear \r\n@article.comments = []\r\n@article.comments.count # => 0\r\n@comment1.article # => nil\r\n\r\n# belongs_to (same for has_one)\r\n@article.comments << [@comment1, @comment2]\r\n@article.comments.count # => 2\r\n@comment1.article # => @article\r\n \r\n# clear\r\n@comment1.article = nil\r\n@article.comments.count # => 1\r\n@comment1.article # => nil\r\n```\r\n\r\nFor more examples please check test/associations_test.rb and test/polymorphic_test.rb\r\n\r\n## Validation\r\n\r\nRedisOrm includes ActiveModel::Validations. So all well-known validation callbacks are already in. An excerpt from test/validations_test.rb:\r\n\r\n```ruby\r\nclass Photo < RedisOrm::Base\r\n property :image, String\r\n \r\n validates_presence_of :image\r\n validates_length_of :image, :in => 7..32\r\n validates_format_of :image, :with => /\\w*\\.(gif|jpe?g|png)/\r\nend\r\n```\r\n\r\n## Callbacks\r\n\r\nRedisOrm provides 6 standard callbacks:\r\n\r\n```ruby\r\nafter_save :callback\r\nbefore_save :callback\r\nafter_create :callback\r\nbefore_create :callback\r\nafter_destroy :callback\r\nbefore_destroy :callback\r\n```\r\n\r\nThey are implemented differently than in ActiveModel though work as expected:\r\n\r\n```ruby\r\nclass Comment < RedisOrm::Base\r\n property :text, String\r\n \r\n belongs_to :user\r\n\r\n before_save :trim_whitespaces\r\n\r\n def trim_whitespaces\r\n self.text = self.text.strip\r\n end\r\nend\r\n```\r\n\r\n## Saving records\r\n\r\nWhen saving object standard ActiveModel's #valid? method is invoked at first. Then appropriate callbacks are run. Then new Hash in Redis is created with keys/values equal to the properties/values of the saving object. \r\n\r\nThe object's id is stored in \"model_name:ids\" sorted set with Time.now.to_f as a score. So records are ordered by created_at time by default. Then record's indices are created/updated.\r\n\r\n## Dirty\r\n\r\nRedis_orm also provides dirty methods to check whether the property has changed and what are these changes. To check it you could use 2 methods: #property_changed? (returns true or false) and #property_changes (returns array with changed values).\r\n\r\n## File attachment management with paperclip and redis\r\n\r\n[3 simple steps](http://def-end.com/post/6669884103/file-attachment-management-with-paperclip-and-redis) you should follow to manage your file attachments with redis and paperclip.\r\n\r\n## Tests\r\n\r\nThough I'm a big fan of the Test::Unit all tests are based on RSpec. And the only reason I use RSpec is possibility to define *before(:all)* and *after(:all)* hooks. So I could spawn/kill redis-server's process (from test_helper.rb):\r\n\r\n```ruby\r\nRSpec.configure do |config|\r\n config.before(:all) do\r\n path_to_conf = File.dirname(File.expand_path(__FILE__)) + \"/redis.conf\"\r\n $redis_pid = spawn 'redis-server ' + path_to_conf, :out => \"/dev/null\"\r\n sleep(0.3) # must be some delay otherwise \"Connection refused - Unable to connect to Redis\"\r\n path_to_socket = File.dirname(File.expand_path(__FILE__)) + \"/../redis.sock\"\r\n $redis = Redis.new(:host => 'localhost', :path => path_to_socket)\r\n end\r\n \r\n config.before(:each) do\r\n $redis.flushall if $redis\r\n end\r\n\r\n config.after(:each) do\r\n $redis.flushall if $redis\r\n end\r\n\r\n config.after(:all) do\r\n Process.kill 9, $redis_pid.to_i if $redis_pid\r\n end\r\nend\r\n```\r\n\r\nTo run all tests just invoke *rake test*\r\n\r\nCopyright © 2011 Dmitrii Samoilov, released under the MIT license\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE.","tagline":"ORM for Redis","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."}