airmodel 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13bc3270f4a9eee17e8559404cdb9f0e9917f899
4
- data.tar.gz: 8ddb417cbac7e452de3f7ca469007370c2a7f6e6
3
+ metadata.gz: 86835f77844aa599d172c8cb92e2aac8f13cbedf
4
+ data.tar.gz: d37a0a44a684f943f68ad7dc5e5ac077ac5752b9
5
5
  SHA512:
6
- metadata.gz: 38013c380e9a5b226c1ab4fe3c80ce10f5901052e9962db2f480fe37c555e29d956accd00b832e83e5aa7fcc5545b2995833ee3a61c0dfa5e0d37775f9cf76ce
7
- data.tar.gz: 3686e3416162fd2744cceb2f81ea0c0272ab4fa6d16009205e1f9d74e34308ef6011a7c6b72dbd558b1e4cd10a67a719b530665c20a1b18fee0765e377ac2a6a
6
+ metadata.gz: 9696fc07326864a461978321b419c09cc4755cef0235593b02f76af93a7ebc65f7259d567b2fb4dafb4f38d9e4514e7926258f559dec2c5e34cb022171c385a3
7
+ data.tar.gz: c91f7ead3cfb6ee25ae107804a3ea7529f4be08126c9f04f757e66b7e3dfb9fa6f449973902d26d1cde6c81a2f6fd55f7d144f2c5fe48f64605a2ae79ad869af
data/README.md CHANGED
@@ -52,7 +52,9 @@ Now you can write code like
52
52
 
53
53
  Song.all
54
54
 
55
- Song.where("Artist Name": "The Beatles", "Composer": "Harrison")
55
+ Song.where("Artist Name" => "The Beatles", "Composer" => "Harrison")
56
+
57
+ Song.search(:q => "Let it Be", fields: ["Name", "Album"])
56
58
 
57
59
  Song.first
58
60
 
@@ -60,29 +62,35 @@ Now you can write code like
60
62
 
61
63
  Song.find("recXYZ")
62
64
 
63
- Song.find(["recXYZ", "recABC", "recJKL"])
65
+ Song.find_by("Composer" => "Harrison")
64
66
 
65
67
 
66
- Most queries are chainable, e.g.
68
+ Queries are chainable, e.g.
67
69
 
68
- Song.where("rating" => 5).where('artist' => "Fiona Apple").order("rating", "DESC").limit(5)
70
+ Song.where("rating" => 5).where('Artist' => "Fiona Apple").order("rating", "DESC").limit(5)
69
71
 
70
- There's also a special `Model.by_formula` query, which overrides any filters
71
- supplied in your `Model.where()` statements, and replaces them with a raw
72
+ There's also a `Model.by_formula` query, which lets you pass explicit
72
73
  [Airtable
73
- Formula](https://support.airtable.com/hc/en-us/articles/203255215-Formula-field-reference)
74
+ Formulas](https://support.airtable.com/hc/en-us/articles/203255215-Formula-field-reference)
74
75
 
75
- You can still chain `.limit` and `.order` with a `.by_forumla` query.
76
+ You can chain `.limit` and `.order` with a `.by_forumla` query.
76
77
 
77
78
  Song.by_formula("NOT({Rating} < 3)").order("rating", "DESC").limit(5)
78
79
 
79
- See lib/airmodel/model.rb for all available methods.
80
+ See `lib/airmodel/model.rb` for all model methods, and
81
+ `lib/airmodel/query.rb` for all Query methods.
80
82
 
81
83
 
82
84
  Contributions
83
85
  ----------------
84
86
 
85
- Add a passing test to spec/model_spec.rb, then send a pull
86
- request. Thanks!
87
+ I'm currently testing against a live Airtable base, because stubbing API
88
+ calls has occasionally yielded false positives in my specs. To run the tests,
89
+ create an Airtable account, set `AIRTABLE_API_KEY=[your API key]` in your `.env` file, and
90
+ then visit [this link](https://airtable.com/invite/l?inviteId=invj96HyFOB6GF8Vq&inviteToken=2e98eff03a646162344bb997a06645e3)
91
+ to request access to the base.
92
+
93
+ Once that's all set, write a passing test for your feature and send a pull request.
87
94
 
95
+ Thanks!
88
96
 
@@ -20,8 +20,10 @@ Gem::Specification.new do |spec|
20
20
  spec.add_development_dependency 'rake', '~> 10'
21
21
  spec.add_development_dependency 'rspec', '~> 3'
22
22
  spec.add_development_dependency 'pry', '~> 0.10'
23
- spec.add_development_dependency 'fakeweb', '~> 1.3'
23
+ spec.add_development_dependency 'vcr', '~> 3.0'
24
+ spec.add_development_dependency 'dotenv', '~> 2.1'
25
+ spec.add_development_dependency 'webmock', '~> 2.3'
24
26
 
25
27
  spec.add_dependency 'airtable', '~> 0.0.9'
26
- spec.add_dependency 'activesupport', '~> 5.0'
28
+ spec.add_dependency 'activesupport', '~> 4.0'
27
29
  end
@@ -1,6 +1,6 @@
1
1
  :albums:
2
- :table_name: "albums"
3
- :base_id: appXYZ
2
+ :table_name: Albums
3
+ :base_id: appTE8VIb595FI4c6
4
4
  :songs:
5
- :table_name: "songs"
6
- :base_id: appXYZ
5
+ :table_name: Songs
6
+ :base_id: appTE8VIb595FI4c6
@@ -14,7 +14,7 @@ module Airmodel
14
14
  File.expand_path '../..', __FILE__
15
15
  end
16
16
 
17
- def self.client(api_key=ENV["AIRTABLE_API_KEY"])
17
+ def self.client(api_key=ENV.fetch("AIRTABLE_API_KEY") )
18
18
  @@api_client ||= Airtable::Client.new(api_key)
19
19
  @@api_client
20
20
  end
@@ -15,10 +15,15 @@ module Airmodel
15
15
  base_id: self.send(args[:base_key]),
16
16
  table_name: args[:table_name] || association_name.to_s.tableize
17
17
  }
18
+ # maybe the base is defined in the config file
19
+ elsif c = Airmodel.bases[args[:class_name].tableize.to_sym]
20
+ c
21
+ # maybe the base is just a table in the same base as the parent
18
22
  else
19
- # the airtable base info is defined in the
20
- # YML config file, with the rest of the data
21
- Airmodel.bases[args[:class_name].tableize.to_sym] || raise(NoSuchBase.new("Couldn't find base '#{association_name}' in config file.\nPlease pass :base_key => foo with your has_many call,\nor add '#{association_name}' to your config file."))
23
+ {
24
+ base_id: self.class.base_config[:base_id],
25
+ table_name: args[:table_name] || association_name.to_s.tableize
26
+ }
22
27
  end
23
28
  finder_name = "@#{association_name}_finder"
24
29
  if f = instance_variable_get(finder_name)
@@ -28,10 +33,19 @@ module Airmodel
28
33
  @base_id = config[:base_id]
29
34
  @table_name = config[:table_name]
30
35
  end
31
- instance_variable_set(finder_name, finder)
36
+ constraints = if args[:constraints].respond_to?(:call)
37
+ args[:constraints].call(self)
38
+ else
39
+ {}
40
+ end
41
+ instance_variable_set(finder_name, finder.where(constraints))
32
42
  end
33
43
  end
34
44
  end
35
45
 
46
+ def default_has_many_contraints
47
+ true
48
+ end
49
+
36
50
  end
37
51
  end
@@ -15,10 +15,16 @@ module Airmodel
15
15
  Query.new(self).by_formula(args)
16
16
  end
17
17
 
18
+ def self.search(args)
19
+ Query.new(self).search(args)
20
+ end
21
+
22
+ # Model.order("Name DESC")
18
23
  def self.order(args)
19
- Query.new(self).order(args)
24
+ Query.new(self).order(*args)
20
25
  end
21
26
 
27
+ # Model.limit(10)
22
28
  def self.limit(args)
23
29
  Query.new(self).limit(args)
24
30
  end
@@ -45,18 +51,7 @@ module Airmodel
45
51
 
46
52
  # find a record by specified attributes, return it
47
53
  def self.find_by(filters)
48
- if filters[:id]
49
- results = self.classify table.find(filters[:id])
50
- else
51
- formula = "AND(" + filters.map{|k,v| "{#{k}}='#{v}'" }.join(',') + ")"
52
- results = self.classify(
53
- table.records(
54
- filterByFormula: formula,
55
- limit: 1
56
- )
57
- )
58
- end
59
- results.count == 0 ? nil : results.first
54
+ Query.new(self).find_by(filters)
60
55
  end
61
56
 
62
57
  # default to whatever order Airtable returns
@@ -1,5 +1,6 @@
1
1
  module Airmodel
2
2
  class Query
3
+ include Enumerable
3
4
 
4
5
  def initialize(querying_class)
5
6
  @querying_class = querying_class
@@ -8,7 +9,9 @@ module Airmodel
8
9
  def params
9
10
  @params ||= {
10
11
  where_clauses: {},
11
- order: @querying_class.default_sort
12
+ formulas: [],
13
+ order: @querying_class.default_sort,
14
+ offset: nil
12
15
  }
13
16
  end
14
17
 
@@ -18,45 +21,76 @@ module Airmodel
18
21
  end
19
22
 
20
23
  def by_formula(formula)
21
- params[:where_clauses] = {}
22
- params[:formula] = formula
24
+ params[:formulas].push formula
25
+ self
26
+ end
27
+
28
+ def search(args={})
29
+ if args && args[:q] && args[:fields]
30
+ searchfields = if args[:fields].is_a?(String)
31
+ args[:fields].split(",").map{|f| f.strip }
32
+ else
33
+ args[:fields]
34
+ end
35
+ query = if args[:q].respond_to?(:downcase)
36
+ args[:q].downcase
37
+ else
38
+ args[:q]
39
+ end
40
+ f = "OR(" + searchfields.map{|field|
41
+ # convert strings to case-insensitive searches
42
+ "FIND('#{query}', LOWER({#{field}}))"
43
+ }.join(',') + ")"
44
+ params[:formulas].push f
45
+ end
23
46
  self
24
47
  end
25
48
 
26
49
  def limit(lim)
27
- params[:limit] = lim
50
+ params[:limit] = lim ? lim.to_i : nil
28
51
  self
29
52
  end
30
53
 
31
- def order(column, direction)
32
- params[:order] = [column, direction.downcase.to_sym]
54
+ def order(order_string)
55
+ if order_string
56
+ ordr = order_string.split(" ")
57
+ column = ordr.first
58
+ direction = ordr.length > 1 ? ordr.last.downcase : "asc"
59
+ params[:order] = [column, direction]
60
+ end
33
61
  self
34
62
  end
35
63
 
64
+
65
+ def offset(airtable_offset_key)
66
+ params[:offset] = airtable_offset_key
67
+ self
68
+ end
69
+
70
+ # return saved airtable offset for this query
71
+ def get_offset
72
+ @offset
73
+ end
74
+
36
75
  # add kicker methods
37
76
  def to_a
38
77
  puts "RUNNING EXPENSIVE API QUERY TO AIRTABLE (#{@querying_class.name})"
39
- # filter by explicit formula, or by joining all where_clasues together
40
- formula = params[:formula] || "AND(" + params[:where_clauses].map{|k,v| "{#{k}}='#{v}'" }.join(',') + ")"
41
- @querying_class.classify @querying_class.table.all(
78
+ # merge explicit formulas and abstracted where-clauses into one Airtable Formula
79
+ formula = "AND(" + params[:where_clauses].map{|k,v| "{#{k}}='#{v}'" }.join(',') + params[:formulas].join(',') + ")"
80
+ records = @querying_class.table.records(
42
81
  sort: params[:order],
43
82
  filterByFormula: formula,
44
- limit: params[:limit]
83
+ limit: params[:limit],
84
+ offset: params[:offset]
45
85
  )
86
+ @offset = records.offset
87
+ @querying_class.classify records
46
88
  end
47
89
 
48
90
  def all
49
91
  to_a
50
92
  end
51
93
 
52
- def first
53
- to_a.first
54
- end
55
-
56
- def last
57
- to_a.last
58
- end
59
-
60
94
  def each(&block)
61
95
  to_a.each(&block)
62
96
  end
@@ -69,6 +103,16 @@ module Airmodel
69
103
  to_a.inspect
70
104
  end
71
105
 
106
+ def last
107
+ to_a.last
108
+ end
109
+
110
+ def find_by(filters)
111
+ params[:limit] = 1
112
+ params[:where_clauses] = filters
113
+ first
114
+ end
115
+
72
116
  end
73
117
  end
74
118
 
@@ -1,3 +1,3 @@
1
1
  module Airmodel
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -3,45 +3,37 @@ require 'spec_helper'
3
3
  class Song < Airmodel::Model
4
4
  end
5
5
 
6
+ class Album < Airmodel::Model
7
+ end
8
+
6
9
  class ParentModel < Airmodel::Model
7
10
  has_many :songs
8
11
 
9
12
  def dynamically_assigned_child_base_id
10
- 'appABCDEF'
13
+ "appTE8VIb595FI4c6"
11
14
  end
15
+
12
16
  end
13
17
 
14
18
  describe ParentModel do
15
19
 
16
- before(:each) do
17
- config = Airmodel.bases[:albums]
18
- #stub INDEX requests
19
- stub_airtable_response!(
20
- Regexp.new("https://api.airtable.com/v0/#{config[:base_id]}/#{config[:table_name]}"),
21
- { "records" => [{"id": "recXYZ", fields: {"color":"red"} }, {"id":"recABC", fields: {"color": "blue"} }] }
22
- )
23
- #stub CREATE requests
24
- stub_airtable_response!("https://api.airtable.com/v0/appXYZ/albums",
25
- { "fields" => { "color" => "red", "foo" => "bar" }, "id" => "12345" },
26
- :post
27
- )
28
- end
29
-
30
- after(:each) do
31
- FakeWeb.clean_registry
32
- end
33
-
34
-
35
20
  describe 'has_many' do
36
21
  it 'should return a list of songs' do
37
22
  songs = ParentModel.new.songs
38
- expect(songs.table_name).to eq 'songs'
23
+ expect(songs.first.is_a? Song).to be true
24
+ end
25
+
26
+ it "Should look in the parent model's base when not passed a base_key" do
27
+ Song.has_many :albums
28
+ albums = Song.first.albums.all
29
+ expect(albums.first.name).to eq "Blood on the Tracks"
39
30
  end
40
31
 
41
32
  it 'should raise NoSuchBase when passed a weird association not backed by bases.yml' do
42
33
  begin
43
34
  ParentModel.has_many :unusual_children
44
- false
35
+ ParentModel.new.unusual_children
36
+ expect(true).to eq false
45
37
  rescue Airmodel::NoSuchBase
46
38
  true
47
39
  end
@@ -52,13 +44,9 @@ describe ParentModel do
52
44
  end
53
45
 
54
46
  it 'should work with a base_key instead of a yml file' do
55
- stub_airtable_response!(
56
- Regexp.new("https://api.airtable.com/v0/appABCDEF/tunes"),
57
- { "records" => [{"id": "recXYZ", fields: {"color":"red"} }, {"id":"recABC", fields: {"color": "blue"} }] }
58
- )
59
- ParentModel.has_many :tunes, base_key: 'dynamically_assigned_child_base_id', class_name: 'Song'
47
+ ParentModel.has_many :tunes, base_key: 'dynamically_assigned_child_base_id', class_name: 'Song', table_name: "Songs"
60
48
  tunes = ParentModel.new.tunes
61
- expect(tunes.first.table.worksheet_name).to eq 'tunes'
49
+ expect(tunes.first.table.worksheet_name).to eq "Songs"
62
50
  end
63
51
 
64
52
  it 'should let me define the important args however I like'
@@ -5,24 +5,6 @@ end
5
5
 
6
6
  describe Album do
7
7
 
8
- before(:each) do
9
- config = Airmodel.bases[:albums]
10
- #stub INDEX requests
11
- stub_airtable_response!(
12
- Regexp.new("https://api.airtable.com/v0/#{config[:base_id]}/#{config[:table_name]}"),
13
- { "records" => [{"id": "recXYZ", fields: {"color":"red"} }, {"id":"recABC", fields: {"color": "blue"} }] }
14
- )
15
- #stub CREATE requests
16
- stub_airtable_response!("https://api.airtable.com/v0/appXYZ/albums",
17
- { "fields" => { "color" => "red", "foo" => "bar" }, "id" => "12345" },
18
- :post
19
- )
20
- end
21
-
22
- after(:each) do
23
- FakeWeb.clean_registry
24
- end
25
-
26
8
  describe "Class Methods" do
27
9
  describe "table" do
28
10
  it "should return an Airtable::Table object" do
@@ -48,53 +30,47 @@ describe Album do
48
30
  end
49
31
 
50
32
  describe "all" do
33
+ use_vcr_cassette
51
34
  it "should return a list of airtable records" do
52
35
  records = Album.all
53
- expect(records.first.id).to eq "recXYZ"
36
+ expect(records.first.id).to eq "rec0bTuIoUQVPMsmi"
54
37
  end
55
38
  end
56
39
 
57
40
  describe "where" do
41
+ use_vcr_cassette
58
42
  it "should return a list of airtable records that match filters" do
59
- records = Album.where(color: "red")
43
+ records = Album.where(name: "Blood on the Tracks")
60
44
  expect(records.first.class).to eq Album
61
- expect(records.first.color).to eq "red"
45
+ expect(records.first.name).to eq "Blood on the Tracks"
62
46
  end
63
47
  end
64
48
 
65
49
  describe "find" do
50
+ use_vcr_cassette
66
51
  it "should call airtable-ruby's 'find' method when passed just one record ID" do
67
- stub_airtable_response! "https://api.airtable.com/v0/appXYZ/albums/recABC", { "id":"recABC", fields: {"name": "example record"} }
68
- record = Album.find("recABC")
69
- expect(record.name).to eq "example record"
52
+ record = Album.find("rec52DfV4E2I2kzrS")
53
+ expect(record.name).to eq "Voodoo"
70
54
  end
71
55
  it "should return an ordered list of records when passed an array of record IDs" do
72
- records = Album.find(["recABC", "recXYZ"])
56
+ records = Album.find(["rec52DfV4E2I2kzrS", "rec0bTuIoUQVPMsmi"])
73
57
  expect(records.class).to eq Array
74
- expect(records.first.id).to eq "recABC"
58
+ expect(records.first.id).to eq "rec52DfV4E2I2kzrS"
75
59
  expect(records.first.class).to eq Album
76
60
  end
77
61
  end
78
62
 
79
63
  describe "find_by" do
80
- it "should return one record that matches the supplied filters", skip_before: true do
81
- stub_airtable_response!(
82
- Regexp.new("https://api.airtable.com/v0/appXYZ/albums"),
83
- { "records" => [{"id":"recABC", fields: {"color": "blue"} }] }
84
- )
85
- record = Album.find_by(color: "blue")
86
- expect(record.color).to eq "blue"
87
- expect(record.class).to eq Album
88
- end
89
- it "should call airtable-ruby's 'find' method when the filter is an id" do
90
- stub_airtable_response! "https://api.airtable.com/v0/appXYZ/albums/recABC", { "id":"recABC", fields: {"name": "example record"} }
91
- record = Album.find_by(id: "recABC")
92
- expect(record.name).to eq "example record"
64
+ use_vcr_cassette
65
+ it "should return one record that matches the supplied filters" do
66
+ record = Album.find_by(name: "Voodoo")
67
+ expect(record.name).to eq "Voodoo"
93
68
  expect(record.class).to eq Album
94
69
  end
95
70
  end
96
71
 
97
72
  describe "first" do
73
+ use_vcr_cassette
98
74
  it "should return the first record" do
99
75
  record = Album.first
100
76
  expect(record.class).to eq Album
@@ -102,21 +78,22 @@ describe Album do
102
78
  end
103
79
 
104
80
  describe "create" do
81
+ use_vcr_cassette
105
82
  it "should create a new record" do
106
- record = Album.create(color: "red")
107
- expect(record.id).to eq "12345"
83
+ record = Album.create("Name" => "Abbey Road")
84
+ expect(record.id).not_to eq nil
85
+ record.destroy
108
86
  end
109
87
  end
110
88
 
111
89
  describe "patch" do
90
+ use_vcr_cassette
112
91
  it "should update a record" do
113
- stub_airtable_response!(Regexp.new("/v0/appXYZ/albums/12345"),
114
- { "fields" => { "color" => "blue", "foo" => "bar" }, "id" => "12345" },
115
- :patch
116
- )
117
- record = Album.create(color: "red")
118
- record = Album.patch("12345", { color: "blue" })
119
- expect(record.color).to eq "blue"
92
+ record = Album.create("Name" => "Let It Be")
93
+ notes = "It wasn't really their last one"
94
+ record = Album.patch(record.id, { "Notes" => notes })
95
+ expect(record.notes).to eq notes
96
+ record.destroy
120
97
  end
121
98
  end
122
99
 
@@ -125,71 +102,56 @@ describe Album do
125
102
  describe "Instance Methods" do
126
103
 
127
104
  describe "save" do
128
- record = Album.new(color: "red")
129
105
  it "should create a new record" do
106
+ record = Album.new("Name" => "His California Record")
130
107
  record.save
131
- expect(record.id).to eq "12345"
108
+ expect(record.id).not_to be nil
109
+ record.destroy
132
110
  end
133
111
  it "should update an existing record" do
134
- stub_airtable_response!("https://api.airtable.com/v0/appXYZ/albums/12345",
135
- { "fields" => { "color" => "red", "foo" => "bar" }, "id" => "12345" },
136
- :get
137
- )
138
- stub_airtable_response!("https://api.airtable.com/v0/appXYZ/albums/12345",
139
- { "fields" => { "color" => "blue" }, "id" => "12345" },
140
- :patch
141
- )
142
- record[:color] = "blue"
112
+ record = Album.create("Name" => "His California Record")
113
+ record["Artist"] = "Bobby Bland"
143
114
  record.save
144
- expect(record.color).to eq "blue"
115
+ expect(record.artist).to eq "Bobby Bland"
116
+ record.destroy
145
117
  end
146
118
  end
147
119
 
148
120
  describe "destroy" do
149
121
  it "should delete a record" do
150
- stub_airtable_response!("https://api.airtable.com/v0/appXYZ/albums/12345",
151
- { "deleted": true, "id" => "12345" },
152
- :delete
153
- )
154
- response = Album.new(id: "12345").destroy
122
+ response = Album.create("Name" => "12345").destroy
155
123
  expect(response["deleted"]).to eq true
156
124
  end
157
125
  end
158
126
 
159
127
  describe "update" do
160
128
  it "should update the supplied attrs on an existing record" do
161
- stub_airtable_response!("https://api.airtable.com/v0/appXYZ/albums/12345",
162
- { "fields" => { "color" => "green"}, "id" => "12345" },
163
- :patch
164
- )
165
- record = Album.create(color: "red", id:"12345")
166
- record.update(color: "green")
167
- expect(record.color).to eq "green"
129
+ record = Album.create("Name" => "Blue")
130
+ record.update("Name" => "Green")
131
+ expect(record.name).to eq "Green"
132
+ record.destroy
168
133
  end
169
134
  end
170
135
 
171
136
  describe "cache_key" do
172
137
  it "should return a unique key that can be used to id this record in memcached" do
173
- record = Album.new(id: "recZXY")
174
- expect(record.cache_key).to eq "albums_recZXY"
138
+ record = Album.first
139
+ expect(record.cache_key).to eq "albums_#{record.id}"
175
140
  end
176
141
  end
177
142
 
178
143
  describe "changed_fields" do
179
144
  it "should return a hash of attrs changed since last save" do
180
- stub_airtable_response!("https://api.airtable.com/v0/appXYZ/albums/12345",
181
- { fields: { 'color': 'red' }, "id" => "12345" },
182
- :get
183
- )
184
- record = Album.create(color: 'red')
185
- record[:color] = 'green'
186
- expect(record.changed_fields).to have_key 'color'
145
+ record = Album.create("Name" => "Pinkerton")
146
+ record["Name"] = "Green"
147
+ expect(record.changed_fields).to have_key "Name"
148
+ record.destroy
187
149
  end
188
150
  end
189
151
 
190
152
  describe "new_record?" do
191
153
  it "should return true if the record hasn't been saved to airtable yet" do
192
- record = Album.new(color: 'red')
154
+ record = Album.new("Name" => "Jim!")
193
155
  expect(record.new_record?).to eq true
194
156
  end
195
157
  end
@@ -5,41 +5,87 @@ end
5
5
 
6
6
  describe Album do
7
7
 
8
- before(:each) do
9
- config = Airmodel.bases[:albums]
10
- #stub INDEX requests
11
- stub_airtable_response!(
12
- Regexp.new("https://api.airtable.com/v0/#{config[:base_id]}/#{config[:table_name]}"),
13
- { "records" => [{"id": "recXYZ", fields: {"color":"red"} }, {"id":"recABC", fields: {"color": "blue"} }] }
14
- )
15
- end
16
-
17
- after(:each) do
18
- FakeWeb.clean_registry
19
- end
20
-
21
-
22
8
  describe "where" do
23
9
  it "allows chaining" do
24
10
  q = Album.where(
25
11
  "name" => "Tidal",
26
- "artist" => "Fiona Apple",
27
- "great" => true
28
- ).limit(10)
12
+ "artist" => "Fiona Apple"
13
+ ).limit(10).order("Name DESC")
29
14
  expect(q.class).to eq Airmodel::Query
30
15
  # it should execute the query on
31
16
  # query.all, and return an array
32
17
  expect(q.all.class).to be Array
18
+ expect(q.count).to eq 1
33
19
  end
34
20
 
35
- it "can replace where_clauses with a raw airtable formula" do
36
- formula = "NOT({Rating} < 4"
37
- q = Album.where("great" => true).by_formula(formula)
38
- expect(q.params[:where_clauses]).to be {}
39
- expect(q.params[:formula]).to be formula
21
+ it "can use a raw airtable formula" do
22
+ formula = "NOT({Rating} > 3)"
23
+ q = Album.by_formula(formula)
24
+ expect(q.params[:formulas].to_s).to eq [formula].to_s
40
25
  expect(q.all.class).to be Array
26
+ expect(q.count).to eq 1
41
27
  end
42
28
  end
43
29
 
44
- end
30
+ describe "limit" do
31
+ it "limits the results to the specified number" do
32
+ q = Album.limit(2)
33
+ expect(q.count).to eq 2
34
+ end
35
+
36
+ it "accepts a string instead of an integer" do
37
+ q = Album.limit("1")
38
+ expect(q.count).to eq 1
39
+ end
40
+
41
+ it "accepts nil as an all-pass filter" do
42
+ q = Album.limit(nil)
43
+ expect(q.count).to eq Album.all.count
44
+ end
45
+ end
46
+
47
+ describe "search" do
48
+ it "searches in args[:fields] for args[:value]" do
49
+ q = Album.search(q: "Fiona", fields: ["Artist"])
50
+ expect(q.count).to eq 1
51
+ expect(q.first.artist).to eq "Fiona Apple"
52
+ end
45
53
 
54
+ it "can search across multiple fields" do
55
+ q = Album.search(q: "Sinatra", fields: ["Artist", "Name"])
56
+ expect(q.count).to eq 2
57
+ expect(q.first.artist).to eq "Frank Sinatra"
58
+ expect(q.last.artist).to eq "Dylan"
59
+ end
60
+
61
+ it "can accept field names as a string" do
62
+ q = Album.search(q: "Sinatra", fields: "Artist, Name")
63
+ expect(q.count).to eq 2
64
+ expect(q.first.artist).to eq "Frank Sinatra"
65
+ expect(q.last.artist).to eq "Dylan"
66
+ end
67
+
68
+ it "is not case-sensitive w/r/t queries" do
69
+ q = Album.search(q: "dylan", fields: "Artist")
70
+ expect(q.count).to eq 2
71
+ end
72
+
73
+ it "accepts nil as an all-pass filter" do
74
+ q = Album.search(nil)
75
+ expect(q.count).to eq Album.all.count
76
+ end
77
+
78
+ end
79
+
80
+ describe "order" do
81
+ it "can order when passed an array" do
82
+ q = Album.order("Name ASC")
83
+ expect(q.first.name).to eq "A Swingin' Affair"
84
+ q = Album.order("Name")
85
+ expect(q.first.name).to eq "A Swingin' Affair"
86
+ q = Album.order("Name DESC")
87
+ expect(q.first.name).to eq "Voodoo"
88
+ end
89
+ end
90
+
91
+ end
@@ -1,21 +1,20 @@
1
- require 'airmodel'
2
- require 'fakeweb'
3
- require 'pry'
4
-
5
- def stub_airtable_response!(url, response, method=:get)
6
- FakeWeb.register_uri(
7
- method,
8
- url,
9
- body: response.to_json,
10
- content_type: "application/json"
11
- )
12
- end
1
+ require "airmodel"
2
+ require "pry"
3
+ require "vcr"
4
+ require "dotenv"
5
+ Dotenv.load
13
6
 
14
7
  RSpec.configure do |config|
15
8
  config.color = true
9
+ config.extend VCR::RSpec::Macros
10
+ config.order = :random
16
11
  end
17
12
 
18
- FakeWeb.allow_net_connect = false
13
+ VCR.configure do |config| config.allow_http_connections_when_no_cassette = true
14
+ config.cassette_library_dir = "#{Airmodel.root}/spec/fixtures/vcr_cassettes"
15
+ config.default_cassette_options = { :record => :new_episodes }
16
+ config.filter_sensitive_data("<AIRTABLE_API_KEY>") { ENV.fetch('AIRTABLE_API_KEY') }
17
+ end
19
18
 
20
19
  # enable Debug mode in Airtable
21
20
  module Airtable
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airmodel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - chrisfrankdotfm
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-16 00:00:00.000000000 Z
11
+ date: 2016-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -67,19 +67,47 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.10'
69
69
  - !ruby/object:Gem::Dependency
70
- name: fakeweb
70
+ name: vcr
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '1.3'
75
+ version: '3.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '1.3'
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: dotenv
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.3'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: airtable
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +128,14 @@ dependencies:
100
128
  requirements:
101
129
  - - "~>"
102
130
  - !ruby/object:Gem::Version
103
- version: '5.0'
131
+ version: '4.0'
104
132
  type: :runtime
105
133
  prerelease: false
106
134
  version_requirements: !ruby/object:Gem::Requirement
107
135
  requirements:
108
136
  - - "~>"
109
137
  - !ruby/object:Gem::Version
110
- version: '5.0'
138
+ version: '4.0'
111
139
  description: Airtable data in ActiveRecord-style syntax
112
140
  email:
113
141
  - chris.frank@thefutureproject.org
@@ -152,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
180
  version: '0'
153
181
  requirements: []
154
182
  rubyforge_project:
155
- rubygems_version: 2.5.1
183
+ rubygems_version: 2.5.2
156
184
  signing_key:
157
185
  specification_version: 4
158
186
  summary: Interact with your Airtable data using ActiveRecord-style models