loquor 0.0.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.
@@ -0,0 +1,88 @@
1
+ # Loquor
2
+
3
+ [![Build Status](https://travis-ci.org/meducation/loquor.png)](https://travis-ci.org/meducation/loquor)
4
+ [![Dependencies](https://gemnasium.com/meducation/loquor.png?travis)](https://gemnasium.com/meducation/loquor)
5
+ [![Code Climate](https://codeclimate.com/github/meducation/loquor.png)](https://codeclimate.com/github/meducation/loquor)
6
+
7
+ Handles calls to the Meducation API via an ActiveRecord-style interface
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'loquor'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install loquor
22
+
23
+
24
+ ## Usage
25
+
26
+ You will want to set up some configuration variables.
27
+ ``` ruby
28
+ Loquor.config do |config|
29
+ config.access_id = "Username"
30
+ config.secret_key = "SecretKey1929292"
31
+ config.endpoint = "http://www.meducation.net"
32
+ end
33
+ ```
34
+
35
+ Now you make requests to get, create, update, destroy and list a range of objects, like this:
36
+
37
+ ```ruby
38
+ User.where(email: "jeremy@meducation.net").where(name: "Jeremy").each do |user|
39
+ p "The user with id ##{user['id']} is #{user['name']}."
40
+ end
41
+
42
+ User.find(2) # => {id: 2, name: "Jeremy Walker"}
43
+
44
+ User.create(name: "Jeremy Walker", email: "jeremy@meducation.net") # => {id: 2, name: "Jeremy Walker", email "jeremy@meducation.net"}
45
+ ```
46
+
47
+ ### Supported Objects
48
+
49
+ The following are currently endpoints are supported:
50
+ * Group Discussions
51
+ * Group Discussion Posts
52
+ * Media Files
53
+ * Users
54
+
55
+ ### Is it any good?
56
+
57
+ [Yes.](http://news.ycombinator.com/item?id=3067434)
58
+
59
+ ## Contributing
60
+
61
+ Firstly, thank you!! :heart::sparkling_heart::heart:
62
+
63
+ We'd love to have you involved. Please read our [contributing guide](https://github.com/meducation/loquor/tree/master/CONTRIBUTING.md) for information on how to get stuck in.
64
+
65
+ ### Contributors
66
+
67
+ This project is managed by the [Meducation team](http://company.meducation.net/about#team).
68
+
69
+ These individuals have come up with the ideas and written the code that made this possible:
70
+
71
+ - [Jeremy Walker](http://github.com/iHID)
72
+
73
+ ## Licence
74
+
75
+ Copyright (C) 2013 New Media Education Ltd
76
+
77
+ This program is free software: you can redistribute it and/or modify
78
+ it under the terms of the GNU Affero General Public License as published by
79
+ the Free Software Foundation, either version 3 of the License, or
80
+ (at your option) any later version.
81
+
82
+ This program is distributed in the hope that it will be useful,
83
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
84
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
85
+ GNU Affero General Public License for more details.
86
+
87
+ A copy of the GNU Affero General Public License is available in [Licence.md](https://github.com/meducation/loquor/blob/master/LICENCE.md)
88
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = "test/**/*_test.rb"
6
+ end
7
+
8
+ namespace :test do
9
+ Rake::TestTask.new(:local) do |t|
10
+ t.pattern = "test/{components/,services/,helpers/,}*_test.rb"
11
+ end
12
+ end
13
+
14
+ task default: :test
@@ -0,0 +1,37 @@
1
+ require 'rest-client'
2
+ require 'api-auth'
3
+ require 'filum'
4
+
5
+ require "loquor/version"
6
+ require "loquor/configuration"
7
+ require "loquor/client"
8
+ require 'loquor/path_builder'
9
+ require 'loquor/representation'
10
+ require 'loquor/representations'
11
+
12
+ require 'loquor/api_call'
13
+ require "loquor/http_action"
14
+
15
+ module Loquor
16
+ def self.config
17
+ if block_given?
18
+ yield loquor.config
19
+ else
20
+ loquor.config
21
+ end
22
+ end
23
+
24
+ def self.get(url)
25
+ loquor.get(url)
26
+ end
27
+
28
+ def self.post(url, payload)
29
+ loquor.post(url, payload)
30
+ end
31
+
32
+ private
33
+
34
+ def self.loquor
35
+ @loquor ||= Client.new
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ module Loquor
2
+ class ApiCall
3
+ include Loquor::PathBuilder
4
+ def initialize(path)
5
+ setup_path_builder(path)
6
+ end
7
+ end
8
+ end
9
+
10
+ require 'loquor/api_calls/show'
11
+ require 'loquor/api_calls/index'
@@ -0,0 +1,46 @@
1
+ module Loquor
2
+ class ApiCall::Index < ApiCall
3
+
4
+ attr_reader :criteria
5
+
6
+ def initialize(path)
7
+ super(path)
8
+ @criteria = {}
9
+ end
10
+
11
+ def where(value)
12
+ value.each do |key, value|
13
+ @criteria[key] = value
14
+ end
15
+ self
16
+ end
17
+
18
+ # Proxy everything to the results so that this this class
19
+ # transparently acts as an Array.
20
+ def method_missing(name, *args, &block)
21
+ results.send(name, *args, &block)
22
+ end
23
+
24
+ private
25
+
26
+ def results
27
+ if @results.nil?
28
+ @results = Loquor.get(generate_url)
29
+ end
30
+ @results
31
+ end
32
+
33
+ def generate_url
34
+ query_string = @criteria.map { |key,value|
35
+ if value.is_a?(String)
36
+ "#{key}=#{URI.encode(value)}"
37
+ elsif value.is_a?(Array)
38
+ "#{key}=[#{URI.encode(value.join(","))}]"
39
+ else
40
+ raise LoquorError.new("Filter values must be strings or arrays.")
41
+ end
42
+ }.join("&")
43
+ "#{build_path}?#{query_string}"
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ module Loquor
2
+ class ApiCall::Show < ApiCall
3
+
4
+ def initialize(path, id)
5
+ super(path)
6
+ @id = id
7
+ end
8
+
9
+ def execute
10
+ Loquor.get("#{build_path}/#{@id}")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Loquor
2
+ class Client
3
+ attr_reader :config
4
+
5
+ def initialize
6
+ @config = Configuration.new
7
+ end
8
+
9
+ def get(url)
10
+ deps = {config: @config}
11
+ HttpAction::Get.get(url, deps)
12
+ end
13
+
14
+ def post(url, payload)
15
+ deps = {config: @config}
16
+ HttpAction::Post.post(url, payload, deps)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ module Loquor
2
+ class LoquorError < StandardError
3
+ end
4
+ class LoquorConfigurationError < LoquorError
5
+ end
6
+
7
+ class Configuration
8
+
9
+ SETTINGS = [
10
+ :logger, :access_id, :secret_key, :endpoint
11
+ ]
12
+
13
+ attr_writer *SETTINGS
14
+
15
+ def initialize
16
+ Filum.config do |config|
17
+ config.logfile = "./log/loquor.log"
18
+ end
19
+ logger = Filum.logger
20
+ end
21
+
22
+ SETTINGS.each do |setting|
23
+ define_method setting do
24
+ get_or_raise(setting)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def get_or_raise(setting)
31
+ instance_variable_get("@#{setting.to_s}") ||
32
+ raise(LoquorConfigurationError.new("Configuration for #{setting} is not set"))
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,16 @@
1
+ module Loquor
2
+ class HttpAction
3
+ def initialize(url, deps)
4
+ @url = url
5
+ @config = deps[:config]
6
+ end
7
+
8
+ def signed_request
9
+ ApiAuth.sign!(request, @config.access_id, @config.secret_key)
10
+ end
11
+ end
12
+ end
13
+
14
+ require 'loquor/http_actions/get'
15
+ require 'loquor/http_actions/post'
16
+
@@ -0,0 +1,22 @@
1
+ module Loquor
2
+ class HttpAction::Get < HttpAction
3
+ def self.get(url, deps)
4
+ new(url, deps).get
5
+ end
6
+
7
+ def initialize(url, deps)
8
+ super
9
+ end
10
+
11
+ def get
12
+ JSON.parse(signed_request.execute)
13
+ end
14
+
15
+ private
16
+ def request
17
+ full_url = "#{@config.endpoint}#{@url}"
18
+ RestClient::Request.new(url: full_url, method: :get)
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,33 @@
1
+ module Loquor
2
+ class HttpAction::Post < HttpAction
3
+ def self.post(url, payload, deps)
4
+ new(url, payload, deps).post
5
+ end
6
+
7
+ def initialize(url, payload, deps)
8
+ super(url, deps)
9
+ @payload = payload
10
+ end
11
+
12
+ def post
13
+ JSON.parse(signed_request.execute)
14
+ end
15
+
16
+ private
17
+
18
+ def signed_request
19
+ signed_request = super
20
+ p signed_request # If you take this line out - it all breaks. Yeah...
21
+ signed_request
22
+ end
23
+
24
+ def request
25
+ full_url = "#{@config.endpoint}#{@url}"
26
+ RestClient::Request.new(url: full_url,
27
+ accept: :json,
28
+ payload: @payload.to_json,
29
+ headers: {'Content-type' => 'application/json'},
30
+ method: :post)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ module Loquor
2
+
3
+ class MissingUrlComponentError < LoquorError
4
+ def initialize(url_component)
5
+ @url_component = url_component
6
+ end
7
+
8
+ def message
9
+ "#{url_component} has not been set. Use Object.for_#{url_component}"
10
+ end
11
+ end
12
+
13
+ module PathBuilder
14
+ PATH_PART_REGEX = /:[a-z0-9_]+/
15
+
16
+ def setup_path_builder(path)
17
+ path.split('/').each do |path_part|
18
+ next unless path_part =~ PATH_PART_REGEX
19
+ path_part = path_part[1..-1]
20
+ method_name = "for_#{path_part}"
21
+
22
+ self.class.send :define_method, method_name do |id|
23
+ @path_parts ||= {}
24
+ @path_parts[path_part.to_sym] = id
25
+ self
26
+ end
27
+
28
+ self.class.class_eval <<-EOS
29
+ def self.#{method_name}(*args)
30
+ new.#{method_name}(*args)
31
+ end
32
+ EOS
33
+ end
34
+
35
+ self.class.send :define_method, :build_path do
36
+ path.gsub(PATH_PART_REGEX) do |path_part|
37
+ path_part = path_part[1..-1].to_sym
38
+ @path_parts ||= {}
39
+ @path_parts.fetch(path_part) { raise MissingUrlComponentError.new(path_part) }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ module Loquor
2
+ module Representation
3
+ module ClassMethods
4
+
5
+ [:find, :where].each do |proxy|
6
+ define_method proxy do |*args|
7
+ new.send proxy, *args
8
+ end
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+ def find(id)
14
+ ApiCall::Show.new(self.class.path, id).execute
15
+ end
16
+
17
+ def where(*args)
18
+ ApiCall::Index.new(self.class.path).where(*args)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ {
2
+ "Group::Discussion" => "/group/:group_id/discussions",
3
+ "Group::DiscussionPost" => "/group/:group_id/discussion",
4
+ "MediaFile" => "/media_files",
5
+ "User" => "/users"
6
+ }.each do |name, path|
7
+ klass = Class.new(Object) do
8
+ extend Loquor::Representation::ClassMethods
9
+ include Loquor::Representation::InstanceMethods
10
+
11
+ define_method :path do
12
+ path
13
+ end
14
+ end
15
+
16
+ # Split off the Group and Discussion parts
17
+ name_parts = name.split("::")
18
+ klass_name = name_parts.pop
19
+
20
+ # Create base modules
21
+ const = Loquor
22
+ name_parts.each do |name_part|
23
+ const.const_set name_part, Module unless const.const_defined?(name_part)
24
+ end
25
+
26
+ # Define the actual klass at the right point
27
+ const.const_set klass_name, klass
28
+ end