airctiverecord 0.2.0 → 0.2.2
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 +4 -4
- data/README.md +22 -8
- data/examples/associations_example.rb +10 -10
- data/examples/basic_usage.rb +8 -8
- data/examples/boolean_fields.rb +3 -3
- data/examples/chainable_queries.rb +4 -4
- data/examples/field_mapping_example.rb +6 -6
- data/examples/readonly_fields.rb +9 -9
- data/examples/scope_isolation.rb +6 -6
- data/lib/airctiverecord/associations.rb +4 -4
- data/lib/airctiverecord/attribute_methods.rb +5 -7
- data/lib/airctiverecord/base.rb +28 -38
- data/lib/airctiverecord/relation.rb +3 -5
- data/lib/airctiverecord/scoping.rb +19 -33
- data/lib/airctiverecord/validations.rb +1 -0
- data/lib/airctiverecord/version.rb +1 -1
- metadata +15 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae3765d9f17173d90f9e6bbbce91fca4adaf5dbcf2543acacab671cb9ecb577c
|
|
4
|
+
data.tar.gz: ef36c911e89d741eb5f33d9a0bba89b2b866283e4f3d89cc447b089247ce36d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c87e579fc0a84a2acfe7f6b9649606190672efec26d80c769432d5e135e80881cb3141410d8a0a3fabad55f48843883a345f667c08673889cae96fdcace44bc8
|
|
7
|
+
data.tar.gz: a110512bd802d6e5181bc982874ffce213a7c01f4055211a795deacaf0f888797cd68663fc7bdb3585318c80d2c182b93b125ba60f5b9a1f7be54ed754911004
|
data/README.md
CHANGED
|
@@ -106,6 +106,16 @@ user = User.first
|
|
|
106
106
|
user = User.find_by(email: "alice@example.com")
|
|
107
107
|
user = User.find_by!(email: "alice@example.com") # raises if not found
|
|
108
108
|
|
|
109
|
+
# find or create
|
|
110
|
+
user = User.find_or_create_by(email: "alice@example.com")
|
|
111
|
+
user = User.find_or_create_by!(email: "alice@example.com") # raises on validation error
|
|
112
|
+
|
|
113
|
+
# block is yielded to the new record only (not if found)
|
|
114
|
+
user = User.find_or_create_by(email: "alice@example.com") do |u|
|
|
115
|
+
u.first_name = "Alice"
|
|
116
|
+
u.role = "admin"
|
|
117
|
+
end
|
|
118
|
+
|
|
109
119
|
# update
|
|
110
120
|
user.update(first_name: "Alicia")
|
|
111
121
|
user.first_name = "Alicia"
|
|
@@ -323,17 +333,21 @@ User.first(10) # limit(10)
|
|
|
323
333
|
|
|
324
334
|
**large tables**
|
|
325
335
|
|
|
326
|
-
for tables with
|
|
336
|
+
for tables with many records, use `find_each` or `find_in_batches`:
|
|
327
337
|
|
|
328
338
|
```ruby
|
|
329
|
-
#
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
batch = User.limit(100).offset(offset).to_a
|
|
333
|
-
break if batch.empty?
|
|
334
|
-
batch.each { |user| process(user) }
|
|
335
|
-
offset += 100
|
|
339
|
+
# process records one at a time (fetches in batches of 100 internally)
|
|
340
|
+
User.where(active: true).find_each do |user|
|
|
341
|
+
process(user)
|
|
336
342
|
end
|
|
343
|
+
|
|
344
|
+
# process records in batches
|
|
345
|
+
User.find_in_batches(batch_size: 50) do |batch|
|
|
346
|
+
bulk_import(batch)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# works with scopes and conditions
|
|
350
|
+
User.active.admins.find_each(batch_size: 50) { |user| sync(user) }
|
|
337
351
|
```
|
|
338
352
|
|
|
339
353
|
## license
|
|
@@ -12,26 +12,26 @@ BASE_KEY = ENV.fetch("AIRTABLE_BASE_KEY")
|
|
|
12
12
|
class Team < AirctiveRecord::Base
|
|
13
13
|
self.base_key = BASE_KEY
|
|
14
14
|
self.table_name = "Teams"
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
attribute :name
|
|
17
17
|
attribute :description
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
has_many :users
|
|
20
20
|
has_one :leader, class_name: "User", foreign_key: "Leader"
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
validates :name, presence: true
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
class User < AirctiveRecord::Base
|
|
26
26
|
self.base_key = BASE_KEY
|
|
27
27
|
self.table_name = "Users"
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
attribute :name
|
|
30
30
|
attribute :email
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
belongs_to :team
|
|
33
33
|
has_many :tasks
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
validates :name, presence: true
|
|
36
36
|
validates :email, presence: true
|
|
37
37
|
end
|
|
@@ -39,17 +39,17 @@ end
|
|
|
39
39
|
class Task < AirctiveRecord::Base
|
|
40
40
|
self.base_key = BASE_KEY
|
|
41
41
|
self.table_name = "Tasks"
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
attribute :title
|
|
44
44
|
attribute :status
|
|
45
45
|
attribute :description
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
belongs_to :user
|
|
48
48
|
has_one :team, through: :user
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
validates :title, presence: true
|
|
51
51
|
validates :status, inclusion: { in: %w[pending in_progress completed] }
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
scope :pending, -> { where("{Status} = 'pending'") }
|
|
54
54
|
scope :completed, -> { where("{Status} = 'completed'") }
|
|
55
55
|
end
|
data/examples/basic_usage.rb
CHANGED
|
@@ -11,33 +11,33 @@ Norairrecord.api_key = ENV.fetch("AIRTABLE_API_KEY")
|
|
|
11
11
|
class User < AirctiveRecord::Base
|
|
12
12
|
self.base_key = ENV.fetch("AIRTABLE_BASE_KEY")
|
|
13
13
|
self.table_name = "Users"
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
# Define attributes
|
|
16
16
|
attribute :name
|
|
17
17
|
attribute :email
|
|
18
18
|
attribute :age
|
|
19
19
|
attribute :role
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
# Validations
|
|
22
22
|
validates :name, presence: true
|
|
23
23
|
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
24
24
|
validates :age, numericality: { greater_than: 0, less_than: 150 }, allow_nil: true
|
|
25
25
|
validates :role, inclusion: { in: %w[admin user guest] }, allow_nil: true
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
# Callbacks
|
|
28
28
|
before_save :normalize_email
|
|
29
29
|
after_create :log_creation
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
# Scopes
|
|
32
32
|
scope :admins, -> { where("{Role} = 'admin'") }
|
|
33
33
|
scope :active, -> { where("{Active} = TRUE()") }
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
private
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
def normalize_email
|
|
38
38
|
self.email = email.downcase.strip if email.present?
|
|
39
39
|
end
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
def log_creation
|
|
42
42
|
puts "Created user: #{name} (#{email})"
|
|
43
43
|
end
|
|
@@ -86,6 +86,6 @@ puts "Name is now: #{user.name}"
|
|
|
86
86
|
puts "\nTrying to create invalid user..."
|
|
87
87
|
invalid_user = User.new(email: "not-an-email")
|
|
88
88
|
puts "Valid? #{invalid_user.valid?}"
|
|
89
|
-
puts "Errors: #{invalid_user.errors.full_messages.join(
|
|
89
|
+
puts "Errors: #{invalid_user.errors.full_messages.join(", ")}"
|
|
90
90
|
|
|
91
91
|
puts "\nDone!"
|
data/examples/boolean_fields.rb
CHANGED
|
@@ -7,17 +7,17 @@ require "airctiverecord"
|
|
|
7
7
|
class User < AirctiveRecord::Base
|
|
8
8
|
self.base_key = "appTest123"
|
|
9
9
|
self.table_name = "Users"
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
field :name, "Name"
|
|
12
12
|
field :email, "Email"
|
|
13
13
|
field :active, "Active", type: :boolean
|
|
14
14
|
field :verified, "Verified", type: :boolean
|
|
15
15
|
field :admin, "Admin", type: :boolean
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
scope :active, -> { where(active: true) }
|
|
18
18
|
scope :verified, -> { where(verified: true) }
|
|
19
19
|
scope :admins, -> { where(admin: true) }
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
def self.records(**params)
|
|
22
22
|
puts "records(#{params.inspect})"
|
|
23
23
|
[]
|
|
@@ -7,18 +7,18 @@ require "airctiverecord"
|
|
|
7
7
|
class User < AirctiveRecord::Base
|
|
8
8
|
self.base_key = "appTest123"
|
|
9
9
|
self.table_name = "Users"
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
field :first_name, "First Name"
|
|
12
12
|
field :email, "Email Address"
|
|
13
13
|
field :role, "Role"
|
|
14
14
|
field :active, "Active"
|
|
15
15
|
field :age, "Age"
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
scope :active, -> { where(active: true) }
|
|
18
18
|
scope :admins, -> { where(role: "admin") }
|
|
19
19
|
scope :adults, -> { where("AND({Age} >= 18, {Age} < 65)") }
|
|
20
20
|
scope :recent, -> { order(created_at: :desc).limit(10) }
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
def self.records(**params)
|
|
23
23
|
puts "Would call Airtable API with:"
|
|
24
24
|
puts params.inspect
|
|
@@ -44,7 +44,7 @@ User.where(age: 18..65).to_a
|
|
|
44
44
|
puts
|
|
45
45
|
|
|
46
46
|
puts "=== IN queries ==="
|
|
47
|
-
User.where(role: [
|
|
47
|
+
User.where(role: %w[admin moderator guest]).to_a
|
|
48
48
|
puts
|
|
49
49
|
|
|
50
50
|
puts "=== raw formulas still work ==="
|
|
@@ -11,7 +11,7 @@ Norairrecord.api_key = ENV.fetch("AIRTABLE_API_KEY", "test_key")
|
|
|
11
11
|
class Contact < AirctiveRecord::Base
|
|
12
12
|
self.base_key = ENV.fetch("AIRTABLE_BASE_KEY", "appTest123")
|
|
13
13
|
self.table_name = "Contacts"
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
# Map Ruby attribute names to Airtable field names with spaces
|
|
16
16
|
field :first_name, "First Name"
|
|
17
17
|
field :last_name, "Last Name"
|
|
@@ -22,21 +22,21 @@ class Contact < AirctiveRecord::Base
|
|
|
22
22
|
field :linkedin_url, "LinkedIn URL"
|
|
23
23
|
field :date_added, "Date Added"
|
|
24
24
|
field :is_vip, "VIP?"
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
# Validations using Ruby attribute names
|
|
27
27
|
validates :first_name, :last_name, presence: true
|
|
28
28
|
validates :email_address, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true
|
|
29
29
|
validates :phone_number, length: { minimum: 10 }, allow_blank: true
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
# Callbacks
|
|
32
32
|
before_save :normalize_email
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
# Scopes
|
|
35
35
|
scope :vip, -> { where("{VIP?} = TRUE()") }
|
|
36
36
|
scope :with_email, -> { where("{Email Address} != ''") }
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
private
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
def normalize_email
|
|
41
41
|
self.email_address = email_address&.downcase&.strip
|
|
42
42
|
end
|
data/examples/readonly_fields.rb
CHANGED
|
@@ -10,15 +10,15 @@ end
|
|
|
10
10
|
|
|
11
11
|
class Contact < AirpplicationRecord
|
|
12
12
|
self.table_name = "Contacts"
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
field :name, "Name"
|
|
15
15
|
field :email, "Email"
|
|
16
16
|
field :company, "Company"
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
# lookup fields (pulled from linked Company record)
|
|
19
19
|
field :company_name, "Company Name (from Company)", readonly: true
|
|
20
20
|
field :company_address, "Company Address (from Company)", readonly: true
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# formula fields (computed by airtable)
|
|
23
23
|
field :full_name, "Full Name (formula)", readonly: true
|
|
24
24
|
end
|
|
@@ -26,16 +26,16 @@ end
|
|
|
26
26
|
contact = Contact.new(
|
|
27
27
|
name: "Alice",
|
|
28
28
|
email: "alice@example.com",
|
|
29
|
-
company_name: "Acme Corp"
|
|
29
|
+
company_name: "Acme Corp" # readonly field - silently ignored
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
puts "=== readonly fields are readable ==="
|
|
33
33
|
contact.instance_variable_set(:@fields, {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})
|
|
34
|
+
"Name" => "Alice",
|
|
35
|
+
"Email" => "alice@example.com",
|
|
36
|
+
"Company Name (from Company)" => "Acme Corp",
|
|
37
|
+
"Full Name (formula)" => "Alice Smith"
|
|
38
|
+
})
|
|
39
39
|
|
|
40
40
|
puts "company_name: #{contact.company_name}"
|
|
41
41
|
puts "full_name: #{contact.full_name}"
|
data/examples/scope_isolation.rb
CHANGED
|
@@ -7,13 +7,13 @@ require "airctiverecord"
|
|
|
7
7
|
class User < AirctiveRecord::Base
|
|
8
8
|
self.base_key = "appTest123"
|
|
9
9
|
self.table_name = "Users"
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
field :role, "Role"
|
|
12
12
|
field :active, "Active"
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
scope :active, -> { where(active: true) }
|
|
15
15
|
scope :admins, -> { where(role: "admin") }
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
def self.records(**params)
|
|
18
18
|
puts "User.records called with: #{params.inspect}"
|
|
19
19
|
[]
|
|
@@ -23,13 +23,13 @@ end
|
|
|
23
23
|
class Post < AirctiveRecord::Base
|
|
24
24
|
self.base_key = "appTest123"
|
|
25
25
|
self.table_name = "Posts"
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
field :status, "Status"
|
|
28
28
|
field :featured, "Featured"
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
scope :published, -> { where(status: "published") }
|
|
31
31
|
scope :featured, -> { where(featured: true) }
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
def self.records(**params)
|
|
34
34
|
puts "Post.records called with: #{params.inspect}"
|
|
35
35
|
[]
|
|
@@ -8,13 +8,13 @@ module AirctiveRecord
|
|
|
8
8
|
# just add activerecord-style defaults, norairrecord does the heavy lifting
|
|
9
9
|
def has_many(name, options_arg = nil, **options_kwargs)
|
|
10
10
|
options = options_arg || options_kwargs
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
if options[:through]
|
|
13
13
|
define_has_many_through(name, options)
|
|
14
14
|
else
|
|
15
15
|
column = options[:column] || options[:foreign_key] || "#{name.to_s.singularize}_ids"
|
|
16
16
|
klass = options[:class_name] || name.to_s.classify
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
super(name, { column: column, class: klass }.merge(options))
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -22,14 +22,14 @@ module AirctiveRecord
|
|
|
22
22
|
def belongs_to(name, **options)
|
|
23
23
|
column = options[:column] || options[:foreign_key] || "#{name}_id"
|
|
24
24
|
klass = options[:class_name] || name.to_s.classify
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
super(name, { column: column, class: klass }.merge(options))
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def has_one(name, **options)
|
|
30
30
|
column = options[:column] || options[:foreign_key] || "#{name}_id"
|
|
31
31
|
klass = options[:class_name] || name.to_s.classify
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
super(name, { column: column, class: klass }.merge(options))
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -22,10 +22,10 @@ module AirctiveRecord
|
|
|
22
22
|
airtable_field_name ||= attr_name
|
|
23
23
|
readonly = options[:readonly] || options[:read_only]
|
|
24
24
|
field_type = options[:type]
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
field_mappings[attr_name] = airtable_field_name
|
|
27
27
|
readonly_fields << airtable_field_name if readonly
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
define_attribute_methods attr_name
|
|
30
30
|
|
|
31
31
|
define_method(attr_name) do
|
|
@@ -35,14 +35,14 @@ module AirctiveRecord
|
|
|
35
35
|
|
|
36
36
|
if readonly
|
|
37
37
|
# readonly fields silently ignore sets (airtable rejects them anyway)
|
|
38
|
-
define_method("#{attr_name}=") do |
|
|
38
|
+
define_method("#{attr_name}=") do |_value|
|
|
39
39
|
nil
|
|
40
40
|
end
|
|
41
41
|
else
|
|
42
42
|
define_method("#{attr_name}=") do |value|
|
|
43
43
|
field_name = self.class.field_mappings[attr_name]
|
|
44
44
|
return if self[field_name] == value
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
send("#{attr_name}_will_change!") unless self[field_name] == value
|
|
47
47
|
self[field_name] = value
|
|
48
48
|
end
|
|
@@ -66,9 +66,7 @@ module AirctiveRecord
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
def attributes
|
|
70
|
-
fields
|
|
71
|
-
end
|
|
69
|
+
def attributes = fields
|
|
72
70
|
|
|
73
71
|
def attributes=(attrs)
|
|
74
72
|
attrs.each do |key, value|
|
data/lib/airctiverecord/base.rb
CHANGED
|
@@ -27,19 +27,19 @@ module AirctiveRecord
|
|
|
27
27
|
@relation_class ||= Class.new(AirctiveRecord::Relation)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def relation_class_name
|
|
31
|
-
"#{name}::Relation"
|
|
32
|
-
end
|
|
30
|
+
def relation_class_name = "#{name}::Relation"
|
|
33
31
|
end
|
|
34
32
|
|
|
35
33
|
def initialize(attributes = {}, **kwargs)
|
|
36
34
|
# Extract id and created_at if present
|
|
37
35
|
id = kwargs.delete(:id)
|
|
38
36
|
created_at = kwargs.delete(:created_at)
|
|
39
|
-
|
|
40
|
-
# Merge positional hash and kwargs to handle both styles
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
|
|
38
|
+
# Merge positional hash and kwargs to handle both styles.
|
|
39
|
+
# Call to_h to handle ActionController::Parameters, which is not a Hash subclass.
|
|
40
|
+
attr_hash = attributes.respond_to?(:to_h) ? attributes.to_h : {}
|
|
41
|
+
all_attrs = attr_hash.merge(kwargs)
|
|
42
|
+
|
|
43
43
|
# Norairrecord::Table expects field names as STRING keys
|
|
44
44
|
# We need to convert Ruby attribute names to Airtable field names
|
|
45
45
|
mapped_attrs = {}
|
|
@@ -47,7 +47,7 @@ module AirctiveRecord
|
|
|
47
47
|
field_name = self.class.field_mappings[key.to_s] || key.to_s
|
|
48
48
|
mapped_attrs[field_name.to_s] = value # Ensure string keys
|
|
49
49
|
end
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
# Call norairrecord's initialize properly
|
|
52
52
|
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0.0")
|
|
53
53
|
if mapped_attrs.empty?
|
|
@@ -58,7 +58,7 @@ module AirctiveRecord
|
|
|
58
58
|
else
|
|
59
59
|
super(mapped_attrs, id: id, created_at: created_at)
|
|
60
60
|
end
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
clear_changes_information
|
|
63
63
|
end
|
|
64
64
|
|
|
@@ -68,34 +68,31 @@ module AirctiveRecord
|
|
|
68
68
|
|
|
69
69
|
def serializable_fields
|
|
70
70
|
# exclude readonly fields from serialization
|
|
71
|
-
fields.reject { |k,
|
|
71
|
+
fields.reject { |k, _v| self.class.readonly_fields.include?(k) }
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def save(**options)
|
|
75
75
|
return false unless valid?
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
super(**options)
|
|
86
|
-
end
|
|
76
|
+
|
|
77
|
+
run_callbacks :save do
|
|
78
|
+
if new_record?
|
|
79
|
+
run_callbacks :create do
|
|
80
|
+
super(**options)
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
run_callbacks :update do
|
|
84
|
+
super(**options)
|
|
87
85
|
end
|
|
88
86
|
end
|
|
89
|
-
changes_applied
|
|
90
|
-
true
|
|
91
|
-
rescue => e
|
|
92
|
-
false
|
|
93
87
|
end
|
|
88
|
+
changes_applied
|
|
89
|
+
true
|
|
94
90
|
end
|
|
95
91
|
|
|
96
92
|
def save!(**options)
|
|
97
93
|
raise RecordInvalid, errors.full_messages.join(", ") unless valid?
|
|
98
|
-
|
|
94
|
+
|
|
95
|
+
save(**options)
|
|
99
96
|
end
|
|
100
97
|
|
|
101
98
|
def update(attributes)
|
|
@@ -110,6 +107,7 @@ module AirctiveRecord
|
|
|
110
107
|
|
|
111
108
|
def reload
|
|
112
109
|
return self if new_record?
|
|
110
|
+
|
|
113
111
|
reloaded = self.class.find(id)
|
|
114
112
|
@fields = reloaded.fields
|
|
115
113
|
@created_at = reloaded.created_at
|
|
@@ -117,20 +115,12 @@ module AirctiveRecord
|
|
|
117
115
|
self
|
|
118
116
|
end
|
|
119
117
|
|
|
120
|
-
def persisted?
|
|
121
|
-
!new_record?
|
|
122
|
-
end
|
|
118
|
+
def persisted? = !new_record?
|
|
123
119
|
|
|
124
|
-
def to_param
|
|
125
|
-
id
|
|
126
|
-
end
|
|
120
|
+
def to_param = id
|
|
127
121
|
|
|
128
|
-
def to_key
|
|
129
|
-
persisted? ? [id] : nil
|
|
130
|
-
end
|
|
122
|
+
def to_key = persisted? ? [id] : nil
|
|
131
123
|
|
|
132
|
-
def to_model
|
|
133
|
-
self
|
|
134
|
-
end
|
|
124
|
+
def to_model = self
|
|
135
125
|
end
|
|
136
126
|
end
|
|
@@ -18,11 +18,9 @@ module AirctiveRecord
|
|
|
18
18
|
# override to use field mappings when building airtable params
|
|
19
19
|
def to_airtable_params
|
|
20
20
|
params = {}
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# filter
|
|
23
|
-
if @where_clause.any?
|
|
24
|
-
params[:filter] = @where_clause.to_airtable_formula
|
|
25
|
-
end
|
|
23
|
+
params[:filter] = @where_clause.to_airtable_formula if @where_clause.any?
|
|
26
24
|
|
|
27
25
|
# sort - use field mappings
|
|
28
26
|
if @order_values.any?
|
|
@@ -34,7 +32,7 @@ module AirctiveRecord
|
|
|
34
32
|
|
|
35
33
|
# limit
|
|
36
34
|
params[:max_records] = @limit_value if @limit_value
|
|
37
|
-
|
|
35
|
+
|
|
38
36
|
# offset
|
|
39
37
|
params[:offset] = @offset_value if @offset_value
|
|
40
38
|
|
|
@@ -5,22 +5,18 @@ module AirctiveRecord
|
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
7
|
class_methods do
|
|
8
|
-
# returns a chainable Relation object
|
|
9
|
-
def all
|
|
10
|
-
relation_class.new(self)
|
|
11
|
-
end
|
|
8
|
+
# returns a chainable Relation object
|
|
9
|
+
def all = relation_class.new(self)
|
|
12
10
|
|
|
13
11
|
# scopes are defined on the model's specific Relation class
|
|
14
12
|
def scope(name, body)
|
|
15
|
-
unless body.respond_to?(:call)
|
|
16
|
-
raise ArgumentError, "The scope body needs to be callable."
|
|
17
|
-
end
|
|
13
|
+
raise ArgumentError, "The scope body needs to be callable." unless body.respond_to?(:call)
|
|
18
14
|
|
|
19
15
|
# define on the class
|
|
20
16
|
singleton_class.send(:define_method, name) do |*args|
|
|
21
17
|
all.public_send(name, *args)
|
|
22
18
|
end
|
|
23
|
-
|
|
19
|
+
|
|
24
20
|
# define on this model's Relation class
|
|
25
21
|
relation_class.send(:define_method, name) do |*args|
|
|
26
22
|
instance_exec(*args, &body)
|
|
@@ -28,41 +24,31 @@ module AirctiveRecord
|
|
|
28
24
|
end
|
|
29
25
|
|
|
30
26
|
# delegate finder methods to all
|
|
31
|
-
def where(conditions)
|
|
32
|
-
all.where(conditions)
|
|
33
|
-
end
|
|
27
|
+
def where(conditions) = all.where(conditions)
|
|
34
28
|
|
|
35
|
-
def order(*args)
|
|
36
|
-
all.order(*args)
|
|
37
|
-
end
|
|
29
|
+
def order(*args) = all.order(*args)
|
|
38
30
|
|
|
39
|
-
def limit(value)
|
|
40
|
-
all.limit(value)
|
|
41
|
-
end
|
|
31
|
+
def limit(value) = all.limit(value)
|
|
42
32
|
|
|
43
|
-
def offset(value)
|
|
44
|
-
all.offset(value)
|
|
45
|
-
end
|
|
33
|
+
def offset(value) = all.offset(value)
|
|
46
34
|
|
|
47
|
-
def find_by(conditions)
|
|
48
|
-
all.find_by(conditions)
|
|
49
|
-
end
|
|
35
|
+
def find_by(conditions) = all.find_by(conditions)
|
|
50
36
|
|
|
51
|
-
def find_by!(conditions)
|
|
52
|
-
all.find_by!(conditions)
|
|
53
|
-
end
|
|
37
|
+
def find_by!(conditions) = all.find_by!(conditions)
|
|
54
38
|
|
|
55
|
-
def
|
|
56
|
-
|
|
39
|
+
def find_or_create_by(conditions, &block)
|
|
40
|
+
find_by(conditions) || new(conditions).tap { |r| block&.call(r) }.tap(&:save)
|
|
57
41
|
end
|
|
58
42
|
|
|
59
|
-
def
|
|
60
|
-
|
|
43
|
+
def find_or_create_by!(conditions, &block)
|
|
44
|
+
find_by(conditions) || new(conditions).tap { |r| block&.call(r) }.tap(&:save!)
|
|
61
45
|
end
|
|
62
46
|
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
def first(limit = nil) = all.first(limit)
|
|
48
|
+
|
|
49
|
+
def last(limit = nil) = all.last(limit)
|
|
50
|
+
|
|
51
|
+
def count = all.count
|
|
66
52
|
end
|
|
67
53
|
end
|
|
68
54
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: airctiverecord
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 24c02
|
|
@@ -9,20 +9,6 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: norairrecord
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - "~>"
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.5'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - "~>"
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.5'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: activemodel
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,6 +51,20 @@ dependencies:
|
|
|
65
51
|
- - ">="
|
|
66
52
|
- !ruby/object:Gem::Version
|
|
67
53
|
version: 0.2.0
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: norairrecord
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.5'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.5'
|
|
68
68
|
description: Provides a familiar ActiveRecord-like interface for Airtable, built on
|
|
69
69
|
top of norairrecord with validations, callbacks, and associations.
|
|
70
70
|
email:
|