diff --git a/lib/couchrest/model/base.rb b/lib/couchrest/model/base.rb index e70d325..5e25834 100644 --- a/lib/couchrest/model/base.rb +++ b/lib/couchrest/model/base.rb @@ -4,10 +4,11 @@ module CouchRest extend ActiveModel::Naming + include CouchRest::Model::Connection include CouchRest::Model::Configuration include CouchRest::Model::Persistence include CouchRest::Model::Callbacks - include CouchRest::Model::DocumentQueries + include CouchRest::Model::DocumentQueries include CouchRest::Model::Views include CouchRest::Model::DesignDoc include CouchRest::Model::ExtendedAttachments diff --git a/lib/couchrest/model/configuration.rb b/lib/couchrest/model/configuration.rb index 9a89d72..6c0d489 100644 --- a/lib/couchrest/model/configuration.rb +++ b/lib/couchrest/model/configuration.rb @@ -11,13 +11,33 @@ module CouchRest add_config :model_type_key add_config :mass_assign_any_attribute add_config :auto_update_design_doc - add_config :database_config_path + add_config :environment + add_config :connection + add_config :connection_config_file configure do |config| config.model_type_key = 'model' # was 'couchrest-type' config.mass_assign_any_attribute = false config.auto_update_design_doc = true - config.database_config_path = File.join(defined?(Rails) ? Rails.root : Dir.pwd, 'config', 'couchdb.yml') + + config.environment = defined?(Rails) ? Rails.env : :development + + config.connection_config_file = + File.join( + defined?(Rails) ? Rails.root : Dir.pwd, + 'config', 'couchdb.yml' + ) + + app_name = defined?(Rails) ? Rails.application.class.to_s.underscore.gsub(/\/.*/, '') : 'couchrest' + config.connection = { + :protocol => 'http', + :host => 'localhost', + :port => '5984', + :prefix => app_name, + :suffix => nil, + :username => nil, + :password => nil + } end end diff --git a/lib/couchrest/model/connection.rb b/lib/couchrest/model/connection.rb new file mode 100644 index 0000000..56340a1 --- /dev/null +++ b/lib/couchrest/model/connection.rb @@ -0,0 +1,74 @@ +module CouchRest + module Model + module Connection + extend ActiveSupport::Concern + + def database + self.class.database + end + + def server + self.class.server + end + + module ClassMethods + + # Overwrite the normal use_database method so that a database + # name can be provided instead of a full connection. + def use_database(db) + @_database_name = db + end + + # Replace CouchRest's database reader with a more advanced + # version that will make a best guess at the database you might + # want to use. Allows for a string to be provided instead of + # a database object. + def database + @database ||= prepare_database(@_database_name) + end + + def server + @server ||= CouchRest::Server.new(prepare_server_uri) + end + + def prepare_database(db = nil) + unless db.is_a?(CouchRest::Database) + conf = connection_configuration + db = [conf[:prefix], db.to_s, conf[:suffix]].compact.join('_') + self.server.database!(db) + else + db + end + end + + protected + + def prepare_server_uri + conf = connection_configuration + userinfo = [conf[:username], conf[:password]].compact.join(':') + userinfo += '@' unless userinfo.empty? + "#{conf[:protocol]}://#{userinfo}#{conf[:host]}:#{conf[:port]}" + end + + def connection_configuration + @server_configuration ||= + self.connection.update( + (load_connection_config_file[environment] || {}).symbolize_keys + ) + end + + def load_connection_config_file + connection_config_cache[connection_config_file] ||= + File.exists?(connection_config_file) ? + YAML::load(ERB.new(IO.read(connection_config_file)).result) : + { } + end + + def connection_config_cache + Thread.current[:connection_config_cache] ||= {} + end + end + + end + end +end diff --git a/lib/couchrest/model/persistence.rb b/lib/couchrest/model/persistence.rb index 774f3a5..e77b663 100644 --- a/lib/couchrest/model/persistence.rb +++ b/lib/couchrest/model/persistence.rb @@ -89,10 +89,6 @@ module CouchRest self end - def database - self.class.database(@database) - end - protected def perform_validations(options = {}) @@ -108,20 +104,6 @@ module CouchRest module ClassMethods - # Replace CouchRest's database reader with a more advanced - # version that will make a best guess at the database you might - # want to use. Allows for a string to be provided instead of - # a database object. - def database(db = nil) - db ||= @database - if db.nil? - # try to grab from configuration files - - else - db - end - end - # Creates a new instance, bypassing attribute protection # # ==== Returns diff --git a/lib/couchrest/model/proxyable.rb b/lib/couchrest/model/proxyable.rb index 81b30b5..8c26105 100644 --- a/lib/couchrest/model/proxyable.rb +++ b/lib/couchrest/model/proxyable.rb @@ -4,8 +4,14 @@ module CouchRest module Proxyable extend ActiveSupport::Concern + def proxy_database + raise StandardError, "Please set the #proxy_database_method" if self.class.proxy_database_method.nil? + @proxy_database ||= self.class.prepare_database(self.send(self.class.proxy_database_method)) + end + module ClassMethods + # Define a collection that will use the base model for the database connection # details. def proxy_for(assoc_name, options = {}) @@ -13,19 +19,19 @@ module CouchRest options[:class_name] ||= assoc_name.to_s.singularize.camelize class_eval <<-EOS, __FILE__, __LINE__ + 1 def #{assoc_name} - unless respond_to?('#{db_method}') - raise "Missing ##{db_method} method for proxy" - end @#{assoc_name} ||= CouchRest::Model::Proxyable::ModelProxy.new(::#{options[:class_name]}, self, self.class.to_s.underscore, #{db_method}) end EOS end + # Tell this model which other model to use a base for the database + # connection to use. def proxied_by(model_name, options = {}) raise "Model can only be proxied once or ##{model_name} already defined" if method_defined?(model_name) || !proxy_owner_method.nil? self.proxy_owner_method = model_name attr_accessor :model_proxy attr_accessor model_name + overwrite_database_reader(model_name) end # Define an a class variable accessor ready to be inherited and unique @@ -34,6 +40,24 @@ module CouchRest def proxy_owner_method=(name); @proxy_owner_method = name; end def proxy_owner_method; @proxy_owner_method; end + # Define the name of a method to call to determine the name of + # the database to use as a proxy. + def proxy_database_method=(name); @proxy_database_method = name; end + def proxy_database_method; @proxy_database_method; end + + private + + # Ensure that the model can no longer be used for normal requests + # be overwriting the database reader method so that a helpful + # error message is displayed. + def overwrite_database_reader(model_name) + class_eval <<-EOS, __FILE__, __LINE__ + 1 + def database + raise StandardError, "#{self.to_s} documents must be accessed via the '#{model_name}' proxy" + end + EOS + end + end class ModelProxy diff --git a/lib/couchrest_model.rb b/lib/couchrest_model.rb index 9a7a18a..7de6b41 100644 --- a/lib/couchrest_model.rb +++ b/lib/couchrest_model.rb @@ -46,6 +46,7 @@ require "couchrest/model/proxyable" require "couchrest/model/collection" require "couchrest/model/associations" require "couchrest/model/configuration" +require "couchrest/model/connection" require "couchrest/model/designs" require "couchrest/model/designs/view" @@ -61,4 +62,6 @@ require "couchrest/model/casted_model" require "couchrest/model/base" # Add rails support *after* everything has loaded -require "couchrest/railtie" +if defined?(Rails) + require "couchrest/railtie" +end diff --git a/spec/couchrest/configuration_spec.rb b/spec/couchrest/configuration_spec.rb index 46681c0..9ff4fcf 100644 --- a/spec/couchrest/configuration_spec.rb +++ b/spec/couchrest/configuration_spec.rb @@ -56,10 +56,52 @@ describe CouchRest::Model::Base do end end + describe "default configuration" do + + it "should provide environment" do + @class.environment.should eql(:development) + end + it "should provide connection config file" do + @class.connection_config_file.should eql(File.join(Dir.pwd, 'config', 'couchdb.yml')) + end + it "should provided simple connection details" do + @class.connection[:prefix].should eql('couchrest') + end + + end + + describe "default configuration with Rails" do + before do + Rails = mock('Rails') unless defined?(Rails) + Rails.stub!(:env).and_return(:dev) + Rails.stub!(:root).and_return("/rails/root") + app = mock('Application') + app.stub!(:class).and_return("SampleCouch::Application") + Rails.stub!(:application).and_return(app) + + # New anon class! + @class = Class.new() + @class.class_eval do + include CouchRest::Model::Configuration + end + end + + it "should provide environment" do + @class.environment.should eql(:dev) + end + it "should provide connection config file" do + @class.connection_config_file.should eql(File.join("/rails/root", 'config', 'couchdb.yml')) + end + it "should provided simple connection details" do + @class.connection[:prefix].should eql('sample_couch') + end + + end + describe "General examples" do before(:all) do - @default_model_key = 'model' + @default_model_key = 'model-type' end diff --git a/spec/couchrest/connection_spec.rb b/spec/couchrest/connection_spec.rb new file mode 100644 index 0000000..2724f00 --- /dev/null +++ b/spec/couchrest/connection_spec.rb @@ -0,0 +1,117 @@ +# encoding: utf-8 +require File.expand_path('../../spec_helper', __FILE__) + +describe CouchRest::Model::Base do + + before do + @class = Class.new(CouchRest::Model::Base) + end + + describe "instance methods" do + before :each do + @obj = @class.new + end + + describe "#database" do + it "should respond to" do + @obj.should respond_to(:database) + end + it "should provided class's database" do + @obj.class.should_receive :database + @obj.database + end + end + + describe "#server" do + it "should respond to method" do + @obj.should respond_to(:server) + end + it "should return class's server" do + @obj.class.should_receive :server + @obj.server + end + end + end + + describe "class methods" do + + describe ".use_database" do + it "should respond to" do + @class.should respond_to(:use_database) + end + end + + describe ".database" do + it "should respond to" do + @class.should respond_to(:database) + end + it "should provide a database object" do + @class.database.should be_a(CouchRest::Database) + end + it "should provide a database with default name" do + + end + + end + + describe ".server" do + it "should respond to" do + @class.should respond_to(:server) + end + it "should provide a server object" do + @class.server.should be_a(CouchRest::Server) + end + it "should provide a server with default config" do + @class.server.uri.should eql("http://localhost:5984") + end + it "should allow the configuration to be overwritten" do + @class.connection = { + :protocol => "https", + :host => "127.0.0.1", + :port => '5985', + :prefix => 'sample', + :suffix => 'test', + :username => 'foo', + :password => 'bar' + } + @class.server.uri.should eql("https://foo:bar@127.0.0.1:5985") + end + + end + + describe ".prepare_database" do + + it "should respond to" do + @class.should respond_to(:prepare_database) + end + + end + + describe "protected methods" do + + describe ".connection_configuration" do + it "should provide main config by default" do + @class.send(:connection_configuration).should eql(@class.connection) + end + end + + describe ".load_connection_config_file" do + it "should provide an empty hash if config not found" do + @class.send(:load_connection_config_file).should eql({}) + end + it "should load file if available" do + @class.connection_config_file = File.join(FIXTURE_PATH, 'config', 'couchdb.yml') + puts @class.send(:connection_config_cache).inspect + hash = @class.send(:load_connection_config_file) + hash[:development].should_not be_nil + @class.server.uri.should eql("https://test:uesr@sample.cloudant.com:443") + end + + end + + end + + end + + +end diff --git a/spec/fixtures/config/couchdb.yml b/spec/fixtures/config/couchdb.yml new file mode 100644 index 0000000..8d04ff7 --- /dev/null +++ b/spec/fixtures/config/couchdb.yml @@ -0,0 +1,10 @@ + +development: + protocol: 'https' + host: sample.cloudant.com + port: 443 + prefix: project + suffix: text + username: test + password: user +