rom-repository 0.2.0 → 0.3.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -5
  4. data/CHANGELOG.md +24 -0
  5. data/Gemfile +21 -5
  6. data/README.md +6 -110
  7. data/lib/rom/repository/changeset/create.rb +26 -0
  8. data/lib/rom/repository/changeset/pipe.rb +40 -0
  9. data/lib/rom/repository/changeset/update.rb +82 -0
  10. data/lib/rom/repository/changeset.rb +99 -0
  11. data/lib/rom/repository/class_interface.rb +142 -0
  12. data/lib/rom/repository/command_compiler.rb +214 -0
  13. data/lib/rom/repository/command_proxy.rb +22 -0
  14. data/lib/rom/repository/header_builder.rb +13 -16
  15. data/lib/rom/repository/mapper_builder.rb +7 -14
  16. data/lib/rom/repository/{loading_proxy → relation_proxy}/wrap.rb +7 -7
  17. data/lib/rom/repository/relation_proxy.rb +225 -0
  18. data/lib/rom/repository/root.rb +110 -0
  19. data/lib/rom/repository/struct_attributes.rb +46 -0
  20. data/lib/rom/repository/struct_builder.rb +31 -14
  21. data/lib/rom/repository/version.rb +1 -1
  22. data/lib/rom/repository.rb +192 -31
  23. data/lib/rom/struct.rb +13 -8
  24. data/rom-repository.gemspec +9 -10
  25. data/spec/integration/changeset_spec.rb +86 -0
  26. data/spec/integration/command_macros_spec.rb +175 -0
  27. data/spec/integration/command_spec.rb +224 -0
  28. data/spec/integration/multi_adapter_spec.rb +3 -3
  29. data/spec/integration/repository_spec.rb +97 -2
  30. data/spec/integration/root_repository_spec.rb +88 -0
  31. data/spec/shared/database.rb +47 -3
  32. data/spec/shared/mappers.rb +35 -0
  33. data/spec/shared/models.rb +41 -0
  34. data/spec/shared/plugins.rb +66 -0
  35. data/spec/shared/relations.rb +76 -0
  36. data/spec/shared/repo.rb +38 -17
  37. data/spec/shared/seeds.rb +19 -0
  38. data/spec/spec_helper.rb +4 -1
  39. data/spec/support/mapper_registry.rb +1 -3
  40. data/spec/unit/changeset_spec.rb +58 -0
  41. data/spec/unit/header_builder_spec.rb +34 -35
  42. data/spec/unit/relation_proxy_spec.rb +170 -0
  43. data/spec/unit/sql/relation_spec.rb +5 -5
  44. data/spec/unit/struct_builder_spec.rb +7 -4
  45. data/spec/unit/struct_spec.rb +22 -0
  46. metadata +38 -41
  47. data/lib/rom/plugins/relation/key_inference.rb +0 -31
  48. data/lib/rom/repository/loading_proxy/combine.rb +0 -158
  49. data/lib/rom/repository/loading_proxy.rb +0 -182
  50. data/spec/unit/loading_proxy_spec.rb +0 -147
@@ -0,0 +1,110 @@
1
+ module ROM
2
+ class Repository
3
+ # A specialized repository type dedicated to work with a root relation
4
+ #
5
+ # This repository type builds commands and aggregates for its root relation
6
+ #
7
+ # @example
8
+ # class UserRepo < ROM::Repository[:users]
9
+ # commands :create, update: :by_pk, delete: :by_pk
10
+ # end
11
+ #
12
+ # rom = ROM.container(:sql, 'sqlite::memory') do |conf|
13
+ # conf.default.create_table(:users) do
14
+ # primary_key :id
15
+ # column :name, String
16
+ # end
17
+ # end
18
+ #
19
+ # user_repo = UserRepo.new(rom)
20
+ #
21
+ # user = user_repo.create(name: "Jane")
22
+ #
23
+ # changeset = user_repo.changeset(user.id, name: "Jane Doe")
24
+ # user_repo.update(user.id, changeset)
25
+ #
26
+ # user_repo.delete(user.id)
27
+ #
28
+ # @api public
29
+ class Root < Repository
30
+ extend ClassMacros
31
+
32
+ defines :root
33
+
34
+ # @!attribute [r] root
35
+ # @return [RelationProxy] The root relation
36
+ attr_reader :root
37
+
38
+ # Sets descendant root relation
39
+ #
40
+ # @api private
41
+ def self.inherited(klass)
42
+ super
43
+ klass.root(root)
44
+ end
45
+
46
+ # @see Repository#initialize
47
+ def initialize(container)
48
+ super
49
+ @root = relations[self.class.root]
50
+ end
51
+
52
+ # Compose a relation aggregate from the root relation
53
+ #
54
+ # @overload aggregate(*associations)
55
+ # Composes an aggregate from configured associations on the root relation
56
+ #
57
+ # @example
58
+ # user_repo.aggregate(:tasks, :posts)
59
+ #
60
+ # @param *associations [Array<Symbol>] A list of association names
61
+ #
62
+ # @overload aggregate(options)
63
+ # Composes an aggregate by delegating to combine_children method.
64
+ #
65
+ # @param options [Hash] An option hash
66
+ #
67
+ # @see RelationProxy::Combine#combine_children
68
+ #
69
+ # @return [RelationProxy]
70
+ #
71
+ # @api public
72
+ def aggregate(*args)
73
+ if args[0].is_a?(Hash) && args.size == 1
74
+ root.combine_children(args[0])
75
+ else
76
+ root.combine(*args)
77
+ end
78
+ end
79
+
80
+ # @overload changeset(name, *args)
81
+ # Delegate to Repository#changeset
82
+ # @see Repository#changeset
83
+ #
84
+ # @overload changeset(data)
85
+ # Builds a create changeset for the root relation
86
+ # @example
87
+ # user_repo.changeset(name: "Jane")
88
+ # @param data [Hash] New data
89
+ # @return [Changeset::Create]
90
+ #
91
+ # @overload changeset(restriction_arg, data)
92
+ # Builds an update changeset for the root relation
93
+ # @example
94
+ # user_repo.changeset(1, name: "Jane Doe")
95
+ # @param restriction_arg [Object] An argument for the restriction view
96
+ # @return [Changeset::Update]
97
+ #
98
+ # @override Repository#changeset
99
+ #
100
+ # @api public
101
+ def changeset(*args)
102
+ if args.first.is_a?(Symbol) && relations.key?(args.first)
103
+ super
104
+ else
105
+ super(root.name, *args)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,46 @@
1
+ module ROM
2
+ class Repository
3
+ # @api private
4
+ class StructAttributes < Module
5
+ def initialize(attributes)
6
+ super()
7
+
8
+ define_constructor(attributes)
9
+
10
+ module_eval do
11
+ include Dry::Equalizer.new(*attributes)
12
+
13
+ attr_reader(*attributes)
14
+
15
+ define_method(:to_h) do
16
+ attributes.each_with_object({}) do |attribute, h|
17
+ h[attribute] = __send__(attribute)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def define_constructor(attributes)
24
+ module_eval do
25
+ def __missing_keyword__(keyword)
26
+ raise ArgumentError.new("missing keyword: #{keyword}")
27
+ end
28
+ private :__missing_keyword__
29
+ end
30
+
31
+ kwargs = attributes.map { |a| "#{a}: __missing_keyword__(:#{a})" }.join(', ')
32
+
33
+ ivs = attributes.map { |a| "@#{a}" }.join(', ')
34
+ values = attributes.join(', ')
35
+
36
+ assignment = attributes.size > 0 ? "#{ivs} = #{values}" : EMPTY_STRING
37
+
38
+ module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
39
+ def initialize(#{kwargs})
40
+ #{assignment}
41
+ end
42
+ RUBY
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,30 +1,47 @@
1
- require 'anima'
2
-
3
1
  require 'rom/struct'
4
2
 
3
+ require 'rom/support/cache'
4
+ require 'rom/support/constants'
5
+ require 'rom/support/class_builder'
6
+
7
+ require 'rom/repository/struct_attributes'
8
+
5
9
  module ROM
6
10
  class Repository
7
11
  # @api private
8
12
  class StructBuilder
9
- attr_reader :registry
13
+ extend Cache
14
+
15
+ def call(*args)
16
+ fetch_or_store(*args) do
17
+ name, header = args
18
+
19
+ build_class(name) { |klass|
20
+ klass.send(:include, StructAttributes.new(visit(header)))
21
+ }
22
+ end
23
+ end
24
+ alias_method :[], :call
25
+
26
+ private
10
27
 
11
- def self.registry
12
- @__registry__ ||= {}
28
+ def visit(ast)
29
+ name, node = ast
30
+ __send__("visit_#{name}", node)
13
31
  end
14
32
 
15
- def initialize
16
- @registry = self.class.registry
33
+ def visit_header(node)
34
+ node.map(&method(:visit))
17
35
  end
18
36
 
19
- def call(*args)
20
- name, columns = args
21
- registry[args.hash] ||= build_class(name) { |klass|
22
- klass.send(:include, Anima.new(*columns))
23
- }
37
+ def visit_relation(node)
38
+ relation_name, meta, * = node
39
+ meta[:combine_name] || relation_name.relation
24
40
  end
25
- alias_method :[], :call
26
41
 
27
- private
42
+ def visit_attribute(node)
43
+ node
44
+ end
28
45
 
29
46
  def build_class(name, &block)
30
47
  ROM::ClassBuilder.new(name: class_name(name), parent: Struct).call(&block)
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  class Repository
3
- VERSION = '0.2.0'.freeze
3
+ VERSION = '0.3.0'.freeze
4
4
  end
5
5
  end
@@ -1,60 +1,221 @@
1
1
  require 'rom/support/deprecations'
2
2
  require 'rom/support/options'
3
3
 
4
+ require 'rom/repository/class_interface'
4
5
  require 'rom/repository/mapper_builder'
5
- require 'rom/repository/loading_proxy'
6
+ require 'rom/repository/relation_proxy'
7
+ require 'rom/repository/command_compiler'
8
+
9
+ require 'rom/repository/root'
10
+ require 'rom/repository/changeset'
6
11
 
7
12
  module ROM
13
+ # Abstract repository class to inherit from
14
+ #
15
+ # A repository provides access to composable relations and commands. Its job is
16
+ # to provide application-specific data that is already materialized, so that
17
+ # relations don't leak into your application layer.
18
+ #
19
+ # Typically, you're going to work with Repository::Root that are configured to
20
+ # use a single relation as its root, and compose aggregates and use commands
21
+ # against the root relation.
22
+ #
23
+ # @example
24
+ # class MyRepo < ROM::Repository[:users]
25
+ # relations :users, :tasks
26
+ #
27
+ # def users_with_tasks
28
+ # users.combine_children(tasks: tasks).to_a
29
+ # end
30
+ # end
31
+ #
32
+ # rom = ROM.container(:sql, 'sqlite::memory') do |conf|
33
+ # conf.default.create_table(:users) do
34
+ # primary_key :id
35
+ # column :name, String
36
+ # end
37
+ #
38
+ # conf.default.create_table(:tasks) do
39
+ # primary_key :id
40
+ # column :user_id, Integer
41
+ # column :title, String
42
+ # end
43
+ # end
44
+ #
45
+ # my_repo = MyRepo.new(rom)
46
+ # my_repo.users_with_tasks
47
+ #
48
+ # @see Repository::Root
49
+ #
50
+ # @api public
8
51
  class Repository
9
- # Abstract repository class to inherit from
52
+ # @deprecated
53
+ class Base < Repository
54
+ def self.inherited(klass)
55
+ super
56
+ Deprecations.announce(self, 'inherit from Repository instead')
57
+ end
58
+ end
59
+
60
+ extend ClassInterface
61
+
62
+ # @!attribute [r] container
63
+ # @return [ROM::Container] The container used to set up a repo
64
+ attr_reader :container
65
+
66
+ # @!attribute [r] relations
67
+ # @return [RelationRegistry] The relation proxy registry used by a repo
68
+ attr_reader :relations
69
+
70
+ # @!attribute [r] mappers
71
+ # @return [MapperBuilder] The auto-generated mappers for repo relations
72
+ attr_reader :mappers
73
+
74
+ # Initializes a new repo by establishing configured relation proxies from
75
+ # the passed container
10
76
  #
11
- # TODO: rename this to Repository once deprecated Repository from rom core is gone
77
+ # @param container [ROM::Container] The rom container with relations and optional commands
12
78
  #
13
79
  # @api public
14
- include Options
80
+ def initialize(container)
81
+ @container = container
82
+ @mappers = MapperBuilder.new
83
+ @relations = RelationRegistry.new do |registry, relations|
84
+ self.class.relations.each do |name|
85
+ relation = container.relation(name)
15
86
 
16
- option :mapper_builder, reader: true, default: proc { MapperBuilder.new }
87
+ proxy = RelationProxy.new(
88
+ relation, name: name, mappers: mappers, registry: registry
89
+ )
90
+
91
+ instance_variable_set("@#{name}", proxy)
92
+
93
+ relations[name] = proxy
94
+ end
95
+ end
96
+ end
17
97
 
18
- # Define which relations your repository is going to use
98
+ # @overload command(type, relation)
99
+ # Returns a command for a relation
100
+ #
101
+ # @example
102
+ # repo.command(:create, repo.users)
103
+ #
104
+ # @param type [Symbol] The command type (:create, :update or :delete)
105
+ # @param relation [RelationProxy] The relation for which command should be built for
106
+ #
107
+ # @overload command(options)
108
+ # Builds a command for a given relation identifier
109
+ #
110
+ # @example
111
+ # repo.command(create: :users)
112
+ #
113
+ # @param options [Hash<Symbol=>Symbol>] A type => rel_name map
114
+ #
115
+ # @overload command(rel_name)
116
+ # Returns command registry for a given relation identifier
117
+ #
118
+ # @example
119
+ # repo.command(:users)[:my_custom_command]
19
120
  #
20
- # @example
21
- # class MyRepo < ROM::Repository::Base
22
- # relations :users, :tasks
23
- # end
121
+ # @param rel_name [Symbol] The relation identifier from the container
24
122
  #
25
- # my_repo = MyRepo.new(rom_env)
123
+ # @return [CommandRegistry]
26
124
  #
27
- # my_repo.users
28
- # my_repo.tasks
125
+ # @overload command(rel_name, &block)
126
+ # Yields a command graph composer for a given relation identifier
29
127
  #
30
- # @return [Array<Symbol>]
128
+ # @param rel_name [Symbol] The relation identifier from the container
129
+ #
130
+ # @return [ROM::Command]
31
131
  #
32
132
  # @api public
33
- def self.relations(*names)
34
- if names.any?
35
- attr_reader(*names)
36
- @relations = names
133
+ def command(*args, **opts, &block)
134
+ all_args = args + opts.to_a.flatten
135
+
136
+ if all_args.size > 1
137
+ commands.fetch_or_store(all_args.hash) do
138
+ compile_command(*args, **opts)
139
+ end
37
140
  else
38
- @relations
141
+ container.command(*args, &block)
39
142
  end
40
143
  end
41
144
 
42
- # @api private
43
- def initialize(env, options = {})
44
- super
45
- self.class.relations.each do |name|
46
- proxy = LoadingProxy.new(
47
- env.relation(name), name: name, mapper_builder: mapper_builder
48
- )
49
- instance_variable_set("@#{name}", proxy)
145
+ # @overload changeset(name, attributes)
146
+ # Returns a create changeset for a given relation identifier
147
+ #
148
+ # @example
149
+ # repo.changeset(:users, name: "Jane")
150
+ #
151
+ # @param name [Symbol] The relation container identifier
152
+ # @param attributes [Hash]
153
+ #
154
+ # @return [Changeset::Create]
155
+ #
156
+ # @overload changeset(name, restriction_arg, attributes)
157
+ # Returns an update changeset for a given relation identifier
158
+ #
159
+ # @example
160
+ # repo.changeset(:users, 1, name: "Jane Doe")
161
+ #
162
+ # @param name [Symbol] The relation container identifier
163
+ # @param restriction_arg [Object] The argument passed to restricted view
164
+ #
165
+ # @return [Changeset::Update]
166
+ #
167
+ # @api public
168
+ def changeset(*args)
169
+ if args.size == 2
170
+ name, data = args
171
+ elsif args.size == 3
172
+ name, pk, data = args
173
+ else
174
+ raise ArgumentError, 'Repository#changeset accepts 2 or 3 arguments'
175
+ end
176
+
177
+ relation = relations[name]
178
+
179
+ if pk
180
+ Changeset::Update.new(relation, data, primary_key: pk)
181
+ else
182
+ Changeset::Create.new(relation, data)
50
183
  end
51
184
  end
52
185
 
53
- class Base < Repository
54
- def self.inherited(klass)
55
- super
56
- Deprecations.announce(self, 'inherit from Repository instead')
186
+ private
187
+
188
+ # Local command cache
189
+ #
190
+ # @api private
191
+ def commands
192
+ @__commands__ ||= Concurrent::Map.new
193
+ end
194
+
195
+ # Build a new command or return existing one
196
+ #
197
+ # @api private
198
+ def compile_command(*args, mapper: nil, use: nil, **opts)
199
+ type, name = args + opts.to_a.flatten(1)
200
+
201
+ relation = name.is_a?(Symbol) ? relations[name] : name
202
+
203
+ ast = relation.to_ast
204
+ adapter = relations[relation.name].adapter
205
+
206
+ if mapper
207
+ mapper_instance = container.mappers[relation.name.relation][mapper]
208
+ else
209
+ mapper_instance = mappers[ast]
57
210
  end
211
+
212
+ command = CommandCompiler[container, type, adapter, ast, use]
213
+ command >> mapper_instance
214
+ end
215
+
216
+ # @api private
217
+ def map_tuple(relation, tuple)
218
+ relations[relation.name].mapper.([tuple]).first
58
219
  end
59
220
  end
60
221
  end
data/lib/rom/struct.rb CHANGED
@@ -1,7 +1,3 @@
1
- require 'anima'
2
-
3
- require 'rom/support/class_builder'
4
-
5
1
  module ROM
6
2
  # Simple data-struct
7
3
  #
@@ -9,7 +5,7 @@ module ROM
9
5
  #
10
6
  # @api public
11
7
  class Struct
12
- # Coerce to hash
8
+ # Coerces a struct to a hash
13
9
  #
14
10
  # @return [Hash]
15
11
  #
@@ -18,15 +14,24 @@ module ROM
18
14
  to_h
19
15
  end
20
16
 
21
- # Access attribute value
17
+ # Reads an attribute value
22
18
  #
23
- # @param [Symbol] name The name of the attribute
19
+ # @param name [Symbol] The name of the attribute
24
20
  #
25
21
  # @return [Object]
26
22
  #
27
23
  # @api public
28
24
  def [](name)
29
- instance_variable_get("@#{name}")
25
+ __send__(name)
26
+ end
27
+
28
+ # Returns a short string representation
29
+ #
30
+ # @return [String]
31
+ #
32
+ # @api public
33
+ def to_s
34
+ "#<#{self.class}:0x#{(object_id << 1).to_s(16)}>"
30
35
  end
31
36
  end
32
37
  end
@@ -4,22 +4,21 @@ require File.expand_path('../lib/rom/repository/version', __FILE__)
4
4
 
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = 'rom-repository'
7
- gem.summary = 'Repository for ROM with auto-mapping and relation extensions'
8
- gem.description = gem.summary
7
+ gem.summary = 'Repository abstraction for rom-rb'
8
+ gem.description = 'rom-repository adds support for auto-mapping and commands on top of rom-rb relations'
9
9
  gem.author = 'Piotr Solnica'
10
- gem.email = 'piotr.solnica@gmail.com'
10
+ gem.email = 'piotr.solnica+oss@gmail.com'
11
11
  gem.homepage = 'http://rom-rb.org'
12
12
  gem.require_paths = ['lib']
13
13
  gem.version = ROM::Repository::VERSION.dup
14
- gem.files = `git ls-files`.split("\n").reject { |name| name.include?('benchmarks') }
14
+ gem.files = `git ls-files`.split("\n").reject { |name| name.include?('benchmarks') || name.include?('examples') || name.include?('bin') }
15
15
  gem.test_files = `git ls-files -- {spec}/*`.split("\n")
16
16
  gem.license = 'MIT'
17
17
 
18
- gem.add_runtime_dependency 'anima', '~> 0.2', '>= 0.2'
19
- gem.add_runtime_dependency 'rom', '~> 1.0.0'
20
- gem.add_runtime_dependency 'rom-support', '~> 1.0.0'
21
- gem.add_runtime_dependency 'rom-mapper', '~> 0.3.0'
18
+ gem.add_runtime_dependency 'rom', '~> 2.0'
19
+ gem.add_runtime_dependency 'rom-support', '~> 2.0'
20
+ gem.add_runtime_dependency 'rom-mapper', '~> 0.4'
22
21
 
23
- gem.add_development_dependency 'rake', '~> 10.3'
24
- gem.add_development_dependency 'rspec', '~> 3.3'
22
+ gem.add_development_dependency 'rake', '~> 11.2'
23
+ gem.add_development_dependency 'rspec', '~> 3.5'
25
24
  end
@@ -0,0 +1,86 @@
1
+ RSpec.describe 'Using changesets' do
2
+ include_context 'database'
3
+ include_context 'relations'
4
+
5
+ describe 'Create' do
6
+ subject(:repo) do
7
+ Class.new(ROM::Repository[:users]) {
8
+ relations :books, :posts
9
+ commands :create, update: :by_pk
10
+ }.new(rom)
11
+ end
12
+
13
+ it 'can be passed to a command' do
14
+ changeset = repo.changeset(name: "Jane Doe")
15
+ command = repo.command(:create, repo.users)
16
+ result = command.(changeset)
17
+
18
+ expect(result.id).to_not be(nil)
19
+ expect(result.name).to eql("Jane Doe")
20
+ end
21
+
22
+ it 'can be passed to a command graph' do
23
+ changeset = repo.changeset(
24
+ name: "Jane Doe", posts: [{ title: "Just Do It", alien: "or sutin" }]
25
+ )
26
+
27
+ command = repo.command(:create, repo.aggregate(:posts))
28
+ result = command.(changeset)
29
+
30
+ expect(result.id).to_not be(nil)
31
+ expect(result.name).to eql("Jane Doe")
32
+ expect(result.posts.size).to be(1)
33
+ expect(result.posts[0].title).to eql("Just Do It")
34
+ end
35
+
36
+ it 'preprocesses data using changeset pipes' do
37
+ changeset = repo.changeset(:books, title: "rom-rb is awesome").map(:add_timestamps)
38
+ command = repo.command(:create, repo.books)
39
+ result = command.(changeset)
40
+
41
+ expect(result.id).to_not be(nil)
42
+ expect(result.title).to eql("rom-rb is awesome")
43
+ expect(result.created_at).to be_instance_of(Time)
44
+ expect(result.updated_at).to be_instance_of(Time)
45
+ end
46
+ end
47
+
48
+ describe 'Update' do
49
+ subject(:repo) do
50
+ Class.new(ROM::Repository[:books]) {
51
+ commands :create, update: :by_pk
52
+ }.new(rom)
53
+ end
54
+
55
+ it 'can be passed to a command' do
56
+ book = repo.create(title: 'rom-rb is awesome')
57
+
58
+ changeset = repo
59
+ .changeset(book.id, title: 'rom-rb is awesome for real')
60
+ .map(:touch)
61
+
62
+ expect(changeset.diff).to eql(title: 'rom-rb is awesome for real')
63
+
64
+ result = repo.update(book.id, changeset)
65
+
66
+ expect(result.id).to be(book.id)
67
+ expect(result.title).to eql('rom-rb is awesome for real')
68
+ expect(result.updated_at).to be_instance_of(Time)
69
+ end
70
+
71
+ it 'skips update execution with no diff' do
72
+ book = repo.create(title: 'rom-rb is awesome')
73
+
74
+ changeset = repo
75
+ .changeset(book.id, title: 'rom-rb is awesome')
76
+
77
+ expect(changeset).to_not be_diff
78
+
79
+ result = repo.update(book.id, changeset)
80
+
81
+ expect(result.id).to be(book.id)
82
+ expect(result.title).to eql('rom-rb is awesome')
83
+ expect(result.updated_at).to be(nil)
84
+ end
85
+ end
86
+ end