quickbase_record 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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OGE4NWIyZGU4ZTNmZGFmMGRjNzdiMzA5ZDZlMzc1NTdmMTJhNzYxNg==
5
+ data.tar.gz: !binary |-
6
+ ZGExMTQ3ZWNiMzllNDk3NzEyODhkN2FlOWY3Mjk4YTc4MGU2NzU3Yg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MzkyNTJiNWM2NmY3OTNhNTQ1NTJiNjY4NjBmNjExMjZkMzQwOTc2MTFhZmIx
10
+ NTkxZmU2NDc2NjY1MjIwNWI3Y2YwZWM3ZjZhMjYyZDc1MTM4N2I1MmI5N2Nl
11
+ YzBmOTc3MzMwMGQ1OGJlN2VhOTFiOTZhZTJkZjk0MWUyYTA3OGY=
12
+ data.tar.gz: !binary |-
13
+ N2ZhNjUzNTIwNDRiMjgyNmYwOTBmMDkyY2Y0OTFmYjM4NWI5MTY0YzZhNjEw
14
+ NThmZmIxZjc0MjAxNDQ4NWU2MGRjYmZkZDc0Njk2MmVkYjM1NThjZTVlNTkw
15
+ YWJmZTdiNTk5ZmIzM2ViMzkzM2FlNjNmOWU5MzEwNjE3YmYwMjM=
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require quickbase_record
3
+ --require quickbase_record_config
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in quickbase_record.gemspec
4
+ gemspec
5
+
6
+ gem 'activesupport'
7
+ gem 'activemodel'
8
+ gem 'advantage_quickbase'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Cullen Jett
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,186 @@
1
+ # QuickbaseRecord
2
+
3
+ QuickbaseRecord is a baller ActiveRecord-style ORM for using the Intuit QuickBase platform as a database for Ruby or Ruby on Rails applications.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'quickbase_record'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install quickbase_record
20
+
21
+ ## Usage
22
+
23
+ ### Initialize the API Client
24
+ QuickbaseRecord is built on top of the [Advantage Quickbase](https://github.com/AdvantageIntegratedSolutions/Quickbase-Gem) gem to make the API calls to QuickBase, so you'll need to configure QuickbaseRecord with your app's realm name and provide a valid username and password. This can be done in a single initializer file with a call to `QuickbaseRecords.configure`, making sure to keep your credentials safe from the world.
25
+
26
+ ```
27
+ # config/initializers/quickbase_record.rb
28
+
29
+ QuickbaseRecord.configure do |config|
30
+ config.realm = "your_apps_realm_name"
31
+ config.username = "valid_username" (or something like ENV["QB_USERNAME"])
32
+ config.password = "valid_password" (or something like ENV["QB_PASSWORD"])
33
+ end
34
+ ```
35
+
36
+ ### Include it in your Class
37
+ Simply `include QuickbaseRecord::Model` in your class and use the `.define_fields` method to supply the table's DBID and a mapping of desired field names => QuickBase FIDs
38
+
39
+ ```
40
+ # app/models/posts.rb
41
+
42
+ class Posts
43
+ include QuickbaseRecord::Model
44
+
45
+ define_fields ({
46
+ dbid: 'abcde12345'
47
+ id: 3,
48
+ content: 7,
49
+ author: 8
50
+ })
51
+
52
+ # code...
53
+ end
54
+ ```
55
+ **IMPORTANT:** You must supply a key/value pair for :dbid and :id (QuickbaseRecord uses :id instead of :record_id to look more like standard ActiveRecord models)
56
+
57
+ ### What You Get
58
+ Classes that include QuickbaseRecord::Model and define their fields will have a handful of class and instance methods for interacting with your QuickBase application similar to ActiveRecord. The goal is to be able to use QuickBase as a database and treat your models the same way you would with a traditional database.
59
+
60
+ ```
61
+ @post = Posts.find(1) => <Post: @id=1, @content="Amazing post content", @author: 'Cullen Jett'>
62
+
63
+ --
64
+
65
+ <%= form_for @post do |f| %>
66
+ # code...
67
+ <% end %>
68
+
69
+ --
70
+
71
+ @post = Post.where(author: 'Cullen Jett').first
72
+ @post.update_attributes(author: 'THE Cullen Jett')
73
+
74
+ --
75
+
76
+ etc.
77
+ ```
78
+
79
+
80
+ Also included/extended are ActiveModel::Naming, ActiveModel::Callbacks, ActiveModel::Validations, and ActiveModel::Conversion ([see ActiveModel docs for details](https://github.com/rails/rails/tree/master/activemodel/lib/active_model)). The biggest benefit here is the availability of `.validates` on your class to validate attributes and capture invalid attributes with `#valid?`.
81
+
82
+ Be aware that validations needing context from the database (i.e. `validates_uniqueness_of`) are not yet supported and will need to be implemented manually.
83
+
84
+ Database callbacks (i.e. `before_save :create_token!`) are not fully functional yet, so stay tuned.
85
+
86
+ ## Available Methods
87
+ * **.create(attributes_hash)**
88
+ - Intantiate *and* save a new object with the given attributes
89
+ - Assigns the returned object it's new ID
90
+ ```
91
+ Posts.create(content: 'Amazing post content', author: 'Cullen Jett')
92
+ ```
93
+
94
+ * **.find(id)**
95
+ - Query for a specific QuickBase record by it's ID
96
+ - Returns a single object
97
+ ```
98
+ Posts.find(params[:id])
99
+ ```
100
+
101
+ * **.where(attributes_hash)**
102
+ - Query QuickBase by any field name defined in your class' field mapping
103
+ - Returns an array of objects
104
+ ```
105
+ Posts.where(author: 'Cullen Jett').first
106
+ ```
107
+
108
+ - Multiple field_name/value pairs are joined with 'AND'
109
+ ```
110
+ Posts.where(id: 1, author: 'Cullen Jett')
111
+ # {'3'.EX.'1'}AND{'8'.EX.'Cullen Jett'}
112
+ ```
113
+
114
+ - Values in an array are joined with 'OR'
115
+ ```
116
+ Posts.where(author: ['Cullen Jett', 'Socrates')
117
+ # {'8'.EX.'Socrates'}OR{'8'.EX.'Socrates'}
118
+ ```
119
+
120
+ - To use a comparitor other than 'EX' pass the value as another hash with the key as the comparitor
121
+ ```
122
+ Posts.where(author: {XEX: 'Cullen Jett'})
123
+ # {'8'.XEX.'Cullen Jett'}
124
+ ```
125
+
126
+ * **.query(qb_query_string)**
127
+ - Accepts a string in the standard QuickBase query format
128
+ - Returns an array of objects
129
+ - Works with field names or FIDs
130
+ ```
131
+ Posts.query("{'3'.EX.'1'}")
132
+ Posts.query("{author.XEX.'Cullen Jett'}")
133
+ ```
134
+
135
+ * **#save**
136
+ - Creates a new record in QuickBase for objects that don't have an ID *or* edits the corresponding QuickBase record if the object already has an ID
137
+ - Returns the object (if #save created a record in QuickBase the the returned object will now have an ID)
138
+ - Uses API_ImportFromCSV under the hood.
139
+ ```
140
+ @post = Posts.new(content: 'Amazing post content', author: 'Cullen Jett')
141
+ @post.save # => <Post: @id: 1, @content: 'Amazing post content', @author: 'Cullen Jett'
142
+
143
+ @post.author = 'Socrates'
144
+ @post.save # => <Post: @id: 1, @content: 'Amazing post content', @author: 'Socrates'
145
+ ```
146
+
147
+ * **#delete**
148
+ - Delete the corresponding record in QuickBase
149
+ - It returns the object's ID if successful or `false` if unsuccessful
150
+ ```
151
+ @post = Post.find(1)
152
+ @post.delete
153
+ ```
154
+
155
+ * **#update_attributes(attributes_hash)**
156
+ - Updates *and* saves the object with the new attributes
157
+ - Returns the object
158
+ ```
159
+ @post = Posts.where(author: 'Cullen Jett').first
160
+ @post.update_attributes(author: 'Socrates', content: 'Something enlightening...') # => <Post: @id: 1, @author: 'Socrates', @content: 'Something enlightening...'
161
+ ```
162
+
163
+ * **#assign_attributes(attributes_hash)**
164
+ - Only changes the objects attributes in memory (i.e. does not save to QuickBase)
165
+ - Useful for assigning multiple attributes at once, otherwise you could use the field name's attr_accessor to change a single attribute.
166
+ - Returns the object
167
+ ```
168
+ @post = Posts.where(author: 'Cullen Jett').first
169
+ @post.assign_attributes(author: 'Socrates', content: 'Something enlightening...')
170
+ @post.save
171
+ ```
172
+
173
+ ## Testing
174
+ Unfortunately you will not be able to run the test suite unless you have access to the QuickBase application used as the test database *or* you create your own QuickBase app to test against that mimics the test fakes. Eventually the test calls will be stubbed out so anyone can test it, but I've got stuff to do -- pull requests are welcome :)
175
+
176
+ As of now the tests serve more as documentation for those who don't have access to the testing QuickBase app.
177
+
178
+ If you're lucky enough to work with me then I can grant you access to the app and you can run the suite until your fingers bleed. You'll just need to modify `spec/quickbase_record_config.rb` to use your own credentials.
179
+
180
+ ## Contributing
181
+
182
+ 1. Fork it ( https://github.com/[my-github-username]/quickbase_record/fork )
183
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
184
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
185
+ 4. Push to the branch (`git push origin my-new-feature`)
186
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ require "quickbase_record/version"
2
+ require 'active_support/all'
3
+ require 'active_model'
4
+ require 'quickbase' # This is the file name for the 'advantage_quickbase' gem. Idk why Ben named it this way...
5
+ require_relative 'quickbase_record/configuration'
6
+ require_relative 'quickbase_record/field_mapping'
7
+ require_relative 'quickbase_record/client'
8
+ require_relative 'quickbase_record/queries'
9
+ require_relative 'quickbase_record/model'
10
+
11
+ module QuickbaseRecord
12
+ class << self
13
+ attr_accessor :configuration
14
+ end
15
+
16
+ def self.configuration
17
+ @configuration ||= Configuration.new
18
+ end
19
+
20
+ def self.configure
21
+ yield configuration
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module QuickbaseRecord
2
+ module Client
3
+ include ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def qb_client
7
+ self.new.qb_client
8
+ end
9
+ end
10
+
11
+ def qb_client
12
+ realm = QuickbaseRecord.configuration.realm
13
+ username = QuickbaseRecord.configuration.username
14
+ password = QuickbaseRecord.configuration.password
15
+
16
+ @qb_client ||= AdvantageQuickbase::API.new(realm, username, password)
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ module QuickbaseRecord
2
+ class Configuration
3
+ attr_accessor :realm, :username, :password
4
+
5
+ def initialize
6
+ @realm = ''
7
+ @username = ''
8
+ @password = ''
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module QuickbaseRecord
2
+ class FieldMapping
3
+ attr_accessor :fields
4
+
5
+ def initialize(fields={})
6
+ @fields = fields
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,64 @@
1
+ module QuickbaseRecord
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+ include QuickbaseRecord::Queries
5
+ include QuickbaseRecord::Client
6
+
7
+ included do
8
+ extend QuickbaseRecord::Queries
9
+ extend ActiveModel::Naming
10
+ extend ActiveModel::Callbacks
11
+ include ActiveModel::Validations
12
+ include ActiveModel::Conversion
13
+ end
14
+
15
+ module ClassMethods
16
+ def fields(fields_hash={})
17
+ @fields ||= FieldMapping.new(fields_hash).fields
18
+ end
19
+
20
+ def define_fields(fields)
21
+ fields(fields)
22
+ end
23
+ end
24
+
25
+ def initialize(attributes={})
26
+ create_attr_accesssors
27
+ assign_attributes(attributes) if attributes
28
+
29
+ super()
30
+ end
31
+
32
+ def persisted?
33
+ false
34
+ end
35
+
36
+ def create_attr_accesssors
37
+ self.class.fields.each do |field_name, fid|
38
+ self.class.send(:attr_accessor, field_name)
39
+ end
40
+ end
41
+
42
+ def assign_attributes(new_attributes)
43
+ if !new_attributes.respond_to?(:stringify_keys)
44
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
45
+ end
46
+ return if new_attributes.blank?
47
+
48
+ attributes = new_attributes.stringify_keys
49
+ _assign_attributes(attributes)
50
+ end
51
+
52
+ def _assign_attributes(attributes)
53
+ attributes.each do |k, v|
54
+ _assign_attribute(k, v)
55
+ end
56
+ end
57
+
58
+ def _assign_attribute(k, v)
59
+ if respond_to?("#{k}=")
60
+ public_send("#{k}=", v)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,166 @@
1
+ require_relative 'client'
2
+
3
+ module QuickbaseRecord
4
+ module Queries
5
+ extend ActiveSupport::Concern
6
+ include QuickbaseRecord::Client
7
+
8
+ UNWRITABLE_FIELDS = ['dbid', 'id', 'date_created', 'date_modified', 'record_owner', 'last_modified_by']
9
+
10
+ module ClassMethods
11
+ def dbid
12
+ @dbid ||= fields[:dbid]
13
+ end
14
+
15
+ def clist
16
+ @clist ||= fields.reject{ |field_name| field_name == :dbid }.values.join('.')
17
+ end
18
+
19
+ def find(id)
20
+ query_options = { query: build_query(id: id), clist: clist }
21
+ query_response = qb_client.do_query(dbid, query_options).first
22
+ converted_response = convert_quickbase_response(query_response)
23
+
24
+ new(converted_response)
25
+ end
26
+
27
+ def where(query_hash)
28
+ query_options = { query: build_query(query_hash), clist: clist }
29
+ query_response = qb_client.do_query(dbid, query_options)
30
+
31
+ array_of_new_objects = query_response.map do |response|
32
+ converted_response = convert_quickbase_response(response)
33
+ new(converted_response)
34
+ end
35
+
36
+ return array_of_new_objects
37
+ end
38
+
39
+ def create(attributes={})
40
+ raise StandardErrror, "You cannot call #{self}.create() with an :id attribute" if attributes.include?(:id)
41
+ object = new(attributes)
42
+ object.save
43
+ return object
44
+ end
45
+
46
+ def query(query_string)
47
+ query_options = { query: build_query(query_string), clist: clist }
48
+ query_response = qb_client.do_query(dbid, query_options)
49
+
50
+ array_of_new_objects = query_response.map do |response|
51
+ converted_response = convert_quickbase_response(response)
52
+ new(converted_response)
53
+ end
54
+
55
+ return array_of_new_objects
56
+ end
57
+
58
+ def build_query(query_hash)
59
+ return convert_query_string(query_hash) if query_hash.is_a? String
60
+
61
+ query_hash.map do |field_name, values|
62
+ fid = convert_field_name_to_fid(field_name)
63
+ if values.is_a? Array
64
+ join_with_or(fid, values)
65
+ elsif values.is_a? Hash
66
+ join_with_custom(fid, values)
67
+ else
68
+ join_with_and(fid, values)
69
+ end
70
+ end.join("AND")
71
+ end
72
+
73
+ private
74
+
75
+ def join_with_and(fid, value, comparitor="EX")
76
+ "{'#{fid}'.#{comparitor}.'#{value}'}"
77
+ end
78
+
79
+ def join_with_or(fid, array, comparitor="EX")
80
+ array.map do |value|
81
+ "{'#{fid}'.#{comparitor}.'#{value}'}"
82
+ end.join("OR")
83
+ end
84
+
85
+ def join_with_custom(fid, hash)
86
+ comparitor = hash.keys.first
87
+ value = hash.values.first
88
+
89
+ if value.is_a? Array
90
+ join_with_or(fid, value, comparitor)
91
+ else
92
+ "{'#{fid}'.#{comparitor}.'#{value}'}"
93
+ end
94
+
95
+ end
96
+
97
+ def convert_field_name_to_fid(field_name)
98
+ self.fields[field_name.to_sym].to_s
99
+ end
100
+
101
+ def covert_fid_to_field_name(fid)
102
+ self.fields.invert[fid.to_i]
103
+ end
104
+
105
+ def convert_quickbase_response(response)
106
+ result = {}
107
+
108
+ response.each do |fid, value|
109
+ field_name = covert_fid_to_field_name(fid)
110
+ result[field_name] = value
111
+ end
112
+
113
+ result
114
+ end
115
+
116
+ def convert_query_string(query_string)
117
+ match_found = false
118
+
119
+ uses_field_name = query_string.match(/\{'?(.*)'?\..*\.'?.*'?\}/)[1].to_i == 0
120
+
121
+ if !uses_field_name
122
+ return query_string
123
+ end
124
+
125
+ fields.each do |field_name, fid|
126
+ field_name = field_name.to_s
127
+ match_string = "\{'?(#{field_name})'?\..*\.'?.*'?\}"
128
+ if query_string.scan(/#{match_string}/).length > 0
129
+ query_string.gsub!(field_name, fid.to_s)
130
+ match_found = true
131
+ end
132
+ end
133
+
134
+ if !match_found
135
+ raise ArgumentError, "Invalid arguments on #{self}.query() - no matching field name found. \nMake sure the field is part of your class configuration."
136
+ end
137
+
138
+ query_string
139
+ end
140
+ end
141
+
142
+ # INSTANCE METHODS
143
+ def save
144
+ current_object = {}
145
+ self.class.fields.each do |field_name, fid|
146
+ current_object[fid] = public_send(field_name) unless UNWRITABLE_FIELDS.include?(field_name.to_s)
147
+ current_object[self.class.fields[:id]] = self.id if self.id
148
+ end
149
+ self.id = qb_client.import_from_csv(self.class.dbid, [current_object]).first
150
+ end
151
+
152
+ def delete
153
+ return false if !self.id
154
+
155
+ successful = qb_client.delete_record(self.class.dbid, self.id)
156
+
157
+ return successful ? self.id : false
158
+ end
159
+
160
+ def update_attributes(attributes={})
161
+ self.assign_attributes(attributes)
162
+ self.save
163
+ return self
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,3 @@
1
+ module QuickbaseRecord
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'quickbase_record/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "quickbase_record"
8
+ spec.version = QuickbaseRecord::VERSION
9
+ spec.authors = ["Cullen Jett"]
10
+ spec.email = ["cullenjett@gmail.com"]
11
+ spec.summary = "An ActiveRecord-style ORM for using Intuit QuickBase tables as models."
12
+ spec.description = "QuickbaseRecord is a baller ActiveRecord-style ORM for using the Intuit QuickBase platform as a database for Ruby or Ruby on Rails applications."
13
+ spec.homepage = "https://github.com/cullenjett/quickbase_record"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "shoulda-matchers"
25
+
26
+ spec.add_runtime_dependency "advantage_quickbase"
27
+ spec.add_runtime_dependency "activesupport"
28
+ spec.add_runtime_dependency "activemodel"
29
+ end
@@ -0,0 +1,13 @@
1
+ require './lib/quickbase_record'
2
+
3
+ class StudentFake
4
+ include QuickbaseRecord::Model
5
+
6
+ StudentFake.define_fields ({
7
+ :dbid => 'bjzrx8ckw',
8
+ :id => 3,
9
+ :name => 6,
10
+ :grade => 7,
11
+ :email => 8
12
+ })
13
+ end
@@ -0,0 +1,14 @@
1
+ require './lib/quickbase_record'
2
+
3
+ class TeacherFake
4
+ include QuickbaseRecord::Model
5
+
6
+ define_fields ({
7
+ :dbid => "bjzrx8cjn",
8
+ :id => 3,
9
+ :name => 6,
10
+ :subject => 7,
11
+ :salary => 8
12
+ })
13
+
14
+ end
@@ -0,0 +1,30 @@
1
+ require './spec/fakes/student_fake'
2
+
3
+ RSpec.describe QuickbaseRecord::Model do
4
+ describe '.define_fields' do
5
+ it "sets the class field mappings for field names => FIDs" do
6
+ StudentFake.define_fields ({
7
+ :dbid => 'bjzrx8ckw',
8
+ :id => 3,
9
+ :name => 6,
10
+ :grade => 7,
11
+ :email => 8
12
+ })
13
+
14
+ expect(StudentFake.fields[:id]).to eq(3)
15
+ end
16
+ end
17
+
18
+ describe 'initialize' do
19
+ it "dynamically creates attr_accessors for the class based on the configuration fields" do
20
+ student = StudentFake.new(name: 'Cullen Jett')
21
+ expect(student.respond_to?(:name)).to be true
22
+ expect(student.respond_to?(:name=)).to be true
23
+ end
24
+
25
+ it "assigns passed in attributes to itself" do
26
+ student = StudentFake.new(name: 'Cullen Jett')
27
+ expect(student.name).to eq('Cullen Jett')
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,156 @@
1
+ require './spec/fakes/teacher_fake'
2
+
3
+ RSpec.describe QuickbaseRecord::Queries do
4
+ describe '.find' do
5
+ it "finds a single Teacher given an ID" do
6
+ teacher = TeacherFake.find(1)
7
+ expect(teacher.id).to eq("1")
8
+ end
9
+
10
+ it "returns an object of the Teacher class" do
11
+ teacher = TeacherFake.find(1)
12
+ expect(teacher).to be_a TeacherFake
13
+ end
14
+ end
15
+
16
+ describe '.where' do
17
+ it "returns an array of objects" do
18
+ teachers = TeacherFake.where(id: 1)
19
+ expect(teachers).to be_a Array
20
+ end
21
+
22
+ it "returns an object of the Teacher class" do
23
+ teachers = TeacherFake.where(id: 1)
24
+ expect(teachers.first).to be_a TeacherFake
25
+ end
26
+ end
27
+
28
+ describe '.create' do
29
+ it "saves the object immediately" do
30
+ teacher = TeacherFake.create(name: 'Professor Dumbledore')
31
+ expect(teacher.id).to be > 1
32
+ teacher.delete
33
+ end
34
+
35
+ it "throws an error if an :id argument is passed" do
36
+ expect { TeacherFake.create(id: 1, name: 'Professor Dumbledore') }.to raise_error
37
+ end
38
+ end
39
+
40
+ describe '.query' do
41
+ it "returns an array of objects" do
42
+ teachers = TeacherFake.query("{id.EX.'1'}")
43
+ expect(teachers).to be_a Array
44
+ end
45
+
46
+ it "returns an object of the Teacher class" do
47
+ teachers = TeacherFake.query("{id.EX.'1'}")
48
+ expect(teachers.first).to be_a TeacherFake
49
+ end
50
+
51
+ it "accepts FIDs instead of field names" do
52
+ teachers = TeacherFake.query("{'3'.EX.'1'}")
53
+ expect(teachers.first.id).to eq('1')
54
+ end
55
+ end
56
+
57
+ describe '#save' do
58
+ it "creates a new record for an object without an ID and sets it's new ID" do
59
+ cullen = TeacherFake.new(name: 'Cullen Jett', salary: '1,000,000.00')
60
+ new_id = cullen.save
61
+ expect(new_id).to be_truthy
62
+ expect(cullen.id).to eq(new_id)
63
+ end
64
+
65
+ it "edits an object that has an existing ID" do
66
+ cullen = TeacherFake.where(name: 'Cullen Jett').first
67
+ cullen.subject = 'Ruby on Rails'
68
+ cullen.name = "THE #{cullen.name}"
69
+ expect(cullen.save).to be_truthy
70
+ cullen.delete
71
+ end
72
+ end
73
+
74
+ describe '#delete' do
75
+ it "deletes an object from QuickBase" do
76
+ socrates = TeacherFake.new(name: 'Socrates')
77
+ socrates.save
78
+ old_id = socrates.id
79
+ expect(socrates.delete).to eq(old_id)
80
+ end
81
+
82
+ it "returns false if the object doesn't yet have an ID" do
83
+ expect(TeacherFake.new(name: 'Socrates').delete).to be false
84
+ end
85
+ end
86
+
87
+ describe '#assign_attributes' do
88
+ it "assigns an objects attributes given a hash attributes and their values" do
89
+ teacher = TeacherFake.new(name: 'teacher1', salary: 35000)
90
+ teacher.assign_attributes(name: 'teacher2', salary: 40000)
91
+ expect(teacher.name).to eq('teacher2')
92
+ expect(teacher.salary).to eq(40000)
93
+ end
94
+
95
+ it "doesn't save the object" do
96
+ teacher = TeacherFake.new(name: 'teacher1', salary: 35000)
97
+ teacher.assign_attributes(name: 'teacher2', salary: 40000)
98
+ expect(teacher.id).to be_falsey
99
+ end
100
+ end
101
+
102
+ describe '#update_attributes' do
103
+ it "assigns an objects attributes given a hash attributes and their values" do
104
+ teacher = TeacherFake.new(name: 'teacher1', salary: 35000)
105
+ teacher.update_attributes(name: 'teacher2', salary: 40000)
106
+ expect(teacher.name).to eq('teacher2')
107
+ expect(teacher.salary).to eq(40000)
108
+ end
109
+
110
+ it "saves the object" do
111
+ teacher = TeacherFake.new(name: 'teacher1', salary: 35000)
112
+ teacher.update_attributes(name: 'teacher2', salary: 40000)
113
+ expect(teacher.id).to be_truthy
114
+ teacher.delete
115
+ end
116
+ end
117
+
118
+ # This is sort of a private method, but it seems pretty important so I'm keeping these tests.
119
+ # It could probably stand to be extracted into a separate class...
120
+ describe '.build_query' do
121
+ it "converts a hash into QuickBase query format with EX as the default comparitor" do
122
+ hash = {id: 1}
123
+ expect(TeacherFake.build_query(hash)).to eq("{'3'.EX.'1'}")
124
+ end
125
+
126
+ it "combines multiple key/value pairs with AND" do
127
+ hash = {id: 1, name: 'Mrs. Brown'}
128
+ expect(TeacherFake.build_query(hash)).to eq("{'3'.EX.'1'}AND{'6'.EX.'Mrs. Brown'}")
129
+ end
130
+
131
+ it "combines values that are arrays with OR" do
132
+ hash = {id: [1, 2]}
133
+ expect(TeacherFake.build_query(hash)).to eq("{'3'.EX.'1'}OR{'3'.EX.'2'}")
134
+ end
135
+
136
+ it "accepts custom comparitors via a nested hash" do
137
+ hash = {id: {XEX: 1}}
138
+ expect(TeacherFake.build_query(hash)).to eq("{'3'.XEX.'1'}")
139
+ end
140
+
141
+ it "combines custom comparitors using arrays with OR" do
142
+ hash = {id: {XEX: [1, 2]}}
143
+ expect(TeacherFake.build_query(hash)).to eq("{'3'.XEX.'1'}OR{'3'.XEX.'2'}")
144
+ end
145
+
146
+ it "converts field names to FIDs" do
147
+ hash = "{name.EX.'Cullen Jett'}"
148
+ expect(TeacherFake.build_query(hash)).to eq("{6.EX.'Cullen Jett'}")
149
+ end
150
+
151
+ it "throws an error for invaid field names" do
152
+ hash = "{not_a_field_name.EX.'Cullen Jett'}"
153
+ expect { TeacherFake.build_query(hash) }.to raise_error
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,5 @@
1
+ QuickbaseRecord.configure do |config|
2
+ config.realm = "ais"
3
+ config.username = ENV["QB_USERNAME"]
4
+ config.password = ENV["QB_PASSWORD"]
5
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quickbase_record
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Cullen Jett
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-11 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
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: shoulda-matchers
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: advantage_quickbase
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activemodel
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: QuickbaseRecord is a baller ActiveRecord-style ORM for using the Intuit
112
+ QuickBase platform as a database for Ruby or Ruby on Rails applications.
113
+ email:
114
+ - cullenjett@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - .gitignore
120
+ - .rspec
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - lib/quickbase_record.rb
126
+ - lib/quickbase_record/client.rb
127
+ - lib/quickbase_record/configuration.rb
128
+ - lib/quickbase_record/field_mapping.rb
129
+ - lib/quickbase_record/model.rb
130
+ - lib/quickbase_record/queries.rb
131
+ - lib/quickbase_record/version.rb
132
+ - quickbase_record.gemspec
133
+ - spec/fakes/student_fake.rb
134
+ - spec/fakes/teacher_fake.rb
135
+ - spec/model_spec.rb
136
+ - spec/queries_spec.rb
137
+ - spec/quickbase_record_config.rb
138
+ homepage: https://github.com/cullenjett/quickbase_record
139
+ licenses:
140
+ - MIT
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ! '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 2.4.2
159
+ signing_key:
160
+ specification_version: 4
161
+ summary: An ActiveRecord-style ORM for using Intuit QuickBase tables as models.
162
+ test_files:
163
+ - spec/fakes/student_fake.rb
164
+ - spec/fakes/teacher_fake.rb
165
+ - spec/model_spec.rb
166
+ - spec/queries_spec.rb
167
+ - spec/quickbase_record_config.rb