fmrest 0.6.0 → 0.10.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 185e6173a3f500ea6fc8ad40df8f52ba538fdd8e3545537e947f19bfd79a3124
4
- data.tar.gz: 89f5c6774366599e1baeef57662af77a3ceb5174abd58e6fd562c2aa29b85384
3
+ metadata.gz: 53c6e0d0260a3b825c031831136651a5f2252d8d0cebd8e2077919caf13d0fb6
4
+ data.tar.gz: 215b6d2a7735ce0a3109ae542f400aa0bee0d60f8808225fc4d77c52009c37d3
5
5
  SHA512:
6
- metadata.gz: b7aa56e455da60fe7b571e2ce4d70ae118ca7ade57356cbdade2b0679f66d10e9d689a4bc3af5ddfb06dfc6d7395af35b0518c23077dbfb28a9e395b845e0841
7
- data.tar.gz: c764ee1dc1962b3f6264ed6a4f26f0b88dd5c69cb62bca93dd269398c3e0d350ced4177b2842ce24136d7d0b84cb9a4809ce23ca1965b923fb91fc7b37ae39e1
6
+ metadata.gz: 4fdbb4dbc83ed0409e6680e8ffd028794ac01c07335ca43c95bd076e99359c17151b14634cb1a77bd41043ad104566ad5f820805de73d1f683aa957b02ad4fa5
7
+ data.tar.gz: 15966e4cc59a6bb9161f6edd6250beaa948d1c5c69ff911b3f2ca1eba2f782d79b8097c2038513e3ad4b84263b6b9e5f812cc7c37708a72f8a52d8db454b078f
@@ -1,5 +1,34 @@
1
1
  ## Changelog
2
2
 
3
+ ### 0.10.0
4
+
5
+ * Added `FmRest::StringDateAwareness` module to correct some issues when using
6
+ `FmRest::StringDate`
7
+ * Added basic timezones support
8
+ * Deprecated `class < FmRest::Spyke::Base(config_hash)` syntax in favor of
9
+ using `self.fmrest_config=`
10
+
11
+ ### 0.9.0
12
+
13
+ * Added `FmRest::Spyke::Base.set_globals`
14
+
15
+ ### 0.8.0
16
+
17
+ * Improved metadata when using `FmRest::Spyke::Model`. Metadata now uses
18
+ Struct/OpenStruct, so properties are accessible through `.property`, as well
19
+ as `[:property]`
20
+ * Added batch-finders `.find_in_batches` and `.find_each` for
21
+ * `FmRest::Spyke::Base`
22
+
23
+ ### 0.7.1
24
+
25
+ * Made sure `Model.find_one` and `Model.find_some` work without needing to call
26
+ `Model.all` in between
27
+
28
+ ### 0.7.0
29
+
30
+ * Added date coercion feature
31
+
3
32
  ### 0.6.0
4
33
 
5
34
  * Implemented session logout
data/README.md CHANGED
@@ -105,6 +105,23 @@ You can also pass a `:log` option for basic request logging, see the section on
105
105
  `:username` is also aliased as `:account_name` to provide cross-compatibility
106
106
  with the ginjo-rfm gem.
107
107
 
108
+ ### Full list of available options
109
+
110
+ Option | Description | Format | Default
111
+ --------------------|--------------------------------------------|-----------------------------|--------
112
+ `:host` | Hostname with optional port, e.g. `"example.com:9000"` | String | None
113
+ `:database` | | String | None
114
+ `:username` | | String | None
115
+ `:password` | | String | None
116
+ `:ssl` | SSL options to be forwarded to Faraday | Faraday SSL options | None
117
+ `:proxy` | Proxy options to be forwarded to Faraday | Faraday proxy options | None
118
+ `:log` | Log JSON responses to STDOUT | Boolean | `false`
119
+ `:coerce_dates` | See section on [date fields](#date-fields) | Boolean \| `:hybrid` \| `:full` | `false`
120
+ `:date_format` | Date parsing format | String (FM date format) | `"MM/dd/yyyy"`
121
+ `:timestamp_format` | Timestmap parsing format | String (FM date format) | `"MM/dd/yyyy HH:mm:ss"`
122
+ `:time_format` | Time parsing format | String (FM date format) | `"HH:mm:ss"`
123
+ `:timezone` | The timezone for the FM server | `:local` \| `:utc` \: `nil` | `nil`
124
+
108
125
  ### Default connection settings
109
126
 
110
127
  If you're only connecting to a single FM database you can configure it globally
@@ -122,6 +139,7 @@ FmRest.default_connection_settings = {
122
139
  This configuration will be used by default by `FmRest::V1.build_connection` as
123
140
  well as your models whenever you don't pass a configuration hash explicitly.
124
141
 
142
+
125
143
  ## Session token store
126
144
 
127
145
  By default fmrest-ruby will use a memory-based store for the session tokens.
@@ -178,7 +196,7 @@ FmRest.token_store = FmRest::TokenStore::Redis.new(redis: Redis.new, prefix: "my
178
196
  FmRest.token_store = FmRest::TokenStore::Redis.new(prefix: "my-fancy-prefix:", host: "10.0.1.1", port: 6380, db: 15)
179
197
  ```
180
198
 
181
- **NOTE:** redis-rb is not included as a gem dependency of fmrest-ruby, so you'll
199
+ NOTE: redis-rb is not included as a gem dependency of fmrest-ruby, so you'll
182
200
  have to add it to your Gemfile.
183
201
 
184
202
  ### Moneta
@@ -215,9 +233,79 @@ FmRest.token_store = FmRest::TokenStore::Moneta.new(
215
233
  )
216
234
  ```
217
235
 
218
- **NOTE:** the moneta gem is not included as a dependency of fmrest-ruby, so
236
+ NOTE: the moneta gem is not included as a dependency of fmrest-ruby, so
219
237
  you'll have to add it to your Gemfile.
220
238
 
239
+
240
+ ## Date fields
241
+
242
+ Since the Data API uses JSON (wich doesn't provide a native date/time object),
243
+ dates and timestamps are received in string format. By default fmrest-ruby
244
+ leaves those string fields untouched, but it provides an opt-in feature to try
245
+ to automatically "coerce" them into Ruby date objects.
246
+
247
+ The connection option `:coerce_dates` controls this feature. Possible values
248
+ are:
249
+
250
+ * `:full` - whenever a string matches the given date/timestamp/time format,
251
+ convert them to `Date` or `DateTime` objects as appropriate
252
+ * `:hybrid` or `true` - similar as above, but instead of converting to regular
253
+ `Date`/`DateTime` it converts strings to `FmRest::StringDate` and
254
+ `FmRest::StringDateTime`, "hybrid" classes provided by fmrest-ruby that
255
+ retain the functionality of `String` while also providing most the
256
+ functionality of `Date`/`DateTime` (more on this below)
257
+ * `false` - disable date coercion entirely (default), leave original string
258
+ values untouched
259
+
260
+ Enabling date coercion works with both basic fmrest-ruby connections and Spyke
261
+ models (ORM).
262
+
263
+ The connection options `:date_format`, `:timestamp_format` and `:time_format`
264
+ control how to match and parse dates. You only need to provide these if you use
265
+ a date/time localization different from American format (the default).
266
+
267
+ Future versions of fmrest-ruby will provide better (and less heuristic) ways of
268
+ specifying and/or detecting date fields (e.g. by requesting layout metadata or
269
+ a DSL in model classes).
270
+
271
+ ### Hybrid string/date objects
272
+
273
+ `FmRest::StringDate` and `FmRest::StringDateTime` are special classes that
274
+ inherit from `String`, but internally parse and store a `Date` or `DateTime`,
275
+ and delegate any methods not provided by `String` to those objects. In other
276
+ words, they quack like a duck *and* bark like a dog.
277
+
278
+ You can use these when you want fmrest-ruby to provide you with date objects,
279
+ but you don't want to worry about date coercion of false positives (i.e. a
280
+ string field that gets converted to `Date` because it just so matched the given
281
+ date format).
282
+
283
+ Be warned however that these classes come with a fair share of known gotchas
284
+ (see GitHub wiki for more info). Some of those gothas can be removed by calling
285
+
286
+ ```ruby
287
+ FmRest::StringDateAwareness.enable
288
+ ```
289
+
290
+ Which will extend the core `Date` and `DateTime` classes to be aware of
291
+ `FmRest::StringDate`, especially when calling `Date.===`, `Date.parse` or
292
+ `Date._parse`.
293
+
294
+ If you're working with ActiveRecord models this will also make them accept
295
+ `FmRest::StringDate` values for date fields.
296
+
297
+ ### Timezones
298
+
299
+ fmrest-ruby has basic timezone support. You can set the `:timezone` option in
300
+ your connection settings to one of the following values:
301
+
302
+ * `:local` - dates will be converted to your system local time offset (as
303
+ defined by `ENV["TZ"]`), or the timezone set by `Time.zone` if you're using
304
+ ActiveSupport
305
+ * `:utc` - dates will be converted to UTC offset
306
+ * `nil` - (default) ignore timezones altogether
307
+
308
+
221
309
  ## Spyke support (ActiveRecord-like ORM)
222
310
 
223
311
  [Spyke](https://github.com/balvig/spyke) is an ActiveRecord-like gem for
@@ -257,17 +345,6 @@ class Honeybee < FmRest::Spyke::Base
257
345
  end
258
346
  ```
259
347
 
260
- In this case you can pass the [`fmrest_config`](#modelfmrest_config) hash as an
261
- argument to `Base()`:
262
-
263
- ```ruby
264
- class Honeybee < FmRest::Spyke::Base(host: "...", database: "...", username: "...", password: "...")
265
- end
266
-
267
- Honeybee.fmrest_config
268
- # => { host: "...", database: "...", username: "...", password: "..." }
269
- ```
270
-
271
348
  All of Spyke's basic ORM operations work:
272
349
 
273
350
  ```ruby
@@ -490,7 +567,7 @@ Honeybee.limit(10)
490
567
  ```
491
568
 
492
569
  NOTE: You can also set a default limit value for a model class, see
493
- [Other notes on querying](#other-notes-on-querying).
570
+ [other notes on querying](#other-notes-on-querying).
494
571
 
495
572
  You can also use `.limit` to set limits on portals:
496
573
 
@@ -662,15 +739,15 @@ the scope object:
662
739
  Honeybee.limit(10).sort(:name).find_some # => [<Honeybee...>, ...]
663
740
  ```
664
741
 
665
- If you want just a single result you can use `.find_one` instead (this will
742
+ If you want just a single result you can use `.first` instead (this will
666
743
  force `.limit(1)`):
667
744
 
668
745
  ```ruby
669
- Honeybee.query(name: "Hutch").find_one # => <Honeybee...>
746
+ Honeybee.query(name: "Hutch").first # => <Honeybee...>
670
747
  ```
671
748
 
672
749
  If you know the id of the record you should use `.find(id)` instead of
673
- `.query(id: id).find_one` (so that the sent request is
750
+ `.query(id: id).first` (so that the sent request is
674
751
  `GET ../:layout/records/:id` instead of `POST ../:layout/_find`).
675
752
 
676
753
  ```ruby
@@ -681,6 +758,52 @@ Note also that if you use `.find(id)` your `.query()` parameters (as well as
681
758
  limit, offset and sort parameters) will be discarded as they're not supported
682
759
  by the single record endpoint.
683
760
 
761
+
762
+ ### Finding records in batches
763
+
764
+ Sometimes you want to iterate over a very large number of records to do some
765
+ processing, but requesting them all at once would result in one huge request to
766
+ the Data API, and loading too many records in memory all at once.
767
+
768
+ To mitigate this problem you can use `.find_in_batches` and `.find_each`. If
769
+ you've used ActiveRecord you're probably familiar with how they operate:
770
+
771
+ ```ruby
772
+ # Find records in batches of 100 each
773
+ Honeybee.query(hive: "Queensville").find_in_batches(batch_size: 100) do |batch|
774
+ dispatch_bees(batch)
775
+ end
776
+
777
+ # Iterate over all records using batches
778
+ Honeybee.query(hive: "Queensville").find_each(batch_size: 100) do |bee|
779
+ bee.dispatch
780
+ end
781
+ ```
782
+
783
+ `.find_in_batches` yields collections of records (batches), while `.find_each`
784
+ yields individual records, but using batches behind the scenes.
785
+
786
+ Both methods accept a block-less form in which case they return an
787
+ `Enumerator`:
788
+
789
+ ```ruby
790
+ batch_enum = Honeybee.find_in_batches
791
+
792
+ batch = batch_enum.next # => Spyke::Collection
793
+
794
+ batch_enum.each do |batch|
795
+ process_batch(batch)
796
+ end
797
+
798
+ record_enum = Honeybee.find_each
799
+
800
+ record_enum.next # => Honeybee
801
+ ```
802
+
803
+ NOTE: By its nature, batch processing is subject to race conditions if other
804
+ processes are modifying the database.
805
+
806
+
684
807
  ### Container fields
685
808
 
686
809
  You can define container fields on your model class with `container`:
@@ -718,6 +841,7 @@ bee.photo.upload(filename_or_io) # Upload a file to the container
718
841
  * `:content_type` - The MIME content type to use (defaults to
719
842
  `application/octet-stream`)
720
843
 
844
+
721
845
  ### Script execution
722
846
 
723
847
  The Data API allows running scripts as part of many types of requests.
@@ -805,7 +929,7 @@ separately, under their matching key.
805
929
  ```ruby
806
930
  bee.save(script: { presort: "My Presort Script", after: "My Script" })
807
931
 
808
- Honeybee.last_request_metadata[:script]
932
+ Honeybee.last_request_metadata.script
809
933
  # => { after: { result: "oh hi", error: "0" }, presort: { result: "lo", error: "0" } }
810
934
  ```
811
935
 
@@ -819,7 +943,7 @@ is performed on that scope.
819
943
 
820
944
  ```ruby
821
945
  # Find one Honeybee record executing a presort and after script
822
- Honeybee.script(presort: ["My Presort Script", "parameter"], after: "My Script").find_one
946
+ Honeybee.script(presort: ["My Presort Script", "parameter"], after: "My Script").first
823
947
  ```
824
948
 
825
949
  The model class' `.last_request_metadata` will be set in case you need to get the result.
@@ -830,6 +954,33 @@ same metadata hash with script execution results. Note that this does not apply
830
954
  to retrieving single records, in that case you'll have to use
831
955
  `.last_request_metadata`.
832
956
 
957
+
958
+ ### Setting global field values
959
+
960
+ You can call `.set_globals` on any `FmRest::Spyke::Base` model to set glabal
961
+ field values on the database that model is configured for.
962
+
963
+ You can pass it either a hash of fully qualified field names
964
+ (table_name::field_name), or 1-level-deep nested hashes, with the outer being a
965
+ table name and the inner keys being the field names:
966
+
967
+ ```ruby
968
+ Honeybee.set_globals(
969
+ "beeTable::myVar" => "value",
970
+ "beeTable::myOtherVar" => "also a value"
971
+ )
972
+
973
+ # Equivalent to the above example
974
+ Honeybee.set_globals(beeTable: { myVar: "value", myOtherVar: "also a value" })
975
+
976
+ # Combined
977
+ Honeybee.set_globals(
978
+ "beeTable::myVar" => "value",
979
+ beeTable: { myOtherVar: "also a value" }
980
+ )
981
+ ```
982
+
983
+
833
984
  ## Logging
834
985
 
835
986
  If using fmrest-ruby + Spyke in a Rails app pretty log output will be set up
@@ -883,7 +1034,7 @@ FM Data API reference: https://fmhelp.filemaker.com/docs/18/en/dataapi/
883
1034
  | Log in using OAuth | No | No |
884
1035
  | Log in to an external data source | No | No |
885
1036
  | Log in using a FileMaker ID account | No | No |
886
- | Log out | Yes | Yes
1037
+ | Log out | Yes | Yes |
887
1038
  | Get product information | Manual* | No |
888
1039
  | Get database names | Manual* | No |
889
1040
  | Get script names | Manual* | No |
@@ -898,25 +1049,12 @@ FM Data API reference: https://fmhelp.filemaker.com/docs/18/en/dataapi/
898
1049
  | Get container data | Manual* | Yes |
899
1050
  | Upload container data | Manual* | Yes |
900
1051
  | Perform a find request | Manual* | Yes |
901
- | Set global field values | Manual* | No |
1052
+ | Set global field values | Manual* | Yes
902
1053
  | Run a script | Manual* | Yes |
903
1054
  | Run a script with another request | Manual* | Yes |
904
1055
 
905
1056
  \* You can manually supply the URL and JSON to a `FmRest` connection.
906
1057
 
907
- ## TODO
908
-
909
- - [ ] Support for FM18 features
910
- - [ ] Better/simpler-to-use core Ruby API
911
- - [ ] Better API documentation and README
912
- - [ ] Oauth support
913
- - [x] Support for portal limit and offset
914
- - [x] More options for token storage
915
- - [x] Support for container fields
916
- - [x] Optional logging
917
- - [x] FmRest::Spyke::Base class for single inheritance (as alternative for mixin)
918
- - [x] Specs
919
- - [x] Support for portal data
920
1058
 
921
1059
  ## Gem development
922
1060
 
@@ -931,6 +1069,7 @@ release a new version, update the version number in `version.rb`, and then run
931
1069
  git commits and tags, and push the `.gem` file to
932
1070
  [rubygems.org](https://rubygems.org).
933
1071
 
1072
+
934
1073
  ## Contributing
935
1074
 
936
1075
  Bug reports and pull requests are welcome. This project is intended to be a
@@ -938,20 +1077,16 @@ safe, welcoming space for collaboration, and contributors are expected to
938
1077
  adhere to the [Contributor Covenant](http://contributor-covenant.org) code of
939
1078
  conduct.
940
1079
 
1080
+
941
1081
  ## License
942
1082
 
943
1083
  The gem is available as open source under the terms of the
944
1084
  [MIT License](https://opensource.org/licenses/MIT).
945
1085
  See [LICENSE.txt](LICENSE.txt).
946
1086
 
1087
+
947
1088
  ## Disclaimer
948
1089
 
949
1090
  This project is not sponsored by or otherwise affiliated with FileMaker, Inc,
950
1091
  an Apple subsidiary. FileMaker is a trademark of FileMaker, Inc., registered in
951
1092
  the U.S. and other countries.
952
-
953
- ## Code of Conduct
954
-
955
- Everyone interacting in the fmrest-ruby project’s codebases, issue trackers,
956
- chat rooms and mailing lists is expected to follow the [code of
957
- conduct](CODE_OF_CONDUCT.md).
@@ -8,7 +8,7 @@ rescue LoadError => e
8
8
  end
9
9
 
10
10
  require "fmrest"
11
- require "fmrest/spyke/json_parser"
11
+ require "fmrest/spyke/spyke_formatter"
12
12
  require "fmrest/spyke/model"
13
13
  require "fmrest/spyke/base"
14
14
 
@@ -8,6 +8,8 @@ module FmRest
8
8
 
9
9
  class << self
10
10
  def Base(config = nil)
11
+ warn "[DEPRECATION] Inheriting from `FmRest::Spyke::Base(config)` is deprecated and will be removed, inherit from `FmRest::Spyke::Base` (without arguments) and use `fmrest_config=` instead"
12
+
11
13
  if config
12
14
  return Class.new(::FmRest::Spyke::Base) do
13
15
  self.fmrest_config = config
@@ -7,6 +7,7 @@ require "fmrest/spyke/model/serialization"
7
7
  require "fmrest/spyke/model/associations"
8
8
  require "fmrest/spyke/model/orm"
9
9
  require "fmrest/spyke/model/container_fields"
10
+ require "fmrest/spyke/model/global_fields"
10
11
  require "fmrest/spyke/model/http"
11
12
  require "fmrest/spyke/model/auth"
12
13
 
@@ -22,6 +23,7 @@ module FmRest
22
23
  include Associations
23
24
  include Orm
24
25
  include ContainerFields
26
+ include GlobalFields
25
27
  include Http
26
28
  include Auth
27
29
 
@@ -10,7 +10,7 @@ module FmRest
10
10
 
11
11
  included do
12
12
  # Keep track of portal options by their FM keys as we could need it
13
- # to parse the portalData JSON in JsonParser
13
+ # to parse the portalData JSON in SpykeFormatter
14
14
  class_attribute :portal_options, instance_accessor: false, instance_predicate: false
15
15
 
16
16
  # class_attribute supports a :default option since ActiveSupport 5.2,
@@ -37,7 +37,7 @@ module FmRest
37
37
  def has_portal(name, options = {})
38
38
  create_association(name, Portal, options)
39
39
 
40
- # Store options for JsonParser to use if needed
40
+ # Store options for SpykeFormatter to use if needed
41
41
  portal_key = options[:portal_key] || name
42
42
  self.portal_options = portal_options.merge(portal_key.to_s => options.dup.merge(name: name.to_s)).freeze
43
43
 
@@ -4,10 +4,19 @@ module FmRest
4
4
  module Spyke
5
5
  module Model
6
6
  module Connection
7
- extend ::ActiveSupport::Concern
7
+ extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- class_attribute :fmrest_config, instance_accessor: false, instance_predicate: false
10
+ class_attribute :fmrest_config, instance_writer: false, instance_predicate: false
11
+
12
+ # Overrides the fmrest_config reader created by class_attribute so we
13
+ # can default set the default at call time.
14
+ #
15
+ # This method gets overwriten in subclasses if self.fmrest_config= is
16
+ # called.
17
+ define_singleton_method(:fmrest_config) do
18
+ FmRest.default_connection_settings
19
+ end
11
20
 
12
21
  class_attribute :faraday_block, instance_accessor: false, instance_predicate: false
13
22
  class << self; private :faraday_block, :faraday_block=; end
@@ -38,15 +47,25 @@ module FmRest
38
47
  private
39
48
 
40
49
  def fmrest_connection
41
- @fmrest_connection ||= FmRest::V1.build_connection(fmrest_config || FmRest.default_connection_settings) do |conn|
42
- faraday_block.call(conn) if faraday_block
43
-
44
- # Pass the class to JsonParser's initializer so it can have
45
- # access to extra context defined in the model, e.g. a portal
46
- # where name of the portal and the attributes prefix don't match
47
- # and need to be specified as options to `portal`
48
- conn.use FmRest::Spyke::JsonParser, self
49
- end
50
+ @fmrest_connection ||=
51
+ begin
52
+ config = fmrest_config
53
+
54
+ FmRest::V1.build_connection(config) do |conn|
55
+ faraday_block.call(conn) if faraday_block
56
+
57
+ # Pass the class to SpykeFormatter's initializer so it can have
58
+ # access to extra context defined in the model, e.g. a portal
59
+ # where name of the portal and the attributes prefix don't match
60
+ # and need to be specified as options to `portal`
61
+ conn.use FmRest::Spyke::SpykeFormatter, self
62
+
63
+ conn.use FmRest::V1::TypeCoercer, config
64
+
65
+ # FmRest::Spyke::JsonParse expects symbol keys
66
+ conn.response :json, parser_options: { symbolize_names: true }
67
+ end
68
+ end
50
69
  end
51
70
  end
52
71
  end