lib/leanback.rb in leanback-0.4.2 vs lib/leanback.rb in leanback-0.5.0

- old
+ new

@@ -1,614 +1,156 @@ require 'rest_client' -require 'yajl' -require 'erb' +require 'json/pure' +require 'active_support/all' -class CouchdbException < RuntimeError - attr :error - def initialize(error) - @error = error.values[0] - end -end - -module Couchdb - def self.couch_error(e) - raise e if e.is_a? OpenSSL::SSL::SSLError - hash = Yajl::Parser.parse(e.response.to_s) - raise CouchdbException.new(hash), "CouchDB: Error - #{hash.values[0]}. Reason - #{hash.values[1]}" - end - - def self.salt - o = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten; - salt = (0..50).map{ o[rand(o.length)] }.join; - end - - #change non-admin user password - def self.change_password(username, new_password,auth_session = "") - salty = salt() - password_sha = Digest::SHA1.hexdigest(new_password + salty) - user_id = 'org.couchdb.user:' + username - data = {"salt" => salty,"password_sha" => password_sha} - doc = { :database => '_users', :doc_id => user_id, :data => data} - update_doc doc,auth_session - end - - #add a new user - def self.add_user(user, auth_session="") - o = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten; - salt = (0..50).map{ o[rand(o.length)] }.join; - new_user = {:username => user[:username], :password => user[:password], :roles => user[:roles], :salt => salt} - create_user(new_user,auth_session) - end - - #create a new user - def self.create_user(user,auth_session= "") - password_sha = Digest::SHA1.hexdigest(user[:password] + user[:salt]) - - user_hash = { :type => "user", - :name => user[:username], - :password_sha => password_sha, - :salt => user[:salt], - :roles => user[:roles] - } - - str = Yajl::Encoder.encode(user_hash) - set_address - begin - response = RestClient.put "#{@address}:#{@port}/_users/org.couchdb.user:#{URI.escape(user[:username])}", str,{:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) - end - - end - - #add security object - def self.set_security(db_name, data,auth_session="") - security_data = Yajl::Encoder.encode(data) - set_address - begin - response = RestClient.put "#{@address}:#{@port}/#{URI.escape(db_name)}/_security/",security_data, {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) - end - end - - #get security object - def self.get_security(db_name, auth_session="") - set_address - begin - response = RestClient.get "#{@address}:#{@port}/#{URI.escape(db_name)}/_security/", {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) - end - end - - #login to couchdb - def self.login(username, password) - set_address - data = "name=#{username}&password=#{password}" - begin - response = RestClient.post "#{@address}:#{@port}/_session/", data, {:content_type => 'application/x-www-form-urlencoded'} - response.cookies - rescue => e - couch_error(e) +module Leanback + class InvalidDatabaseName < StandardError; end + class InvalidDocumentID < StandardError; end + class CouchdbException < StandardError + attr_reader :response + def initialize(response) + @response = response end end - - #couchdb configuration api - def self.set_config(data,auth_session = "") - section = data[:section] - key = data[:key] - value = data[:value] - json_data = Yajl::Encoder.encode(value) - set_address - begin - response = RestClient.put "#{@address}:#{@port}/_config/#{URI.escape(section)}/#{URI.escape(key)}",json_data, {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) + class Couchdb + attr_reader :address + attr_reader :port + attr_reader :username + attr_reader :password + attr_reader :database + def initialize(args = {}) + @database = args.fetch(:database, nil) + @address = args.fetch(:address, 'http://127.0.0.1') + @port = args.fetch(:port, '5984') + @username = args.fetch(:username, nil) + @password = args.fetch(:password, nil) end - end - - def self.delete_config(data,auth_session = "") - section = data[:section] - key = data[:key] - set_address - begin - response = RestClient.delete "#{@address}:#{@port}/_config/#{URI.escape(section)}/#{URI.escape(key)}", {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) + def create + api_request { RestClient.put "#{address_port}/#{db_uri}", content_type, cookies } end - end - - - def self.get_config(data,auth_session = "") - section = data[:section] - key = data[:key] - set_address - begin - response = RestClient.get "#{@address}:#{@port}/_config/#{URI.escape(section)}/#{URI.escape(key)}", {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) + def delete + api_request { RestClient.delete "#{address_port}/#{db_uri}", cookies } end - end - - #create a document - def self.create_doc( doc,auth_session = "") - db_name = doc[:database] - doc_id = doc[:doc_id] - data = doc[:data] - json_data = Yajl::Encoder.encode(data) - set_address - begin - response = RestClient.put "#{@address}:#{@port}/#{URI.escape(db_name)}/#{URI.escape(doc_id)}",json_data, {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) + def create_doc(doc_id, doc) + api_request { RestClient.put "#{address_port}/#{db_uri}/#{doc_uri(doc_id)}", generate_json(doc), cookies } end - end - - #edit a document - def self.edit_doc(doc,auth_session = "") - db_name = doc[:database] - doc_id = doc[:doc_id] - data = doc[:data] - json_data = Yajl::Encoder.encode(data) - set_address - begin - response = RestClient.put "#{@address}:#{@port}/#{URI.escape(db_name)}/#{URI.escape(doc_id)}", json_data, {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) + def delete_doc(doc_id, rev) + api_request { RestClient.delete "#{address_port}/#{db_uri}/#{doc_uri(doc_id)}?rev=#{rev}", cookies } end - end - - #update a doc - def self.update_doc(doc,auth_session = "") - db_name = doc[:database] - doc_id = doc[:doc_id] - data = doc[:data] - doc = {:database => db_name, :doc_id => doc_id} - options = Couchdb.view doc,auth_session - options = options.merge(data) - doc = {:database => db_name, :doc_id => doc_id, :data => options} - edit_doc doc,auth_session - end - - #delete document - def self.delete_doc(doc,auth_session = "") - db_name = doc[:database] - doc_id = doc[:doc_id] - doc = {:database => db_name, :doc_id => doc_id} - hash = Couchdb.view doc,auth_session - doc = {:database => db_name, :doc_id => doc_id, :rev => hash["_rev"]} - delete_rev(doc,auth_session) - end - - - #delete a doc by rev# - def self.delete_rev(doc,auth_session = "") - db_name = doc[:database] - doc_id = doc[:doc_id] - rev = doc[:rev] - set_address - begin - response = RestClient.delete "#{@address}:#{@port}/#{URI.escape(db_name)}/#{URI.escape(doc_id)}?rev=#{rev}", {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) + def delete_doc!(doc_id) + document = get_doc(doc_id) + delete_doc(document[:_id], document[:_rev]) end - end - - - #create a database if one with the same name doesn't already exist - def self.create(database_name,auth_session = "") - set_address - begin - response = RestClient.put "#{@address}:#{@port}/#{URI.escape(database_name)}", {:content_type => :json},{:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) + def get_doc(doc_id) + api_request { RestClient.get "#{address_port}/#{@database}/#{doc_id}", cookies } end - end - - #delete a database - def self.delete(database_name,auth_session = "") - set_address - begin - response = RestClient.delete "#{@address}:#{@port}/#{URI.escape(database_name)}", {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - couch_error(e) - end - end - - #return a list of all databases - def self.all(auth_session = "") - set_address - begin - response = RestClient.get "#{@address}:#{@port}/_all_dbs", {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rescue => e - raise e + def update_doc(doc_id, data) + api_request { RestClient.put "#{address_port}/#{db_uri}/#{doc_uri(doc_id)}", generate_json(data), cookies } end - end - - ##view a document - def self.view(doc,auth_session = "", options = {}) - set_address - db_name = doc[:database] - doc_id = doc[:doc_id] - begin - response = RestClient.get "#{@address}:#{@port}/#{db_name}/#{doc_id}",{:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str, symbolize_keys(options)) - rescue => e - couch_error(e) - end - end - - def self.get_params(options) - params = "" - if options.has_key?(:startkey) - if options[:startkey].is_a? String - params = 'startkey="' + options[:startkey] + '"' - else - params = 'startkey=' + options[:startkey].to_s # for complex keys - end + def edit_doc!(doc_id, data) + document = get_doc(doc_id) + document_with_rev = document.merge(data) + update_doc(doc_id, document_with_rev) end - if options.has_key?(:endkey) - if options[:endkey].is_a? String - params = params + '&endkey="' + options[:endkey] + '"' - else - params = params + '&endkey=' + options[:endkey].to_s #for complex keys - end + def view(design_doc_name, view_name, options = {}) + api_request { RestClient.get "#{address_port}/#{db_uri}/#{URI.escape(design_doc_name)}/_view/#{URI.escape(view_name)}?#{options.to_query}", cookies } end - - if options.has_key?(:limit) - params = params + "&" + "limit=" + options[:limit].to_s + def where(hash, options = {}) + search_term = hash.values + index = hash.keys.join("_") + new_options = options.merge({startkey: search_term.to_s, endkey: search_term.to_s}) + view!("_design/#{index}_keys_finder", "find_by_keys_#{index}", new_options) + rescue CouchdbException => e + add_multiple_finder(hash.keys) + view!("_design/#{index}_keys_finder", "find_by_keys_#{index}", new_options) end - - if options.has_key?(:skip) - params = params + "&" + "skip=" + options[:skip].to_s + def security_object=(security_settings) + api_request { RestClient.put "#{address_port}/#{db_uri}/_security/", generate_json(security_settings), cookies } end - - if options.has_key?(:descending) - params = params + "&" + "descending=true" + def security_object + api_request { RestClient.get "#{address_port}/#{db_uri}/_security/", cookies } end - - return params - end - - - #query a permanent view - def self.find(doc,auth_session = "", key=nil, options = {}) - set_address - db_name = doc[:database] - design_doc_name = doc[:design_doc] - view_name = doc[:view] - params = get_params(options) - - begin - if key == nil - response = RestClient.get "#{@address}:#{@port}/#{db_name}/_design/#{design_doc_name}/_view/#{view_name}?#{URI.escape(params)}",{:cookies => {"AuthSession" => auth_session}} - else - key = URI.escape('?key="' + key + '"') - response = RestClient.get "#{@address}:#{@port}/#{db_name}/_design/#{design_doc_name}/_view/#{view_name}#{key}&#{URI.escape(params)}" ,{:cookies => {"AuthSession" => auth_session}} - end - hash = Yajl::Parser.parse(response.to_str, symbolize_keys(options)) - data = [] - rows = hash["rows"] unless hash["rows"].nil? - rows = hash[:rows] unless hash[:rows].nil? - rows.each do |row| - value = row["value"] unless row["value"].nil? - value = row[:value] unless row[:value].nil? - data << value - end - return data - rescue => e - couch_error(e) - end - end - - #create a design document with views - def self.create_design(doc,auth_session = "") - set_address - db_name = doc[:database] - design_doc_name = doc[:design_doc] - json_doc_name = doc[:json_doc] - - begin - #bind json doc to string - message_template = ERB.new File.new(json_doc_name).read - str = message_template.result(binding) - rescue => e - raise e + def set_config(section, key, value) + !!(RestClient.put "#{address_port}/_config/#{URI.escape(section)}/#{URI.escape(key)}", value, cookies) end - - begin - response = RestClient.put "#{@address}:#{@port}/#{db_name}/_design/#{design_doc_name}", str, {:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) + def get_config(section, key) + RestClient.get "#{address_port}/_config/#{URI.escape(section)}/#{URI.escape(key)}", cookies rescue => e - couch_error(e) + raise_error(e) end - end - - #Query view, create view on fly if it dosen't already exist - def self.find_on_fly(doc,auth_session = "",key = nil, options = {}) - db_name = doc[:database] - design_doc = doc[:design_doc] - view = doc[:view] - json_doc = doc[:json_doc] - query_info = {:database => db_name, :design_doc => design_doc, :view => view} - begin - if( key == nil) - docs = find(query_info,auth_session,key=nil,options) - else - docs = find(query_info,auth_session,key,options) - end - rescue CouchdbException => e - document = { :database => db_name, :design_doc => design_doc, :json_doc => json_doc} - create_design document,auth_session - if( key == nil) - docs = find(query_info,auth_session,key=nil,options) - else - docs = find(query_info,auth_session,key,options) - end - end - return docs - end - - - #add a finder method to the database - #this creates a find by key method - def self.add_finder(options,auth_session = "") - set_address - db_name = options[:database] - key = options[:key] - design_doc_name = key + '_finder' - - view ='{ - "language" : "javascript", - "views" :{ - "find_by_'+key+'" : { - "map" : "function(doc){ if(doc.'+key+') emit(doc.'+key+',doc);}" - } - } - }' - - begin - response = RestClient.put "#{@address}:#{@port}/#{db_name}/_design/#{design_doc_name}", view, {:cookies => {"AuthSession" => auth_session}} + def delete_config(section, key) + !!(RestClient.delete "#{address_port}/_config/#{URI.escape(section)}/#{URI.escape(key)}", cookies) rescue => e - couch_error(e) + raise_error(e) end - end - - #add a multiple finder method to the database - #this creates a find by keys method - def self.add_multiple_finder(options,auth_session = "") - set_address - db_name = options[:database] - keys = options[:keys] - view_name = keys.join("_") - key = keys.join(",doc.") - condition = keys.join(" && doc.") - design_doc_name = "#{view_name}_keys_finder" - - view = '{ - "language" : "javascript", - "views" :{ - "find_by_keys_'+view_name+'" : { - "map" : "function(doc){ if(doc.'+condition+') emit([doc.'+key+'],doc);}" - } - } - }' - - begin - response = RestClient.put "#{@address}:#{@port}/#{db_name}/_design/#{design_doc_name}", view, {:cookies => {"AuthSession" => auth_session}} - rescue => e - couch_error(e) + private + def view!(design_doc_name, view_name, options = {}) + new_options = options.merge({include_docs: true}) + result = view(design_doc_name, view_name, new_options) + rows = result[:rows] + rows.map { |row| row[:doc] } end - end - - #add a multiple counter method to the database - #this creates a count by keys method - def self.add_multiple_counter(options,auth_session = "") - set_address - db_name = options[:database] - keys = options[:keys] - view_name = keys.join("_") - key = keys.join(",doc.") - condition = keys.join(" && doc.") - design_doc_name = "#{view_name}_keys_counter" - view = '{ - "language" : "javascript", - "views" :{ - "count_by_keys_'+view_name+'" : { - "map" : "function(doc){ if(doc.'+condition+') emit([doc.'+key+'],null);}", "reduce": "_count" - } - } - }' - - begin - response = RestClient.put "#{@address}:#{@port}/#{db_name}/_design/#{design_doc_name}", view, {:cookies => {"AuthSession" => auth_session}} - rescue => e - couch_error(e) + def add_multiple_finder(keys) + view_name = keys.join("_") + condition = keys.join(" && doc.") + key = keys.join(",doc.") + design_doc_name = "#{view_name}_keys_finder" + design_doc = { + language: "javascript", + views: { + "find_by_keys_#{view_name}" => { + map: "function(doc){ if(doc.#{condition}) emit([doc.#{key}]);}" + } + } + } + create_doc "_design/#{design_doc_name}", design_doc end - end - - #add a counter method to the database - #this creates a count method that counts documents by key - def self.add_counter(options,auth_session = "") - set_address - db_name = options[:database] - key = options[:key] - design_doc_name = key + '_counter' - - view ='{ - "language" : "javascript", - "views" :{ - "count_'+key+'" : { - "map" : "function(doc){ if(doc.'+key+') emit(doc.'+key+',null);}", "reduce": "_count" - } - } - }' - - begin - response = RestClient.put "#{@address}:#{@port}/#{db_name}/_design/#{design_doc_name}", view, {:cookies => {"AuthSession" => auth_session}} + def api_request + response = yield + parse_json(response) rescue => e - couch_error(e) + raise_error(e) end - end - - #count by key - def self.count(options,auth_session = "") - set_address - db_name = options[:database] - index = options.keys[1].to_s - search_term = options.values[1] - design_doc_name = "#{index}_counter" - view_name = "count_#{index}" - - begin - view = { :database => db_name, :design_doc => design_doc_name, :view => view_name} - docs = find view,auth_session,search_term - rescue CouchdbException => e - #add a counter index if one doesn't already exist in the database - #then count_by_key - add_counter({:database => db_name, :key => index},auth_session) - docs = find view,auth_session,search_term + def doc_uri(doc_id) + error_message = "Invalid Document ID: #{doc_id}" + raise(Leanback::InvalidDocumentID.new(error_message), error_message) unless doc_id.is_a?(String) + URI.escape(doc_id) end - count = docs[0] - return count.to_i - end - - #count by keys - #example: - #hash = {:firstname =>'john', :gender => 'male' } - #Couchdb.count_by_keys({:database => 'contacts', :keys => hash},auth_session) - def self.count_by_keys(options,auth_session = "", params = {}) - set_address - db_name = options[:database] - keys = [] - search_term = [] - hash = options[:keys] - - hash.each do |k,v| - keys << k - search_term << v + def db_uri + error_message = "Invalid database name: #{@database}" + raise(Leanback::InvalidDatabaseName.new(error_message), error_message) unless @database.is_a?(String) + URI.escape(@database) end - index = keys.join("_") - design_doc_name = "#{index}_keys_counter" - view_name = "count_by_keys_#{index}" - params[:startkey] = search_term - params[:endkey] = search_term - - begin - view = { :database => db_name, :design_doc => design_doc_name, :view => view_name} - docs = find view,auth_session,key=nil,params - rescue CouchdbException => e - #add a finder/index if one doesn't already exist in the database - #then find_by_keys - add_multiple_counter({:database => db_name, :keys => keys},auth_session) - docs = find view,auth_session,key=nil,params - end - count = docs[0] - return count.to_i - end - - #find by key - def self.find_by(options,auth_session = "", params = {}) - set_address - db_name = options[:database] - index = options.keys[1].to_s - search_term = options.values[1] - design_doc_name = "#{index}_finder" - view_name = "find_by_#{index}" - - begin - view = { :database => db_name, :design_doc => design_doc_name, :view => view_name} - docs = find view,auth_session,search_term,params - rescue CouchdbException => e - #add a finder/index if one doesn't already exist in the database - #then find_by_key - add_finder({:database => db_name, :key => index},auth_session) - docs = find view,auth_session,search_term,params + def content_type + {content_type: :json} end - return docs - end - - #find by keys - #example: - #hash = {:firstname =>'john', :gender => 'male' } - #Couchdb.find_by_keys({:database => 'contacts', :keys => hash},auth_session) - def self.find_by_keys(options,auth_session = "", params = {}) - set_address - db_name = options[:database] - keys = [] - search_term = [] - hash = options[:keys] - - hash.each do |k,v| - keys << k - search_term << v + def cookies + {cookies: {"AuthSession" => auth_session}} end - index = keys.join("_") - design_doc_name = "#{index}_keys_finder" - view_name = "find_by_keys_#{index}" - params[:startkey] = search_term - params[:endkey] = search_term - - begin - view = { :database => db_name, :design_doc => design_doc_name, :view => view_name} - docs = find view,auth_session,key=nil,params - rescue CouchdbException => e - #add a finder/index if one doesn't already exist in the database - #then find_by_keys - add_multiple_finder({:database => db_name, :keys => keys},auth_session) - docs = find view,auth_session,key=nil,params + def address_port + "#{@address}:#{@port}" end - return docs - end - - #return a list of all docs in the database - def self.docs_from(database_name,auth_session = "") - set_address - begin - response = RestClient.get "#{@address}:#{@port}/#{URI.escape(database_name)}/_all_docs?include_docs=true",{:cookies => {"AuthSession" => auth_session}} - hash = Yajl::Parser.parse(response.to_str) - rows = hash["rows"] - count = 0 - rows.each do |row| - rows[count] = row["doc"] - count += 1 + def parse_json(json_doc) + JSON.parse(json_doc, symbolize_names: true) + end + def generate_json(data) + JSON.generate(data) + end + def raise_error(exeception) + if exeception.respond_to?('response') + response = parse_json(exeception.response) + raise(CouchdbException.new(response), response) + else + raise(exeception) end - return rows + end + def auth_session + return "" if @username.nil? && @password.nil? + data = "name=#{@username}&password=#{@password}" + response = RestClient.post "#{@address}:#{@port}/_session/", data, {content_type: 'application/x-www-form-urlencoded'} + hash = response.cookies + hash["AuthSession"] rescue => e - couch_error(e) - end - end - - def self.symbolize_keys(options) - return {symbolize_keys: true} if options[:symbolize_keys] - return {} - end - - private_class_method :symbolize_keys - - class << self - attr_accessor :address - attr_accessor :port - - def set_address() - if @address == nil - @address = 'http://127.0.0.1' - end - if @port == nil - @port = '5984' - end + raise_error(e) end end end