yssk22-couch_resource 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,7 +1,219 @@
1
- = couch_resource
1
+ = What is CouchResource ?
2
2
 
3
- Description goes here.
3
+ CouchResource is an ActiveRecord-style data mapper for CouchDB.
4
+ You can develop model classes baed on the ActiveRecord way.
5
+
6
+ == Install
7
+
8
+ You can install from github,
9
+
10
+ gem source -a http://github.com
11
+ sudo gem install yssk22-couch_resource
12
+
13
+ And official gem packages is also available in {WebJourney Project @RubyForge}[http://rubyforge.org/projects/webjourney/]
14
+
15
+ == Supported CouchDB
16
+
17
+ version 0.9.0-incubating (currently installed from svn trunk).
18
+
19
+
20
+ == Examples of ActiveRecord-like Features
21
+ - Automapping CouchDB database
22
+
23
+ CouchReSource model classes are automatically mapped to databases.
24
+
25
+ class MyDocument < CouchResource::Base
26
+ end
27
+ #=> mapped to http://localhost:5984/my_documents
28
+
29
+ You can set the specific database uri with <tt>set_database</tt> method.
30
+
31
+ class MyDocument < CouhcResource::Base
32
+ set_database http://user:password@host:port/database1
33
+ end
34
+ #=> mapped to http://user:password@host:port/database1
35
+
36
+ - Typed attributes
37
+
38
+ CouchDB is schemaless so that you can set data attributes directly in your model code.
39
+
40
+ class MyDocument < CouhcResource::Base
41
+ string :title
42
+ string :content
43
+ array :tags, :is_a => String
44
+ end
45
+ doc = MyDocument.new
46
+ doc.title = "My document"
47
+ doc.content = "My content..."
48
+
49
+ And you can use nested data structure using CouchResource::SubResource.
50
+
51
+ class Comment < CouchResource::SubResource
52
+ string :content
53
+ end
54
+
55
+ class MyDocument < CouhcResource::Base
56
+ string :title
57
+ string :content
58
+ array :tags, :is_a => String
59
+ array :comments, :is_a => Comment
60
+ end
61
+ doc = MyDocument.new
62
+ doc.comments = []
63
+ doc.comments << Comment.new(:content => "comment ... ")
64
+
65
+ - Validation with typed attributes
66
+
67
+ ActiveRecord-like validations are supported.
68
+
69
+ class MyDocument < CouhcResource::Base
70
+ string :title, :validates => [[:length_of, {:in => 1..64, :allow_nil => false}]]
71
+ end
72
+
73
+ You can use validations indivisually out of attributes.
74
+
75
+ class MyDocument < CouhcResource::Base
76
+ string :title
77
+ validates_length_of :title, :in => 1..64, :allow_nil => false
78
+ end
79
+
80
+ - Callbacks
81
+
82
+ This feature is also the same as ActiveRecord.
83
+
84
+ class Person < CouchResource::Base
85
+ string :credit_card_id
86
+ def before_destroy # is called just before Person#destroy
87
+ CreditCard.find(credit_card_id).destroy
88
+ end
89
+ end
90
+
91
+ - Direct manipulation
92
+
93
+ docId = "abcdefg"
94
+ MyDocument.find(docId)
95
+
96
+ - Logging Support
97
+
98
+ CouchResource::Base.logger = Logger.new(STDOUT)
99
+ CouchResource::Base.logger = Log4r::Logger.new("Application Log")
100
+
101
+
102
+ - Observers
103
+
104
+ TBD, not implemented yet.
105
+
106
+ - Inheritance hierarchies
107
+
108
+ TBD, not implemented yet.
109
+
110
+ - Transactions
111
+
112
+ Not applicable for CouchDB.
113
+
114
+ - Reflections on columns, associations, and aggregations
115
+
116
+ Not applicable for CouchResource.
117
+
118
+ == Examples of CouchDB Features
119
+
120
+ - MapReduce finders
121
+
122
+ class MyDocument < CouchResource::Base
123
+ string :title
124
+ string :content
125
+ array :tags, :is_a => String
126
+
127
+ # define finder method (find_by_{design_name}_{view_name}) using map/reduce scripts
128
+ view :tags, :count => {
129
+ :map => include_js("path/to/map.js"),
130
+ :reduce => include_js("path/to/reduce.js")
131
+ }
132
+ end
133
+
134
+ MyDocument.find_tags_count("web")
135
+
136
+ - Pagination in finders
137
+
138
+ class MyDocument < CouchResource::Base
139
+ string :title
140
+ string :content
141
+ array :tags, :is_a => String
142
+ end
143
+
144
+ docs = MyDocument.find(:all, :count => 3)
145
+ # => first 3 documents in docs[:rows]
146
+ docs = MyDocument.find(docs[:next])
147
+ # => next 3 documents in docs[:rows] (*1)
148
+ docs = MyDocument.find(docs[:next])
149
+ # => next more 3 documents in docs[:rows]
150
+ docs = MyDocument.find(docs[:previous])
151
+ # => backward 3 documents in docs[:rows] (the same as *1)
152
+
153
+ == Rails Support
154
+
155
+ To enable rails utilities in CouchResource, you may need to add the following sentences in your config/environment.rb.
156
+
157
+ require 'couch_resource'
158
+ require 'couch_resource/rails'
159
+
160
+ - CouchConfig
161
+
162
+ You can define multipe CouchDB sites in config/couchdb.yml and use the configurations in your CouchResource models.
163
+
164
+ development:
165
+ user_profiles:
166
+ host: localhost
167
+ port: 5984
168
+ database: user_profiles_development
169
+ pages:
170
+ host: localhost
171
+ port: 5984
172
+ database: pages_development
173
+ feeds:
174
+ host: localhost
175
+ port: 5984
176
+ database: feeds_development
177
+
178
+ class Page < CouchResource::Base
179
+ set_database CouchConfig.database_uri_for(:db => :wj_pages)
180
+ end
181
+
182
+ - CouchFixture
183
+
184
+ Add followings in your test/test_helper.rb.
185
+
186
+ class Test::Unit::TestCase
187
+ # ... (snip) ...
188
+
189
+ # Add more helper methods to be used by all tests here...
190
+ setup :setup_couch_fixture
191
+ def setup_couch_fixture
192
+ CouchFixture.load
193
+ end
194
+ end
195
+
196
+ Then You can define fixtures in test/fixtures/couchdb/{model_name}.yml
197
+
198
+
199
+ == Other information
200
+
201
+ === Application Practical Example
202
+
203
+ WebJourney and it's blog component. Both of them are developed on http://github.com/yssk22/webjourney/tree/master
204
+
205
+ === Bugs/Requests/Quesions and ...
206
+
207
+ Let's collabolate {our project site}[http://www.webjourney.org/project/projects/show/couch-resource]
208
+ or please fork the github repo at http://github.com/yssk22/couch_resource.
209
+
210
+ === Altenertives
211
+
212
+ There are some useful alternatves for CouchDB mapper in Ruby environment.
213
+
214
+ CouchPotato : http://github.com/langalex/couch_potato/tree/master
215
+ CouchRest : http://github.com/jchris/couchrest/tree/master
4
216
 
5
217
  == Copyright
6
218
 
7
- Copyright (c) 2009 Yohei Sasaki. See LICENSE for details.
219
+ Copyright (c) 2009 Yohei Sasaki. See MIT_LICENSE for details.
@@ -3,76 +3,53 @@ require 'json'
3
3
  require File.join(File.dirname(__FILE__), 'error')
4
4
  require File.join(File.dirname(__FILE__), 'connection')
5
5
 
6
+ #
7
+ # CouchResource is an ActiveRecord-style data mapper for CouchDB.
8
+ #
6
9
  module CouchResource
7
10
  class Base
8
11
  cattr_accessor :logger, :instance_writer => false
12
+ @@logger = Logger.new("/dev/null")
9
13
  cattr_accessor :check_design_revision_every_time, :instance_writer => false
14
+ @@check_design_revision_every_time = true
10
15
 
11
16
  class << self
12
17
  # Get the URI of the CouchDB database to map for this class
13
18
  def database
14
- if defined?(@database)
15
- @database
16
- elsif superclass != Object && superclass.database
17
- superclass.database.dup.freeze
19
+ db = read_inheritable_attribute(:database)
20
+ if db
21
+ db
22
+ elsif superclass != CouchResource::Base && superclass.database
23
+ superclass.database
24
+ else
25
+ # convension
26
+ db = self.name.to_s.underscore.pluralize.gsub("/", "_")
27
+ URI.parse("http://localhost:5984/#{URI.decode(db)}")
18
28
  end
19
29
  end
20
30
 
21
31
  # Set the URI of the CouchDB database to map for this class
22
32
  def database=(uri)
23
- @connection = nil
24
33
  if uri.nil?
25
- @database = nil
34
+ write_inheritable_attribute(:database, nil)
26
35
  else
27
- @database = uri.is_a?(URI) ? uri.dup : URI.parse(uri)
28
- @user = URI.decode(@database.user) if @database.user
29
- @password = URI.decode(@database.password) if @database.password
36
+ db = uri.is_a?(URI) ? uri.dup : URI.parse(uri)
37
+ write_inheritable_attribute(:database, db)
38
+ db
30
39
  end
31
40
  end
32
41
  alias :set_database :database=
33
42
 
34
- # Gets the user for REST HTTP authentication.
35
- def user
36
- # Not using superclass_delegating_reader. See +site+ for explanation
37
- if defined?(@user)
38
- @user
39
- elsif superclass != Object && superclass.user
40
- superclass.user.dup.freeze
41
- end
42
- end
43
-
44
- # Sets the user for REST HTTP authentication.
45
- def user=(user)
46
- @connection = nil
47
- @user = user
48
- end
49
-
50
- # Gets the password for REST HTTP authentication.
51
- def password
52
- # Not using superclass_delegating_reader. See +site+ for explanation
53
- if defined?(@password)
54
- @password
55
- elsif superclass != Object && superclass.password
56
- superclass.password.dup.freeze
57
- end
58
- end
59
-
60
- # Sets the password for REST HTTP authentication.
61
- def password=(password)
62
- @connection = nil
63
- @password = password
64
- end
65
-
66
43
  # Sets the number of seconds after which requests to the REST API should time out.
67
44
  def timeout=(timeout)
68
- @connection = nil
69
- @timeout = timeout
45
+ write_inheritable_attribute(:timeout, timeout)
70
46
  end
71
47
 
72
48
  # Gets tthe number of seconds after which requests to the REST API should time out.
73
49
  def timeout
74
- if defined?(@timeout)
75
- @timeout
50
+ tm = read_inheritable_attribute(:timeout)
51
+ if tm
52
+ tm
76
53
  elsif superclass != Object && superclass.timeout
77
54
  superclass.timeout
78
55
  end
@@ -82,15 +59,16 @@ module CouchResource
82
59
  # The +refresh+ parameter toggles whether or not the connection is refreshed at every request
83
60
  # or not (defaults to <tt>false</tt>).
84
61
  def connection(reflesh = false)
85
- if defined?(@connection) || superclass == Object
86
- @connection = Connection.new(database) if reflesh || @connection.nil?
87
- @connection.user = user if user
88
- @connection.password = password if password
89
- @connection.timeout = timeout if timeout
90
- @connection
91
- else
92
- superclass.connection
62
+ conn = read_inheritable_attribute(:connection)
63
+ if reflesh || conn.nil?
64
+ db = database
65
+ conn = Connection.new(db)
66
+ conn.user = user if db.user
67
+ conn.password = password if db.password
68
+ conn.timeout = timeout if timeout
69
+ write_inheritable_attribute(:connection, conn)
93
70
  end
71
+ conn
94
72
  end
95
73
 
96
74
  # Get the document path specified by the document id
@@ -0,0 +1,123 @@
1
+ #
2
+ # CouchDB Configuration Loader class
3
+ #
4
+ # = Configuration CouchDB Data Source in YAML
5
+ #
6
+ # Database configurations can be defined in Yaml format. WebJourney can support multiple CouchDB databases.
7
+ # There are two types of configuration YAML files in WebJourney.
8
+ #
9
+ # - site level configuration, located in RAILS_ROOT/config/couchdb.yml
10
+ # - component level configuration, located in RAILS_ROOT/components/{component_dir}/_config/couchdb.yml
11
+ #
12
+ # = YAML format
13
+ #
14
+ # The configuration format is almost the same as database.yml except that couchdb.yml requires <tt>db identifier</tt> for the multiple database support.
15
+ # The format is as follows::
16
+ #
17
+ # {environement}:
18
+ # {db_identifier}:
19
+ # host : {couchdb hostname}
20
+ # port : {couchdb port}
21
+ # database : {couchdb database name}
22
+ #
23
+ # For example, WjPage objects are persisted in one CouchDB database and WjUserProfile objects in another. In this case,
24
+ # two databases are required so that the couchdb.yml (for <tt>development</tt> and <tt>production</tt>) is as follows::
25
+ #
26
+ # development:
27
+ # wj_user_profiles:
28
+ # host: localhost
29
+ # port: 5984
30
+ # database: webjourney_dev_wj_profile
31
+ # wj_pages:
32
+ # host: localhost
33
+ # port: 5984
34
+ # database: webjourney_dev_wj_pages
35
+ #
36
+ # production:
37
+ # wj_user_profiles:
38
+ # host: couch_site1
39
+ # port: 5984
40
+ # database: webjourney_dev_wj_profile
41
+ # wj_pages:
42
+ # host: couch_site2
43
+ # port: 5984
44
+ # database: webjourney_dev_wj_pages
45
+ #
46
+ # = Specify the database in CouchResource models
47
+ #
48
+ # CouchConfig class does only resolve a name to the couchdb database url by the <tt>database_uri_for</tt> method.
49
+ # On the other hand, it is required to specify a database in a CouchResource model class (by <tt>set_database</tt> method).
50
+ # So configruations loaded by CouchConfig and CouchResource classes are related as follows.
51
+ #
52
+ # At first, there are two couchdb.yml files. one is the site level configuration file.
53
+ #
54
+ # # RAILS_ROOT/config/couchdb.yml
55
+ # development:
56
+ # abc:
57
+ # host: localhost
58
+ # port: 5984
59
+ # database: system_abc
60
+ #
61
+ # The other is the component level configuraiton.
62
+ #
63
+ # # RAILS_ROOT/comopnents/my_component/config/couchdb.yml
64
+ # development:
65
+ # abc:
66
+ # host: 192.168.1.10
67
+ # port: 5984
68
+ # database: my_component_abc
69
+ #
70
+ # == Using site level database (often used by the WebJounrney framework)
71
+ #
72
+ # class MyDocumentModel < CouchResource::Base
73
+ # # specify without the '/' separator.
74
+ # set_database CouchConfig.database_uri_for(:db => "abc")
75
+ #
76
+ # end
77
+ #
78
+ # In this case, CouchConfig loads the configuration from RAILS_ROOT/config/couchdb.yml so that
79
+ # MyDocumentModel objects are stored at <tt>#http://localhost:5984/system_abc</tt>.
80
+ #
81
+ # == Using component level database (often used by components)
82
+ #
83
+ # class MyComponent::MyDocumentModel < CouchResource::Base
84
+ # # specify with the '/' separator.
85
+ # set_database CouchConfig.database_uri_for(:db => "my_component/abc")
86
+ #
87
+ # end
88
+ #
89
+ # In this case, CouchConfig loads the configuration from RAILS_ROOT/component/my_component/_config/couchdb.yml so that
90
+ # MyComponent::MyDocumentModel objects are stored at <tt>#http://192.168.1.10:5984/my_component_abc</tt>.
91
+ #
92
+ #
93
+ class CouchConfig
94
+ # Get the all configuration hash
95
+ def self.get(component = nil)
96
+ if component.blank?
97
+ @@config ||= YAML.load(File.open(File.join(RAILS_ROOT, "config/couchdb.yml")))
98
+ else
99
+ @@component_config ||= {}
100
+ @@component_config[component] ||= YAML.load(File.open(File.join(RAILS_ROOT, "components", component, "_config/couchdb.yml")))
101
+ end
102
+ end
103
+
104
+ # Get the current configuration (specified to the database) by URI string
105
+ def self.database_uri_for(option = {})
106
+ env = (option[:env] || RAILS_ENV).to_s
107
+ db = (option[:db] || :system).to_s
108
+
109
+ component, dbname = db.split("/")
110
+
111
+ # swap if db does not have any '/' separators.
112
+ component, dbname = dbname, component if dbname.blank?
113
+
114
+ config = self.get(component)[env.to_s][dbname]
115
+
116
+ # check each attributes
117
+ scheme = config["scheme"] || "http"
118
+ host = config["host"] || "localhost"
119
+ port = config["port"] || 5984
120
+ database = config["database"] || ("webjourney_#{RAILS_ENV}_#{dbname}")
121
+ "#{scheme}://#{host}:#{port}/#{database}"
122
+ end
123
+ end
@@ -0,0 +1,52 @@
1
+ #
2
+ # = Experimental loader for CouchDB fixture data
3
+ #
4
+ # CouchDB fixture is an experimental module for Testing. Fixture data is stored in test/fixtures/couchdb/*.yml in YAML format.
5
+ # Some useful features in ActiveRecord fixture such as transactional data cannot not supported in CouchDB.
6
+ #
7
+ class CouchFixture
8
+ #
9
+ # Load all fixtures in the <tt>basedir</tt>. If the fixture data is already stored in database, it'll be destroyed and newly created.
10
+ #
11
+ def self.load(basedir = File.join(RAILS_ROOT, "test/fixtures/couchdb/"))
12
+ # collecting database and yaml dataset pairs
13
+ datasets = {}
14
+ Dir.glob("#{basedir}/**/*.yml") do |f|
15
+ # resolve class
16
+ file = f.gsub(/^#{basedir}\//, '').gsub(/\.yml$/, '')
17
+ klass = file.singularize.camelize.constantize
18
+ # reset a database
19
+ uri = klass.database
20
+ datasets[uri] ||= []
21
+ content = YAML.load(ERB.new(File.read(f)).result)
22
+ datasets[uri] << [klass, content]
23
+ end
24
+
25
+ datasets.each do |uri, classes|
26
+ RAILS_DEFAULT_LOGGER.debug "CouchFixture : reset database (#{uri})."
27
+ Net::HTTP.start(uri.host, uri.port) { |http|
28
+ http.delete(uri.path)
29
+ http.put(uri.path, nil)
30
+ }
31
+ RAILS_DEFAULT_LOGGER.debug "CouchFixture : push the datasets (#{uri})."
32
+ classes.each do |klass, content|
33
+ klass.write_inheritable_attribute(:design_revision_checked, false)
34
+ # load yaml file
35
+ if content
36
+ content.each do |key, value|
37
+ value.symbolize_keys!
38
+ value[:_id] = key unless value[:_id]
39
+ # obj = klass.find(value[:_id]) rescue nil
40
+ # obj.destroy if obj
41
+ obj = klass.new(value)
42
+ begin
43
+ obj.save!
44
+ rescue CouchResource::PreconditionFailed
45
+ raise "Cannot load fixture with #{key} on #{uri} (duplicated key?)"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,2 @@
1
+ require File.join(File.dirname(__FILE__), 'rails/couch_config')
2
+ require File.join(File.dirname(__FILE__), 'rails/couch_fixture')
@@ -3,13 +3,6 @@ require 'rubygems'
3
3
  require 'active_support'
4
4
  require 'json'
5
5
 
6
- #
7
- # == Synchronization of design documents
8
- #
9
- # Synchronization mechanism of design documents is controlled by <tt>check_design_revision_every_time</tt>.
10
- # The value is true, the finder methods of views always check the revision of the design document.
11
- # When false, the finder methods only check at first time.
12
- #
13
6
  module CouchResource
14
7
  module View
15
8
  def self.included(base)
data/test/test_base.rb CHANGED
@@ -39,6 +39,14 @@ class MagicAttributesTest < CouchResource::Base
39
39
  datetime :updated_on
40
40
  end
41
41
 
42
+ class DatabaseNameTest < CouchResource::Base
43
+ end
44
+
45
+ module Nested
46
+ class DatabaseNameTest < CouchResource::Base
47
+ end
48
+ end
49
+
42
50
  class TestBase < Test::Unit::TestCase
43
51
  def setup
44
52
  res = SimpleDocument.connection.put(SimpleDocument.database.path)
@@ -365,20 +373,25 @@ class TestBase < Test::Unit::TestCase
365
373
  assert_equal 0, docs[:rows].length
366
374
  end
367
375
 
368
- private
369
- def register_simple_documents
370
- 0.upto(9) do |i|
371
- doc = SimpleDocument.new(:title => "title_#{i}", :content => "content_#{i}")
372
- doc.save
373
- end
376
+ def test_database_name
377
+ assert_equal "http://localhost:5984/database_name_tests", DatabaseNameTest.database.to_s
378
+ assert_equal "http://localhost:5984/nested_database_name_tests", Nested::DatabaseNameTest.database.to_s
374
379
  end
375
380
 
376
381
  def test_magic_attributes
377
- obj = MagicAttributesTest
382
+ obj = MagicAttributesTest.new
378
383
  obj.save
379
384
  assert_not_nil obj.created_at
380
385
  assert_not_nil obj.created_on
381
386
  assert_not_nil obj.updated_at
382
387
  assert_not_nil obj.updated_on
383
388
  end
389
+
390
+ private
391
+ def register_simple_documents
392
+ 0.upto(9) do |i|
393
+ doc = SimpleDocument.new(:title => "title_#{i}", :content => "content_#{i}")
394
+ doc.save
395
+ end
396
+ end
384
397
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yssk22-couch_resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yohei Sasaki
@@ -9,10 +9,29 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-10 00:00:00 -07:00
12
+ date: 2009-03-22 00:00:00 -07:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.1.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: active_support
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.2
34
+ version:
16
35
  description:
17
36
  email: yssk22@gmail.com
18
37
  executables: []
@@ -28,6 +47,9 @@ files:
28
47
  - lib/couch_resource/callbacks.rb
29
48
  - lib/couch_resource/connection.rb
30
49
  - lib/couch_resource/error.rb
50
+ - lib/couch_resource/rails/couch_config.rb
51
+ - lib/couch_resource/rails/couch_fixture.rb
52
+ - lib/couch_resource/rails.rb
31
53
  - lib/couch_resource/struct.rb
32
54
  - lib/couch_resource/validations.rb
33
55
  - lib/couch_resource/view.rb