remote_resource 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 06c0f5f8b3a5ab36d0a2545bc7356d46652a9d09
4
+ data.tar.gz: e9c83ef1c274f63439dc1c117cb987086e4ba4ec
5
+ SHA512:
6
+ metadata.gz: 64d28b4b2a7a8e4080c64c475049a6c365bf08dbf3671a7a46c303f2c28fcc68f3ae26dc65ef165387c86aa8783d97324685636b98a47bed493df2bf966a5a0b
7
+ data.tar.gz: a982c4ad71b391459e9ab8128727017a01c1d66030c7b4a32073d2b605402d458b28a44d4d54913b3f1d7fc569d7f0d4f0e8ff137a7aa85bb9ab3145676e5173
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /.idea
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ *.bundle
12
+ *.so
13
+ *.o
14
+ *.a
15
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in remote_resource.gemspec
4
+ gemspec
5
+
6
+ gem 'activesupport', '~> 4'
7
+ gem 'activemodel', '~> 4'
8
+ gem 'nokogiri'
9
+ gem 'httparty'
10
+ gem 'chronic', require: false
11
+
12
+ group :test do
13
+ gem 'rspec', '~> 3.1.0'
14
+ gem 'webmock'
15
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 dizer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # RemoteResource
2
+
3
+
4
+
5
+ ## Remote::Html::Model
6
+ Object interface over remote HTML (and JSON) resources.
7
+
8
+ ### Example
9
+
10
+ For example you have ```http://example.com/companies/a``` with this content:
11
+
12
+ ```html
13
+ <html>
14
+ <body>
15
+ <h1>Company A</h1>
16
+ <div class="desc">Lovely company</div>
17
+ </body>
18
+ </html>
19
+ ```
20
+
21
+ First, define Company model:
22
+
23
+ ```ruby
24
+ class Company < RemoteResource::Html::Model
25
+ attr_accessor :name, :description
26
+
27
+ mapping do |doc|
28
+ self.name = doc.c('h1')
29
+ self.description = doc.c('.desc')
30
+ end
31
+ end
32
+ ```
33
+
34
+ Now you can make requests:
35
+
36
+ ```ruby
37
+ company = Company.path('http://example.com/companies/a').find
38
+ company.name # => "Company A"
39
+ company.description # => "Lovely company"
40
+ ```
41
+
42
+ #### Document maps
43
+
44
+ ```mapping``` describes how to get attributes from html:
45
+
46
+ ```ruby
47
+ class Company < RemoteResource::Html::Model
48
+ attr_accessor :name, :description
49
+
50
+ mapping do |doc|
51
+ self.name = doc.c('h1')
52
+ self.description = doc.c('.desc')
53
+ end
54
+
55
+ mapping :fake_name do |doc|
56
+ self.name = 'Fake'
57
+ self.description = doc.c('.desc')
58
+ end
59
+ end
60
+
61
+ company = Company.path('http://example.com/companies/a').find(:fake_name)
62
+ company.name # => "Fake"
63
+ company.description # => "Lovely company"
64
+ ```
65
+
66
+ ##### Helpers
67
+
68
+ Inside document_map you can use special helpers:
69
+
70
+ ```ruby
71
+ c(selector) # Get content of html node by css selector
72
+ a(attribute, selector) # Get tag attribute by css selector
73
+ parse_date(string) # Get date, uses chronic gem to parse complex dates
74
+ ```
75
+
76
+ #### Collections
77
+
78
+ .find (and its alias .first) returns only first occurrence, meanwhile .all returns Array of all elements.
79
+ ```http://example.com/companies```:
80
+
81
+ ```html
82
+ <html>
83
+ <body>
84
+ <div class="company">
85
+ <h1>Company A</h1>
86
+ <div class="desc">Lovely company</div>
87
+ </div>
88
+ <div class="company">
89
+ <h1>Company B</h1>
90
+ <div class="desc">Worst company ever</div>
91
+ </div>
92
+ </body>
93
+ </html>
94
+ ```
95
+
96
+ Collection of companies:
97
+
98
+ ```ruby
99
+ companies = Company.path('http://example.com/companies').separate(css: '.company').all # => [<Company...>, <Company...>]
100
+ companies.first.name # => "Company A"
101
+ companies.last.name # => "Company B"
102
+ ```
103
+
104
+ ##### Pagination example
105
+ Pagination can be performed with multiple ways, for example:
106
+
107
+ ```ruby
108
+ class Company < RemoteResource::Html::Model
109
+ # ...
110
+ module RelationMethods
111
+ def page(n)
112
+ query(limit: 100, offset: n.to_i * 100)
113
+ end
114
+ end
115
+ end
116
+
117
+ Company.path('http://example.com/companies').page(0).all
118
+ # will perform request to http://example.com/companies?limit=100&offset=0
119
+ ```
120
+
121
+ ## Installation
122
+
123
+ Add this line to your application's Gemfile:
124
+
125
+ ```ruby
126
+ gem 'remote_resource'
127
+ ```
128
+
129
+ And then execute:
130
+
131
+ $ bundle
132
+
133
+ Or install it yourself as:
134
+
135
+ $ gem install remote_resource
136
+
137
+ ## Usage
138
+
139
+ TODO: Write usage instructions here
140
+
141
+ ## Contributing
142
+
143
+ 1. Fork it ( https://github.com/[my-github-username]/remote_resource/fork )
144
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
145
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
146
+ 4. Push to the branch (`git push origin my-new-feature`)
147
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,26 @@
1
+ module RemoteResource::Concerns::Attributes
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include ActiveModel::Serialization
6
+ end
7
+
8
+ def attributes
9
+ self.class.attributes
10
+ end
11
+
12
+ def to_localone_hash
13
+ serializable_hash
14
+ end
15
+
16
+ module ClassMethods
17
+ def attr_accessor(*vars)
18
+ @attributes = (@attributes || []) + vars
19
+ super
20
+ end
21
+
22
+ def attributes
23
+ Hash[(@attributes || []).map{|e| [e, nil]}]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module RemoteResource::Concerns::Mappings
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ class << self
6
+ attr_accessor :mappings
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def mapping(name = :default, &block)
12
+ @mappings ||= {}
13
+ @mappings[name] = block
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,62 @@
1
+ module RemoteResource::PartyQuery
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include HTTParty
6
+ end
7
+
8
+ def query(path, options={})
9
+ result = nil
10
+ query_options = default_options.deep_merge(options.compact)
11
+ time = Benchmark.ms do
12
+ response = case options.delete(:http_method)
13
+ when :post
14
+ post(path, query_options)
15
+ else
16
+ get(path, query_options)
17
+ end
18
+ raise ResponseException.new(response.code, response) if response.code != 200
19
+ result = response.parsed_response
20
+ end
21
+ result
22
+ ensure
23
+ logger.debug "(#{time.try(:round, 1) || 'Failed'} ms) #{path} opts: #{query_options.except(:logger, :log_level, :log_format)}" if logger
24
+ end
25
+
26
+ class ResponseException < StandardError
27
+ attr_accessor :code
28
+ def initialize(code, msg, &block)
29
+ @code = code
30
+ super(["HTTP code: #{code}", msg].join('. '), &block)
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def default_options
37
+ {
38
+ headers: {
39
+ 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36',
40
+ 'Accept-Language' => 'en-US,en;q=0.8',
41
+ },
42
+ logger: logger,
43
+ log_format: :apache
44
+ }
45
+ end
46
+
47
+ def get(path, options={}, &block)
48
+ self.class.get(path, options, &block)
49
+ end
50
+
51
+ def post(path, options={}, &block)
52
+ self.class.post(path, options, &block)
53
+ end
54
+
55
+ def logger
56
+ defined?(Rails) ? Rails.logger : nil
57
+ end
58
+
59
+ def debug?
60
+ false
61
+ end
62
+ end
@@ -0,0 +1,52 @@
1
+ module RemoteResource::Concerns::Relation
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ extend SingleForwardable
6
+ end
7
+
8
+ module ClassMethods
9
+ def relation
10
+ @relation ||= RemoteResource::Model::Relation.for_model(self)
11
+ end
12
+
13
+ def delegate_to_relation(*methods)
14
+ @delegated_to_relation ||= []
15
+ @delegated_to_relation += methods
16
+ single_delegate methods => :relation
17
+ end
18
+
19
+ def delegated_to_relation
20
+ collect_from_superclasses(:@delegated_to_relation)
21
+ end
22
+
23
+ def delegate_to_relation_merged(*methods)
24
+ @delegated_to_relation_merged ||= []
25
+ @delegated_to_relation_merged += methods
26
+ delegate_to_relation(*methods)
27
+ end
28
+
29
+ def delegated_to_relation_merged
30
+ collect_from_superclasses(:@delegated_to_relation_merged)
31
+ end
32
+
33
+ def delegate_from_relation(*methods)
34
+ @delegated_from_relation ||= []
35
+ @delegated_from_relation += methods
36
+ end
37
+
38
+ def delegated_from_relation
39
+ collect_from_superclasses(:@delegated_from_relation)
40
+ end
41
+
42
+ def collect_from_superclasses(variable_name)
43
+ methods = []
44
+ klass = self
45
+ while klass do
46
+ methods += Array.wrap(klass.instance_variable_get(variable_name))
47
+ klass = klass.superclass
48
+ end
49
+ methods
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module RemoteResource::Concerns
2
+
3
+ end
@@ -0,0 +1,28 @@
1
+ require 'remote_resource/concerns/party_query'
2
+
3
+ class RemoteResource::Connection
4
+ include RemoteResource::PartyQuery
5
+
6
+ def request(path, options={})
7
+ separate = options[:separate]
8
+
9
+ if options[:unwrap]
10
+ original_parser = self.class.parser
11
+ options[:parser] = Proc.new { |body|
12
+ body = options[:unwrap].call(body)
13
+ original_parser.call(body)
14
+ }
15
+ end
16
+
17
+ result = query(path, options)
18
+ Array.wrap(
19
+ if separate.try(:[], :json)
20
+ separate[:json].inject(result) { |r, p| r.try(:[], p.to_s) }
21
+ elsif separate.try(:[], :css)
22
+ result.css(separate[:css])
23
+ else
24
+ result
25
+ end
26
+ )
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ class RemoteResource::DocumentWrapper < SimpleDelegator
2
+ def css_content(selector)
3
+ selected = at_css(selector).try(:clone)
4
+ return unless selected
5
+ selected.css('br').each{ |br| br.replace "\n" }
6
+ selected.try(:content).to_s.strip
7
+ end
8
+
9
+ alias :c :css_content
10
+
11
+ def tag_attribute(attribute, selector)
12
+ at_css(selector).try(:[], attribute).to_s
13
+ end
14
+
15
+ alias :a :tag_attribute
16
+
17
+ def css_self_content(selector)
18
+ at_css(selector).try(:xpath, 'text()').to_s.strip
19
+ end
20
+
21
+ alias :c_self :css_self_content
22
+
23
+ def parse_date(date, options={})
24
+ if options.any? && options.delete(:chronic) != false
25
+ require 'chronic'
26
+ Chronic.parse(date.to_s, {guess: :begin, context: :past}.merge(options))
27
+ else
28
+ begin
29
+ Time.zone.parse(date.to_s)
30
+ rescue ArgumentError
31
+ nil
32
+ end
33
+ end
34
+ end
35
+
36
+ # abs_url('http://example.com', 'path?param=1')
37
+ # => 'http://example.com/path?param=1'
38
+ #
39
+ # abs_url('ftp://sub.domain.dev/other_path?other_param=2', 'path?param=1#anchor')
40
+ # => 'ftp://sub.domain.dev/path?param=1#anchor'
41
+ def abs_url(base, path)
42
+ abs = URI(base)
43
+ rel = URI(path)
44
+ rel.scheme = abs.scheme
45
+ rel.host = abs.host
46
+ rel.to_s
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ class RemoteResource::Html::Connection < RemoteResource::Connection
2
+ parser Proc.new { |body| Nokogiri::HTML(body) }
3
+ end
@@ -0,0 +1,7 @@
1
+ class RemoteResource::Html::Model < RemoteResource::Model
2
+ class << self
3
+ def connection
4
+ @connection ||= RemoteResource::Html::Connection.new
5
+ end
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ module RemoteResource::Html; end
@@ -0,0 +1,7 @@
1
+ class RemoteResource::Json::Connection < RemoteResource::Connection
2
+ parser Proc.new { |body| ActiveSupport::JSON.decode(body) }
3
+
4
+ def default_options
5
+ super.except(:logger)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class RemoteResource::Json::Model < RemoteResource::Model
2
+ class << self
3
+ def connection
4
+ @connection ||= RemoteResource::Json::Connection.new
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,99 @@
1
+ class RemoteResource::Model::Relation
2
+
3
+ attr_reader :model_class
4
+ attr_accessor :attributes
5
+
6
+ def initialize(model_class, attributes={})
7
+ @model_class = model_class
8
+ self.attributes = attributes
9
+ end
10
+
11
+ def method_missing(meth, *args, &block)
12
+ if model_class.delegated_to_relation.try(:include?, meth)
13
+ if args.count > 0
14
+ arg = if model_class.delegated_to_relation_merged.try(:include?, meth)
15
+ (attributes[meth] || {}).merge(args.first)
16
+ else
17
+ args.first
18
+ end
19
+ self.class.for_model(model_class, attributes.merge(meth => arg))
20
+ else
21
+ attributes[meth]
22
+ end
23
+
24
+ elsif model_class.delegated_from_relation.try(:include?, meth)
25
+ options = args.extract_options!
26
+ model_class.send(meth, *args, options.merge(attributes), &block)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def relation
33
+ self
34
+ end
35
+
36
+ def all(options={})
37
+ raise '`path` for query not specified' unless attributes[:path]
38
+ results = model_class.connection.request(
39
+ attributes[:path],
40
+ separate: attributes[:separate],
41
+ http_method: attributes[:via],
42
+ query: attributes[:query],
43
+ body: attributes[:body],
44
+ cookies: attributes[:cookies],
45
+ headers: attributes[:headers],
46
+ unwrap: attributes[:unwrap],
47
+ )
48
+ results.take(attributes[:limit] || results.count).map do |result_row|
49
+ build(result_row, options)
50
+ end
51
+ end
52
+
53
+ def first(options={})
54
+ limit(1).all(options).first
55
+ end
56
+
57
+ alias :find :first
58
+
59
+ def build(doc, options={}, &block)
60
+ model = model_class.new(url: attributes[:path])
61
+ document = RemoteResource::DocumentWrapper.new(doc)
62
+
63
+ mapping_name = options[:mapping] || :default
64
+ mapping = model_class.mappings[mapping_name]
65
+ raise "Mapping `#{mapping_name}` not found" unless mapping
66
+
67
+ model.instance_exec(document, &mapping)
68
+ instance_exec(model, &block) if block_given?
69
+ model
70
+ end
71
+
72
+ def on_all_pages(all_options={}, &block)
73
+ all_entities = []
74
+ begin
75
+ page = (page || -1) + 1
76
+ remaining_limit = attributes[:limit] ? attributes[:limit] - all_entities.count : nil
77
+ entities = block_given? ? instance_exec(page, &block) : limit(remaining_limit).page(page).all(all_options)
78
+ all_entities += entities if entities.any?
79
+ end while entities.any?
80
+ all_entities.compact
81
+ end
82
+
83
+ def on_pages(urls, all_options={})
84
+ on_all_pages do |n|
85
+ if urls[n]
86
+ path(urls[n]).all(all_options)
87
+ else
88
+ []
89
+ end
90
+ end
91
+ end
92
+
93
+ def self.for_model(model, attributes={})
94
+ rel = RemoteResource::Model::Relation.new(model, attributes)
95
+ rel.send(:extend, "#{model.name}::RelationMethods".constantize) if model.const_defined?(:RelationMethods)
96
+ rel
97
+ end
98
+
99
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_support/all'
2
+ require 'active_model'
3
+ require 'httparty'
4
+ require 'nokogiri'
5
+
6
+ require 'remote_resource/connection'
7
+ require 'remote_resource/document_wrapper'
8
+
9
+ require 'remote_resource/concerns'
10
+ require 'remote_resource/concerns/attributes'
11
+ require 'remote_resource/concerns/mappings'
12
+ require 'remote_resource/concerns/relation'
13
+
14
+
15
+ class RemoteResource::Model
16
+ include ActiveModel::Model
17
+ include RemoteResource::Concerns::Attributes
18
+ include RemoteResource::Concerns::Relation
19
+ include RemoteResource::Concerns::Mappings
20
+
21
+ attr_accessor :url
22
+
23
+ delegate_to_relation :path, :separate, :via, :unwrap, :limit
24
+ delegate_to_relation_merged :query, :body, :cookies, :headers
25
+
26
+ def connection
27
+ self.class.connection
28
+ end
29
+
30
+ class << self
31
+ def connection
32
+ @connection ||= RemoteResource::Connection.new
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module RemoteResource
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'remote_resource/version'
2
+ require 'remote_resource/model'
3
+
4
+ require 'remote_resource/model/relation'
5
+
6
+ require 'remote_resource/html'
7
+ require 'remote_resource/html/connection'
8
+ require 'remote_resource/html/model'
9
+
10
+ module RemoteResource
11
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'remote_resource/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "remote_resource"
8
+ spec.version = RemoteResource::VERSION
9
+ spec.authors = ["dizer"]
10
+ spec.email = ["dizeru@gmail.com"]
11
+ spec.summary = %q{Access remote resources with OOP interface}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.7"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ module TestCase
4
+ class Company < RemoteResource::Html::Model
5
+ attr_accessor :name
6
+
7
+ mapping do |document|
8
+ self.name = document.c('h1')
9
+ end
10
+ end
11
+
12
+ class User < RemoteResource::Html::Model
13
+ attr_accessor :name, :link
14
+
15
+ mapping do |document|
16
+ self.name = document.c('a')
17
+ self.link = document.a('href', 'a')
18
+ end
19
+
20
+ mapping :only_name do |document|
21
+ self.name = document.c('a')
22
+ end
23
+ end
24
+ end
25
+
26
+ describe RemoteResource::Html::Model do
27
+
28
+ let(:url) { 'http://example.com/companies/a' }
29
+ let(:query) { TestCase::User.path(url).separate(css: 'li') }
30
+
31
+ before do
32
+ stub_request(:get, url).
33
+ to_return(status: 200, body: '
34
+ <body>
35
+ <h1>Company A</h1>
36
+ <ul class="users">
37
+ <li><a href="users/1">User 1</a></li>
38
+ <li><a href="users/2">User 2</a></li>
39
+ </ul>
40
+ </body>'
41
+ )
42
+ end
43
+
44
+ context '#all' do
45
+ subject { query.all }
46
+
47
+ it do
48
+ expect(subject).to all(be_instance_of(TestCase::User))
49
+ end
50
+
51
+ it do
52
+ expect(subject.count).to eq(2)
53
+ end
54
+
55
+ context 'instance' do
56
+ let(:user) { subject.first }
57
+ it do
58
+ expect(user.name).to eq('User 1')
59
+ expect(user.link).to eq('users/1')
60
+ end
61
+ end
62
+
63
+ context 'another mapping' do
64
+ it do
65
+ expect(query.all(mapping: :only_name)).to all(be_instance_of(TestCase::User))
66
+ expect(query.all(mapping: :only_name).count).to eq(2)
67
+ end
68
+
69
+ context 'instance' do
70
+ let(:user) { query.first(mapping: :only_name) }
71
+ it do
72
+ expect(user.name).to eq('User 1')
73
+ expect(user.link).to eq(nil)
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'limit' do
79
+ it do
80
+ expect(query.limit(nil).all.count).to eq(2)
81
+ expect(query.limit(0).all.count).to eq(0)
82
+ expect(query.limit(1).all.count).to eq(1)
83
+ expect(query.limit(2).all.count).to eq(2)
84
+ end
85
+ end
86
+ end
87
+
88
+ context '#first' do
89
+ subject { query.first }
90
+
91
+ it do
92
+ expect(subject).to be_instance_of(TestCase::User)
93
+ end
94
+
95
+ it do
96
+ expect(subject.name).to eq('User 1')
97
+ end
98
+ end
99
+
100
+ context 'relation' do
101
+ it do
102
+ expect(RemoteResource::Html::Model.path('a')).to be_instance_of(RemoteResource::Model::Relation)
103
+ end
104
+
105
+ it do
106
+ expect(RemoteResource::Html::Model.path('a').attributes).to eq(path: 'a')
107
+ expect(RemoteResource::Html::Model.path('a').path('b').attributes).to eq(path: 'b')
108
+ expect(RemoteResource::Html::Model.path('a').path).to eq('a')
109
+ end
110
+ end
111
+
112
+ context 'mappings' do
113
+ it do
114
+ expect(TestCase::User.mappings).to have_key(:default)
115
+ expect(TestCase::User.mappings).to have_key(:only_name)
116
+ end
117
+ end
118
+
119
+ end
@@ -0,0 +1,6 @@
1
+ require 'webmock/rspec'
2
+ require File.expand_path('../../lib/remote_resource', __FILE__)
3
+
4
+ RSpec.configure do |config|
5
+ config.order = 'random'
6
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remote_resource
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - dizer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description:
42
+ email:
43
+ - dizeru@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - lib/remote_resource.rb
55
+ - lib/remote_resource/concerns.rb
56
+ - lib/remote_resource/concerns/attributes.rb
57
+ - lib/remote_resource/concerns/mappings.rb
58
+ - lib/remote_resource/concerns/party_query.rb
59
+ - lib/remote_resource/concerns/relation.rb
60
+ - lib/remote_resource/connection.rb
61
+ - lib/remote_resource/document_wrapper.rb
62
+ - lib/remote_resource/html.rb
63
+ - lib/remote_resource/html/connection.rb
64
+ - lib/remote_resource/html/model.rb
65
+ - lib/remote_resource/json/connection.rb
66
+ - lib/remote_resource/json/model.rb
67
+ - lib/remote_resource/model.rb
68
+ - lib/remote_resource/model/relation.rb
69
+ - lib/remote_resource/version.rb
70
+ - remote_resource.gemspec
71
+ - spec/model_spec.rb
72
+ - spec/spec_helper.rb
73
+ homepage: ''
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Access remote resources with OOP interface
97
+ test_files:
98
+ - spec/model_spec.rb
99
+ - spec/spec_helper.rb
100
+ has_rdoc: