stretchy-model 0.3.0 → 0.5.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: a85f7bffcc11c54366d4cae7fec47b91341425e06b7f1c8a6269e0b23d4e649b
4
- data.tar.gz: cd0b9314fa734aa7af7179f12ed00626a17bc869186f82088158bfca87c8d915
3
+ metadata.gz: dd18b424c18bda352233d72af113e9bbc944277e03a02cfb950780f0fab9a405
4
+ data.tar.gz: 6eb86522e7bc91012cc4c743d0a9fccc54c7e75b14ff4d6f4fca5bdb9fa31626
5
5
  SHA512:
6
- metadata.gz: e193c837695100177eac9888841b14243596dfc9694515696ab1a9934174287c821cf52b2ca165e3f3eda04de9ba16c7c7330cb187641d71229d322c06674977
7
- data.tar.gz: 21ace7b64f85d505a05b61b89b5b85d9d2af34941b57672da45f70e6c8736e8b9451c59891fef7d0795f8b54076cffd88bfc141fcd4da1c1f8ffaddcf8276697
6
+ metadata.gz: 0fd2a0fb25439d79c799f43f43bb01df7c4dfe0ce9eb577d445b6d415eb9c38ecdc0f45c73d8255d2e079ff502e943f6491b874e3bb9c41fc9c853efed84d252
7
+ data.tar.gz: 8ea4cfa584ec825ac30aa4aa5dea88b04c709cbf7cc75089e28cd1e850318f150f1f68ead89bc571535739aa8ff8db777102d5dd05173087d4af499bac8d8bbb
data/.rspec CHANGED
@@ -1 +1 @@
1
- --require spec_helper
1
+ --require spec_helper
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  stretchy-model
2
2
  ===
3
-
4
3
  <p>
5
4
  <a href="https://stretchy.io/" target="_blank"><img src="./stretchy.logo.png" alt="Gum Image" width="450" /></a>
6
5
  <br><br>
@@ -9,114 +8,42 @@ stretchy-model
9
8
 
10
9
  </p>
11
10
 
12
- Stretchy provides Elasticsearch models in a Rails environment with an integrated ActiveRecord-like interface and features.
13
11
 
14
12
  ## Features
15
13
  Stretchy simplifies the process of querying, aggregating, and managing Elasticsearch-backed models, allowing Rails developers to work with search indices as comfortably as they would with traditional Rails models.
16
14
 
17
- ## Attributes
18
-
19
- ```ruby
20
- class Post < Stretchy::Record
21
-
22
- attribute :title, :string
23
- attribute :body, :string
24
- attribute :flagged, :boolean, default: false
25
- attribute :author, :hash
26
- attribute :tags, :array, default: []
27
-
28
- end
29
- ```
30
- >[!NOTE]
31
- >`created_at`, `:updated_at` and `:id` are automatically added to all `Stretchy::Records`
32
-
33
-
34
- ## Query
35
- ```ruby
36
- Post.where('author.name': "Jadzia", flagged: true).first
37
- #=> <Post id: aW02w3092, title: "Fun Cats", body: "...", flagged: true,
38
- # author: {name: "Jadzia", age: 20}, tags: ["cat", "amusing"]>
39
- ```
40
-
41
- ## Aggregations
42
- ```ruby
43
-
44
- result = Post.filter(:range, 'author.age': {gte: 18})
45
- .aggregation(:post_frequency, date_histogram: {
46
- field: :created_at,
47
- calender_interval: :month
48
- })
15
+ * Model fully back by Elasticsearch/Opensearch
16
+ * Chain queries, scopes and aggregations
17
+ * Reduce Elasticsearch query complexity
18
+ * Support for time-based indices and aliases
19
+ * Associations to both ActiveRecord models and Stretchy::Record
20
+ * Bulk Operations made easy
21
+ * Validations, custom attributes, and more...
49
22
 
50
- result.aggregations.post_frequency
51
- #=> {buckets: [{key_as_string: "2024-01-01", doc_count: 20}, ...]}
52
- ```
53
-
54
- ## Scopes
55
-
56
- ```ruby
57
- class Post < Stretchy::Record
58
- # ...attributes
59
-
60
- # Scopes
61
- scope :flagged, -> { where(flagged: true) }
62
- scope :top_links, lambda do |size=10, url=".com"|
63
- aggregation(:links,
64
- terms: {
65
- field: :links,
66
- size: size,
67
- include: ".*#{url}.*"
68
- })
69
- end
70
- end
71
-
72
- # Returns flagged posts and includes the top 10 'youtube.com'
73
- # links in results.aggregations.links
74
- result = Post.flagged.top_links(10, "youtube.com")
75
-
76
- ```
23
+ Follow the guides to learn more about:
77
24
 
78
- ## Bulk Operations
79
-
80
-
81
- ```ruby
82
- Model.bulk(records_as_bulk_operations)
83
- ```
84
-
85
- #### Bulk helper
86
- Generates structure for the bulk operation
87
- ```ruby
88
- record.to_bulk # default to_bulk(:index)
89
- record.to_bulk(:delete)
90
- record.to_bulk(:update)
91
- ```
25
+ * [Models](https://theablefew.github.io/stretchy/#/guides/models?id=models)
26
+ * [Querying](https://theablefew.github.io/stretchy/#/guides/querying?id=querying)
27
+ * [Aggregations](https://theablefew.github.io/stretchy/#/guides/aggregations?id=aggregations)
28
+ * [Scopes](https://theablefew.github.io/stretchy/#/guides/scopes?id=scopes)
92
29
 
93
- #### In batches
94
- Run bulk operations in batches specified by `size`
95
- ```ruby
96
- Model.bulk_in_batches(records, size: 100) do |batch|
97
- batch.map! { |record| Model.new(record).to_bulk }
98
- end
99
- ```
100
30
 
31
+ [Read the Documentation](https://theablefew.github.io/stretchy/#/) or walk through of a simple [Data Analysis](https://theablefew.github.io/stretchy/#/examples/data_analysis?id=data-analysis) example.
101
32
 
102
- ## Instrumentation
103
- ```ruby
104
- Blanket.first
105
- ```
106
33
 
107
- ```sh
108
- Blanket (6.322ms) curl -X GET 'http://localhost:9200/blankets/_search?size=1' -d '{"sort":{"date":"desc"}}'
109
- ```
110
34
 
111
35
  ## Installation
112
36
 
113
37
  Install the gem and add to the application's Gemfile by executing:
114
38
 
115
- $ bundle add stretchy-model
39
+ ```sh
40
+ bundle add stretchy-model
41
+ ```
116
42
 
117
43
  If bundler is not being used to manage dependencies, install the gem by executing:
118
-
119
- $ gem install stretchy-model
44
+ ```sh
45
+ gem install stretchy-model
46
+ ```
120
47
 
121
48
  <details>
122
49
  <summary>Rails Configuration</summary>
@@ -131,6 +58,12 @@ rails credentials:edit
131
58
  ```yaml
132
59
  elasticsearch:
133
60
  url: localhost:9200
61
+
62
+ # or opensearch
63
+ # opensearch:
64
+ # host: https://localhost:9200
65
+ # user: admin
66
+ # password: admin
134
67
  ```
135
68
 
136
69
  #### Create an initializer
@@ -149,11 +82,24 @@ end
149
82
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
150
83
 
151
84
  >[!TIP]
152
- >This library is built on top of the excellent [elasticsearch-persistence](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-persistence) gem.
153
- >
154
85
  > Full documentation on [Elasticsearch Query DSL and Aggregation options](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-persistence)
155
86
 
156
87
  ## Testing
88
+ <details>
89
+ <summary>Act</summary>
90
+
91
+ Run github action workflow locally
92
+
93
+ ```sh
94
+ brew install act --HEAD
95
+ ```
96
+
97
+ ```sh
98
+ act -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:runner-latest
99
+ ```
100
+
101
+ </details>
102
+
157
103
  <details>
158
104
  <summary>Elasticsearch</summary>
159
105
 
data/Rakefile CHANGED
@@ -2,3 +2,95 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  task default: %i[]
5
+
6
+ require 'octokit'
7
+ require 'versionomy'
8
+ require 'rainbow'
9
+
10
+ def determine_current_version
11
+ # Load current version
12
+ load 'lib/stretchy/version.rb'
13
+ current_version = Versionomy.parse(Stretchy::VERSION)
14
+ end
15
+
16
+ def determine_new_version(version)
17
+ # Load current version
18
+ current_version = determine_current_version
19
+
20
+ # Determine new version
21
+ case version.to_sym
22
+ when :major
23
+ current_version.bump(:major)
24
+ when :minor
25
+ current_version.bump(:minor)
26
+ when :patch
27
+ current_version.bump(:tiny)
28
+ else
29
+ version =~ /\Av?\d+\.\d+\.\d+\z/ ? Versionomy.parse(version).to_s.gsub(/v/,'') : current_version
30
+ end
31
+ end
32
+
33
+ def create_release_branch(new_version, base_branch)
34
+ system("git stash save 'Changes before creating release branch'")
35
+ system("git fetch origin #{base_branch}")
36
+ branch_name = "release/v#{new_version}"
37
+ system("git checkout -b #{branch_name} #{base_branch}")
38
+ branch_name
39
+ end
40
+
41
+ def update_version_file(new_version)
42
+ # Update lib/stretchy/version.rb
43
+ File.open('lib/stretchy/version.rb', 'w') do |file|
44
+ file.puts "module Stretchy\n VERSION = '#{new_version}'\nend"
45
+ end
46
+ end
47
+
48
+ def commit_and_push_changes(new_version, branch_name)
49
+ system("git add lib/stretchy/version.rb")
50
+ system("git commit -m 'Bump version to v#{new_version}'")
51
+ system("git tag v#{new_version}")
52
+ system("git push origin #{branch_name} --tags -f")
53
+ end
54
+
55
+ def create_pull_request(new_version, base_branch, branch_name)
56
+ # Create a pull request
57
+ client = Octokit::Client.new(access_token: ENV['GH_TOKEN'])
58
+ client.create_pull_request('theablefew/stretchy', base_branch, branch_name, "Release v#{new_version}")
59
+ end
60
+
61
+ namespace :publish do
62
+ desc "Create a release"
63
+ task :release, [:version, :base_branch] do |t, args|
64
+ args.with_defaults(version: :patch, base_branch: 'main')
65
+ version = args[:version]
66
+ base_branch = args[:base_branch]
67
+
68
+ old_version = determine_current_version
69
+ new_version = determine_new_version(version)
70
+ puts Rainbow("Bumping version from #{old_version} to #{new_version}").green
71
+ branch_name = create_release_branch(new_version, base_branch)
72
+ begin
73
+ update_version_file(new_version)
74
+ commit_and_push_changes(new_version, branch_name)
75
+ create_pull_request(new_version, base_branch, branch_name)
76
+ rescue => e
77
+ puts "Error: #{e.message}"
78
+ puts "Rolling back changes"
79
+ system("git tag -d v#{new_version}")
80
+ system("git checkout #{base_branch}")
81
+ system("git branch -D #{branch_name}")
82
+ end
83
+ end
84
+
85
+ task :major do
86
+ Rake::Task['publish:release'].invoke('major')
87
+ end
88
+
89
+ task :minor do
90
+ Rake::Task['publish:release'].invoke('minor')
91
+ end
92
+
93
+ task :patch do
94
+ Rake::Task['publish:release'].invoke('patch')
95
+ end
96
+ end
@@ -3,7 +3,11 @@ module Stretchy
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  def save!
6
+ if valid?
6
7
  self.save
8
+ else
9
+ raise "Record is invalid"
10
+ end
7
11
  end
8
12
 
9
13
  # Required for Elasticsearch < 7
@@ -34,7 +38,7 @@ module Stretchy
34
38
  end
35
39
 
36
40
  def association_reflection(association)
37
- ElasticRelation.new @@_associations[association], (dirty[association.to_sym] || [])
41
+ Stretchy::Relation.new @@_associations[association], (dirty[association.to_sym] || [])
38
42
  end
39
43
 
40
44
  def _destroy=(bool)
@@ -48,6 +52,7 @@ module Stretchy
48
52
  def save_associations
49
53
  @_after_save_objects.each_pair do |association, collection|
50
54
  collection.each do |instance|
55
+ # TODO: bulk update
51
56
  instance.send("#{@@_association_options[association.to_sym][:foreign_key]}=", self.id)
52
57
  instance.save
53
58
  end
@@ -59,59 +64,194 @@ module Stretchy
59
64
  @@_associations ||= {}
60
65
  @@_association_options ||= {}
61
66
 
67
+ # The belongs_to method is used to set up a one-to-one connection with another model.
68
+ # This indicates that this model has exactly one instance of another model.
69
+ # For example, if your application includes authors and books, and each book can be assigned exactly one author,
70
+ # you'd declare the book model to belong to the author model.
71
+ #
72
+ # association:: [Symbol] the name of the association
73
+ # options:: [Hash] a hash to set up options for the association
74
+ # :foreign_key - the foreign key used for the association. Defaults to "#{association}_id"
75
+ # :primary_key - the primary key used for the association. Defaults to "id"
76
+ # :class_name - the name of the associated object's class. Defaults to the name of the association
77
+ #
78
+ # Example:
79
+ # belongs_to :author
80
+ #
81
+ # This creates a book.author method that returns the author of the book.
82
+ # It also creates an author= method that allows you to assign the author of the book.
83
+ #
62
84
  def belongs_to(association, options = {})
63
85
  @@_association_options[association] = {
64
86
  foreign_key: "#{association}_id",
65
87
  primary_key: "id",
66
88
  class_name: association
67
- }.reverse_merge(options)
89
+ }.merge(options)
68
90
 
69
91
  klass = @@_association_options[association][:class_name].to_s.singularize.classify.constantize
70
92
  @@_associations[association] = klass
71
93
 
72
94
  define_method(association.to_sym) do
73
- klass.where(_id: self.send(@@_association_options[association][:foreign_key].to_sym)).first
95
+ instance_variable_get("@#{association}") ||
96
+ klass.where(_id: self.send(@@_association_options[association][:foreign_key].to_sym)).first
74
97
  end
75
98
 
76
99
  define_method("#{association}=".to_sym) do |val|
77
100
  options = @@_association_options[association]
78
- instance_variable_set("@#{options[:foreign_key]}", val.send(options[:primary_key]))
101
+ self.send("#{options[:foreign_key]}=", val.send(options[:primary_key]))
102
+ instance_variable_set("@#{association}", val)
103
+ end
104
+
105
+ define_method("build_#{association}") do |*args|
106
+ associated_object = klass.new(*args)
107
+ instance_variable_set("@#{association}", associated_object)
108
+ associated_object
109
+ end
110
+
111
+ before_save do
112
+ associated_object = instance_variable_get("@#{association}")
113
+ if associated_object && associated_object.new_record?
114
+ if associated_object.save!
115
+ self.send("#{@@_association_options[association][:foreign_key]}=", associated_object.id)
116
+ end
117
+ end
79
118
  end
80
119
  end
81
120
 
82
- def has_one(association, class_name: nil, foreign_key: nil, dependent: :destroy)
83
121
 
84
- klass = association.to_s.singularize.classify.constantize unless class_name.present?
85
- foreign_key = "#{self.name.downcase}_id" unless foreign_key.present?
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+ # The has_one method is used to set up a one-to-one connection with another model.
131
+ # This indicates that this model contains the foreign key.
132
+ #
133
+ # association:: [Symbol] The name of the association.
134
+ # options:: [Hash] A hash to set up options for the association.
135
+ # :class_name - The name of the associated model. If not provided, it's derived from +association+.
136
+ # :foreign_key - The name of the foreign key on the associated model. If not provided, it's derived from the name of this model.
137
+ # :dependent - If set to +:destroy+, the associated object will be destroyed when this object is destroyed. This is the default behavior.
138
+ # :primary_key - The name of the primary key on the associated model. If not provided, it's assumed to be +id+.
139
+ #
140
+ #
141
+ # Example:
142
+ # has_one :profile
143
+ #
144
+ # This creates a user.profile method that returns the profile of the user.
145
+ # It also creates a profile= method that allows you to assign the profile of the user.
146
+ #
147
+ def has_one(association, options = {})
148
+
149
+ @@_association_options[association] = {
150
+ foreign_key: "#{self.name.underscore}_id",
151
+ primary_key: "id",
152
+ class_name: association
153
+ }.merge(options)
154
+
155
+ klass = @@_association_options[association][:class_name].to_s.singularize.classify.constantize
86
156
  @@_associations[association] = klass
87
157
 
158
+ foreign_key = @@_association_options[association][:foreign_key]
159
+
88
160
  define_method(association.to_sym) do
89
- klass.where("#{foreign_key}": self.id).first
161
+ instance_variable_get("@#{association}") ||
162
+ klass.where("#{foreign_key}": self.id).first
163
+ end
164
+
165
+ define_method("#{association}=".to_sym) do |val|
166
+ instance_variable_set("@#{association}", val)
167
+ save!
168
+ end
169
+
170
+ before_save do
171
+ associated_object = instance_variable_get("@#{association}")
172
+ if associated_object
173
+ associated_object.send("#{foreign_key}=", self.id)
174
+ associated_object.save!
175
+ end
90
176
  end
91
177
  end
92
178
 
93
- def has_many(association, klass, options = {})
94
- @@_associations[association] = klass
95
179
 
96
- opt_fk = options.delete(:foreign_key)
97
- foreign_key = opt_fk ? opt_fk : "#{self.name.split("::").last.tableize.singularize}_id"
98
180
 
99
- @@_association_options[association] = { foreign_key: foreign_key }
181
+
182
+
183
+
184
+
185
+
186
+
187
+ # The has_many method is used to set up a one-to-many connection with another model.
188
+ # This indicates that this model can be matched with zero or more instances of another model.
189
+ # For example, if your application includes authors and books, and each author can have many books,
190
+ # you'd declare the author model to have many books.
191
+ #
192
+ # association:: [Symbol] the name of the association
193
+ # options:: [Hash] a hash to set up options for the association
194
+ # :foreign_key - the foreign key used for the association. Defaults to "#{self.name.downcase}_id"
195
+ # :primary_key - the primary key used for the association. Defaults to "id"
196
+ # :class_name - the name of the associated object's class. Defaults to the name of the association
197
+ # :dependent - if set to :destroy, the associated object will be destroyed when this object is destroyed. This is the default behavior.
198
+ #
199
+ #
200
+ # Example:
201
+ # has_many :books
202
+ #
203
+ # This creates an author.books method that returns a collection of books for the author.
204
+ # It also creates a books= method that allows you to assign the books for the author.
205
+ #
206
+ def has_many(association, options = {})
207
+ @@_association_options[association] = {
208
+ foreign_key: "#{self.name.underscore}_id",
209
+ primary_key: "id",
210
+ class_name: association.to_s.singularize.to_sym
211
+ }.merge(options)
212
+
213
+ klass = @@_association_options[association][:class_name].to_s.classify.constantize
214
+ foreign_key = @@_association_options[association][:foreign_key]
215
+ primary_key = @@_association_options[association][:primary_key]
216
+ @@_associations[association] = klass
100
217
 
101
218
  define_method(association.to_sym) do
102
219
  args = {}
103
- args[foreign_key] = self.id
220
+ args["_#{primary_key}"] = self.send("#{association.to_s.singularize}_ids")
104
221
  self.new_record? ? association_reflection(association) : klass.where(args)
105
222
  end
106
223
 
224
+ define_method("#{association.to_s.singularize}_ids") do
225
+ instance_variable_get("@#{association.to_s.singularize}_ids".to_sym)
226
+ end
227
+
228
+ define_method("#{association.to_s.singularize}_ids=") do |val|
229
+ instance_variable_set("@#{association.to_s.singularize}_ids".to_sym, val)
230
+ end
231
+
232
+ define_method("#{association}=".to_sym) do |val|
233
+ val.each { |v| after_save_objects(v.attributes, association)}
234
+ self.send("#{association.to_s.singularize}_ids=", val.map(&:id))
235
+ dirty
236
+ end
237
+
107
238
  define_method("build_#{association}".to_sym) do |*args|
108
239
  opts = {}
109
240
  opts[foreign_key] = self.id
110
241
  args.first.merge! opts
111
242
  klass.new *args
112
243
  end
244
+
245
+ after_save do
246
+ save_associations
247
+ end
113
248
  end
114
249
 
250
+
251
+
252
+
253
+
254
+
115
255
  def validates_associated(*attr_names)
116
256
  validates_with AssociatedValidator, _merge_attributes(attr_names)
117
257
  end
@@ -131,7 +271,7 @@ module Stretchy
131
271
  end
132
272
 
133
273
  def reflect_on_association(association)
134
- ElasticRelation.new @@_associations[association]
274
+ Stretchy::Relation.new @@_associations[association]
135
275
  end
136
276
 
137
277
  def update_all(records, **attributes)
@@ -0,0 +1,85 @@
1
+ module Stretchy
2
+ module Attributes
3
+ module Transformers
4
+ class KeywordTransformer
5
+
6
+ KEYWORD_AGGREGATION_KEYS = [:terms, :rare_terms, :significant_terms, :cardinality, :string_stats]
7
+
8
+ attr_reader :attribute_types
9
+
10
+ def initialize(attribute_types)
11
+ @attribute_types = attribute_types
12
+ end
13
+
14
+ def cast_value_keys
15
+ values.transform_values do |value|
16
+ case value
17
+ when Array
18
+ value.map { |item| transform_keys_for_item(item) }
19
+ when Hash
20
+ transform_keys_for_item(value)
21
+ else
22
+ value
23
+ end
24
+ end
25
+ end
26
+
27
+ def keyword?(arg)
28
+ attr = @attribute_types[arg.to_s]
29
+ return false unless attr
30
+ attr.is_a?(Stretchy::Attributes::Type::Keyword)
31
+ end
32
+
33
+ def protected?(arg)
34
+ return false if arg.nil?
35
+ Stretchy::Relations::AggregationMethods::AGGREGATION_METHODS.include?(arg.to_sym)
36
+ end
37
+
38
+ def transform(item, *ignore)
39
+ item.each_with_object({}) do |(k, v), new_item|
40
+ if ignore && ignore.include?(k)
41
+ new_item[k] = v
42
+ next
43
+ end
44
+ new_key = (!protected?(k) && keyword?(k)) ? "#{k}.keyword" : k
45
+
46
+ new_value = v
47
+
48
+ if new_value.is_a?(Hash)
49
+ new_value = transform(new_value)
50
+ elsif new_value.is_a?(Array)
51
+ new_value = new_value.map { |i| i.is_a?(Hash) ? transform(i) : i }
52
+ elsif new_value.is_a?(String) || new_value.is_a?(Symbol)
53
+ new_value = "#{new_value}.keyword" if keyword?(new_value)
54
+ end
55
+
56
+ new_item[new_key] = new_value
57
+ end
58
+ end
59
+
60
+ # If terms are used, we assume that the field is a keyword field
61
+ # and append .keyword to the field name
62
+ # {terms: {field: 'gender'}}
63
+ # or nested aggs
64
+ # {terms: {field: 'gender'}, aggs: {name: {terms: {field: 'position.name'}}}}
65
+ # should be converted to
66
+ # {terms: {field: 'gender.keyword'}, aggs: {name: {terms: {field: 'position.name.keyword'}}}}
67
+ # {date_histogram: {field: 'created_at', interval: 'day'}}
68
+ # TODO: There may be cases where we don't want to add .keyword to the field and there should be a way to override this
69
+ def assume_keyword_field(args={}, parent_match=false)
70
+ if args.is_a?(Hash)
71
+ args.each do |k, v|
72
+ if v.is_a?(Hash)
73
+ assume_keyword_field(v, KEYWORD_AGGREGATION_FIELDS.include?(k))
74
+ else
75
+ next unless v.is_a?(String) || v.is_a?(Symbol)
76
+ args[k] = ([:field, :fields].include?(k.to_sym) && v !~ /\.keyword$/ && parent_match) ? "#{v}.keyword" : v.to_s
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,15 @@
1
+ module Stretchy
2
+ module Attributes
3
+ module Type
4
+
5
+ class Array < ActiveModel::Type::Value # :nodoc:
6
+
7
+ def type
8
+ :array
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Stretchy
2
+ module Attributes
3
+ module Type
4
+ class Hash < ActiveModel::Type::Value # :nodoc:
5
+ def type
6
+ :hash
7
+ end
8
+
9
+ private
10
+
11
+ def cast_value(value)
12
+ Elasticsearch::Model::HashWrapper[value]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Stretchy
2
+ module Attributes
3
+ module Type
4
+ class Keyword < ActiveModel::Type::String # :nodoc:
5
+ def type
6
+ :keyword
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Stretchy
2
+ module Attributes
3
+ module Type
4
+ # alias for ActiveModel::Type::String
5
+ class Text < ActiveModel::Type::String
6
+ def type
7
+ :text
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end