yssk22-couch_resource 0.1.0 → 0.1.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.
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