fmrest 0.6.0 → 0.10.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
  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