praxis 2.0.pre.2 → 2.0.pre.3
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 +5 -5
- data/CHANGELOG.md +6 -0
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +27 -31
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +35 -39
- data/lib/praxis/mapper/active_model_compat.rb +38 -3
- data/lib/praxis/mapper/resource.rb +3 -3
- data/lib/praxis/mapper/selector_generator.rb +98 -75
- data/lib/praxis/mapper/sequel_compat.rb +42 -3
- data/lib/praxis/plugins/mapper_plugin.rb +16 -2
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +3 -0
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +106 -0
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +147 -0
- data/spec/praxis/extensions/field_selection/support/spec_resources_active_model.rb +130 -0
- data/spec/praxis/extensions/field_selection/support/spec_resources_sequel.rb +106 -0
- data/spec/praxis/mapper/selector_generator_spec.rb +275 -283
- data/spec/spec_helper.rb +11 -0
- data/spec/support/be_deep_equal_matcher.rb +39 -0
- data/spec/support/spec_resources.rb +42 -49
- metadata +37 -4
- data/spec/spec_app/app/models/person.rb +0 -3
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'active_support/concern'
|
2
2
|
|
3
|
+
|
3
4
|
module Praxis::Mapper
|
4
5
|
module SequelCompat
|
5
6
|
extend ActiveSupport::Concern
|
6
7
|
|
7
|
-
|
8
|
+
included do
|
8
9
|
attr_accessor :_resource
|
9
10
|
end
|
10
11
|
|
@@ -13,11 +14,16 @@ module Praxis::Mapper
|
|
13
14
|
Praxis::Extensions::SequelFilterQueryBuilder
|
14
15
|
end
|
15
16
|
|
17
|
+
def _field_selector_query_builder_class
|
18
|
+
Praxis::Extensions::FieldSelection::SequelQuerySelector
|
19
|
+
end
|
20
|
+
|
16
21
|
def _praxis_associations
|
17
22
|
orig = self.association_reflections.clone
|
18
|
-
|
19
23
|
orig.each do |k,v|
|
20
24
|
v[:model] = v.associated_class
|
25
|
+
v[:local_key_columns] = local_columns_used_for_the_association(v[:type], v)
|
26
|
+
v[:remote_key_columns] = remote_columns_used_for_the_association(v[:type], v)
|
21
27
|
if v.respond_to?(:primary_key)
|
22
28
|
v[:primary_key] = v.primary_key
|
23
29
|
else
|
@@ -31,7 +37,40 @@ module Praxis::Mapper
|
|
31
37
|
orig
|
32
38
|
end
|
33
39
|
|
40
|
+
private
|
41
|
+
def local_columns_used_for_the_association(type, assoc_reflection)
|
42
|
+
case type
|
43
|
+
when :one_to_many
|
44
|
+
# The associated table (or middle table if many to many) will point to us by PK
|
45
|
+
assoc_reflection[:primary_key_columns]
|
46
|
+
when :many_to_one
|
47
|
+
# We have the FKs to the associated model
|
48
|
+
assoc_reflection[:keys]
|
49
|
+
when :many_to_many
|
50
|
+
# The middle table if many to many) will point to us by key (usually the PK, but not always)
|
51
|
+
assoc_reflection[:left_primary_keys]
|
52
|
+
else
|
53
|
+
raise "association type #{type} not supported"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def remote_columns_used_for_the_association(type, assoc_reflection)
|
58
|
+
case type
|
59
|
+
when :one_to_many
|
60
|
+
# The columns in the associated table that will point back to the original association
|
61
|
+
assoc_reflection[:keys]
|
62
|
+
when :many_to_one
|
63
|
+
# The columns in the associated table that the children will point to (usually the PK, but not always) ??
|
64
|
+
[assoc_reflection.associated_class.primary_key]
|
65
|
+
when :many_to_many
|
66
|
+
# The middle table if many to many will point to us by key (usually the PK, but not always) ??
|
67
|
+
[assoc_reflection.associated_class.primary_key]
|
68
|
+
else
|
69
|
+
raise "association type #{type} not supported"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
34
73
|
end
|
35
74
|
|
36
75
|
end
|
37
|
-
end
|
76
|
+
end
|
@@ -8,6 +8,21 @@ module Praxis
|
|
8
8
|
|
9
9
|
class Plugin < Praxis::Plugin
|
10
10
|
include Singleton
|
11
|
+
|
12
|
+
def config_key
|
13
|
+
:mapper
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_config!
|
17
|
+
{} # override the default one, since we don't necessarily want to configure it via a yaml file.
|
18
|
+
end
|
19
|
+
|
20
|
+
def prepare_config!(node)
|
21
|
+
node.attributes do
|
22
|
+
attribute :debug_queries, Attributor::Boolean, default: false,
|
23
|
+
description: 'Weather or not to log debug information about queries executed in the build_query automation module'
|
24
|
+
end
|
25
|
+
end
|
11
26
|
end
|
12
27
|
|
13
28
|
module Controller
|
@@ -32,8 +47,7 @@ module Praxis
|
|
32
47
|
filters = request.params.filters if request.params&.respond_to?(:filters)
|
33
48
|
base_query = domain_model.craft_filter_query( base_query , filters: filters )
|
34
49
|
|
35
|
-
|
36
|
-
base_query = domain_model.craft_field_selection_query(base_query, selectors: selector_generator.selectors, resolved: resolved)
|
50
|
+
base_query = domain_model.craft_field_selection_query(base_query, selectors: selector_generator.selectors)
|
37
51
|
|
38
52
|
# TODO: handle pagination and ordering
|
39
53
|
base_query
|
data/lib/praxis/version.rb
CHANGED
data/praxis.gemspec
CHANGED
@@ -51,4 +51,7 @@ Gem::Specification.new do |spec|
|
|
51
51
|
spec.add_development_dependency 'fuubar', '~> 2'
|
52
52
|
spec.add_development_dependency 'yard', ">= 0.9.20"
|
53
53
|
spec.add_development_dependency 'coveralls'
|
54
|
+
# Just for the query selector extensions etc...
|
55
|
+
spec.add_development_dependency 'sequel', '~> 5'
|
56
|
+
spec.add_development_dependency 'activerecord', '> 4'
|
54
57
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require_relative 'support/spec_resources_active_model.rb'
|
4
|
+
|
5
|
+
describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
6
|
+
let(:selector_fields) do
|
7
|
+
{
|
8
|
+
name: true,
|
9
|
+
author: {
|
10
|
+
id: true,
|
11
|
+
books: true
|
12
|
+
},
|
13
|
+
category: {
|
14
|
+
name: true,
|
15
|
+
books: true
|
16
|
+
},
|
17
|
+
tags: {
|
18
|
+
name: true
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
let(:expected_select_from_to_query) do
|
23
|
+
# The columns to select from the top Simple model
|
24
|
+
[
|
25
|
+
:simple_name, # from the :name alias
|
26
|
+
:author_id, # the FK needed for the author association
|
27
|
+
:added_column, # from the extra column defined in the parent property
|
28
|
+
:category_uuid, # the FK needed for the cateory association
|
29
|
+
:id # We always load the primary keys
|
30
|
+
]
|
31
|
+
end
|
32
|
+
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields) }
|
33
|
+
|
34
|
+
subject(:selector) {described_class.new(query: query, selectors: selector_node) }
|
35
|
+
context '#generate with a mocked' do
|
36
|
+
let(:query) { double("Query") }
|
37
|
+
it 'calls the select columns for the top level, and includes the right association hashes' do
|
38
|
+
expect(query).to receive(:select).with(*expected_select_from_to_query).and_return(query)
|
39
|
+
expected_includes = {
|
40
|
+
author: {
|
41
|
+
books: {}
|
42
|
+
},
|
43
|
+
category: {
|
44
|
+
books: {}
|
45
|
+
},
|
46
|
+
tags: {}
|
47
|
+
}
|
48
|
+
expect(query).to receive(:includes).with(expected_includes).and_return(query)
|
49
|
+
expect(subject).to_not receive(:explain_query)
|
50
|
+
subject.generate
|
51
|
+
end
|
52
|
+
it 'calls the explain debug method if enabled' do
|
53
|
+
expect(query).to receive(:select).and_return(query)
|
54
|
+
expect(query).to receive(:includes).and_return(query)
|
55
|
+
expect(subject).to receive(:explain_query)
|
56
|
+
subject.generate(debug: true)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context '#generate with a real AR model' do
|
61
|
+
let(:query) { ActiveBook }
|
62
|
+
|
63
|
+
it 'calls the select columns for the top level, and includes the right association hashes' do
|
64
|
+
expected_includes = {
|
65
|
+
author: {
|
66
|
+
books: {}
|
67
|
+
},
|
68
|
+
category: {
|
69
|
+
books: {}
|
70
|
+
},
|
71
|
+
tags: {}
|
72
|
+
}
|
73
|
+
#expect(query).to receive(:includes).with(expected_includes).and_return(query)
|
74
|
+
expect(subject).to_not receive(:explain_query)
|
75
|
+
final_query = subject.generate
|
76
|
+
expect(final_query.select_values).to match_array(expected_select_from_to_query)
|
77
|
+
# Our query selector always uses a single hash tree from the top, not an array of things
|
78
|
+
includes_hash = final_query.includes_values.first
|
79
|
+
expect(includes_hash).to match(expected_includes)
|
80
|
+
# Also, make AR do the actual query to make sure everything is wired up correctly
|
81
|
+
result = final_query.to_a
|
82
|
+
expect(result.size).to be 2
|
83
|
+
book1 = result[0]
|
84
|
+
book2 = result[1]
|
85
|
+
expect(book1.author.id).to eq 11
|
86
|
+
expect(book1.author.books.size).to eq 1
|
87
|
+
expect(book1.author.books.map(&:simple_name)).to eq(['Book1'])
|
88
|
+
expect(book1.category.name).to eq 'cat1'
|
89
|
+
expect(book1.tags.map(&:name)).to match_array(['blue','red'])
|
90
|
+
|
91
|
+
expect(book2.author.id).to eq 22
|
92
|
+
expect(book2.author.books.size).to eq 1
|
93
|
+
expect(book2.author.books.map(&:simple_name)).to eq(['Book2'])
|
94
|
+
expect(book2.category.name).to eq 'cat2'
|
95
|
+
expect(book2.tags.map(&:name)).to match_array(['red'])
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'calls the explain debug method if enabled' do
|
99
|
+
suppress_output do
|
100
|
+
# Actually make it run all the way...but suppressing the output
|
101
|
+
subject.generate(debug: true)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
require 'praxis/extensions/field_selection/sequel_query_selector'
|
5
|
+
|
6
|
+
|
7
|
+
describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
8
|
+
class Q
|
9
|
+
attr_reader :object, :cols
|
10
|
+
def initialize
|
11
|
+
@object = {}
|
12
|
+
@cols = []
|
13
|
+
end
|
14
|
+
def eager(hash)
|
15
|
+
raise "we are only calling eager one at a time!" if hash.keys.size > 1
|
16
|
+
name = hash.keys.first
|
17
|
+
# Actually call the incoming proc with an instance of Q, to collect the further select/eager calls
|
18
|
+
@object[name] = hash[name].call(Q.new)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
def select(*names)
|
22
|
+
@cols += names.map(&:column)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
def dump
|
26
|
+
eagers = @object.each_with_object({}) do |(name, val), hash|
|
27
|
+
hash[name] = val.dump
|
28
|
+
end
|
29
|
+
{
|
30
|
+
columns: @cols,
|
31
|
+
eagers: eagers
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Pay the price for creating and connecting only in this spec instead in spec helper
|
38
|
+
# this way all other specs do not need to be slower and it's a better TDD experience
|
39
|
+
|
40
|
+
require_relative 'support/spec_resources_sequel.rb'
|
41
|
+
|
42
|
+
let(:selector_fields) do
|
43
|
+
{
|
44
|
+
name: true,
|
45
|
+
other_model: {
|
46
|
+
id: true
|
47
|
+
},
|
48
|
+
parent: {
|
49
|
+
children: true
|
50
|
+
},
|
51
|
+
tags: {
|
52
|
+
tag_name: true
|
53
|
+
}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
let(:expected_select_from_to_query) do
|
57
|
+
# The columns to select from the top Simple model
|
58
|
+
[
|
59
|
+
:simple_name, # from the :name alias
|
60
|
+
:added_column, # from the extra column defined in the parent property
|
61
|
+
:id, # We always load the primary keys
|
62
|
+
:other_model_id, # the FK needed for the other_model association
|
63
|
+
:parent_id # the FK needed for the parent association
|
64
|
+
]
|
65
|
+
end
|
66
|
+
|
67
|
+
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields) }
|
68
|
+
subject {described_class.new(query: query, selectors: selector_node) }
|
69
|
+
|
70
|
+
context 'generate' do
|
71
|
+
context 'using the real models and DB' do
|
72
|
+
let(:query) { SequelSimpleModel }
|
73
|
+
|
74
|
+
it 'calls the select columns for the top level, and includes the right association hashes' do
|
75
|
+
ds = subject.generate
|
76
|
+
opts = ds.opts
|
77
|
+
# Top model is our simplemodel
|
78
|
+
expect(opts[:model]).to be(SequelSimpleModel)
|
79
|
+
selected_column_names = opts[:select].map(&:column)
|
80
|
+
expect(selected_column_names).to match_array(expected_select_from_to_query)
|
81
|
+
# 2 Eager loaded associations as well
|
82
|
+
expect(opts[:eager].keys).to match_array([:other_model, :parent, :tags])
|
83
|
+
# We can not introspect those eagers, as they are procs...but at least validate they are
|
84
|
+
expect(opts[:eager][:other_model]).to be_a Proc
|
85
|
+
expect(opts[:eager][:parent]).to be_a Proc
|
86
|
+
|
87
|
+
# Also, let's make sure the query actually works by making Sequel attempt to retrieve it and finding the right things.
|
88
|
+
result = ds.all
|
89
|
+
# 2 simple models
|
90
|
+
expect(result.size).to be 2
|
91
|
+
# First simple model points to other_model 11 and parent 1
|
92
|
+
simple_one = result.find{|i| i.id == 1}
|
93
|
+
expect(simple_one.other_model.id).to be 11
|
94
|
+
expect(simple_one.parent.id).to be 1
|
95
|
+
# also, its' parent in turn has 2 children (1 and 2) linked by its parent_uuid
|
96
|
+
expect(simple_one.parent.children.map(&:id)).to match_array([1,2])
|
97
|
+
# Has the blue and red tags
|
98
|
+
expect(simple_one.tags.map(&:tag_name)).to match_array(['blue','red'])
|
99
|
+
|
100
|
+
# second simple model points to other_model 22 and parent 2
|
101
|
+
simple_two = result.find{|i| i.id == 2}
|
102
|
+
expect(simple_two.other_model.id).to be 22
|
103
|
+
expect(simple_two.parent.id).to be 2
|
104
|
+
# also, its' parent in turn has no children (as no simple models point to it by uuid)
|
105
|
+
expect(simple_two.parent.children.map(&:id)).to be_empty
|
106
|
+
# Also has the red tag
|
107
|
+
expect(simple_two.tags.map(&:tag_name)).to match_array(['red'])
|
108
|
+
end
|
109
|
+
it 'calls the explain debug method if enabled' do
|
110
|
+
suppress_output do
|
111
|
+
# Actually make it run all the way...but suppressing the output
|
112
|
+
subject.generate(debug: true)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
context 'just mocking the query' do
|
117
|
+
let(:query) { Q.new }
|
118
|
+
|
119
|
+
it 'creates the right recursive lambdas for the eager loading' do
|
120
|
+
|
121
|
+
ds = subject.generate
|
122
|
+
result = ds.dump
|
123
|
+
expect(result[:columns]).to match_array(expected_select_from_to_query)
|
124
|
+
# 2 eager loads
|
125
|
+
expect(result[:eagers].keys).to match_array([:other_model, :parent, :tags])
|
126
|
+
# 1 - other model
|
127
|
+
other_model_eagers = result[:eagers][:other_model]
|
128
|
+
expect(other_model_eagers[:columns]).to match_array([:id])
|
129
|
+
|
130
|
+
# 2 - parent association
|
131
|
+
parent_eagers = result[:eagers][:parent]
|
132
|
+
expect(parent_eagers[:columns]).to match_array([:id,:uuid]) # uuid is necessary for the "children" assoc
|
133
|
+
expect(parent_eagers[:eagers].keys).to match_array([:children])
|
134
|
+
# 2.1 - children association off of the parent
|
135
|
+
parent_children_eagers = parent_eagers[:eagers][:children]
|
136
|
+
expect(parent_children_eagers[:columns]).to match_array([:id,:parent_uuid]) # parent_uuid is required for the assoc
|
137
|
+
expect(parent_children_eagers[:eagers]).to be_empty
|
138
|
+
|
139
|
+
# 3 - tags association
|
140
|
+
tags_eagers = result[:eagers][:tags]
|
141
|
+
expect(tags_eagers[:columns]).to match_array([:id, :tag_name]) # uuid is necessary for the "children" assoc
|
142
|
+
expect(tags_eagers[:eagers].keys).to be_empty
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
require 'praxis/mapper/active_model_compat'
|
4
|
+
|
5
|
+
# Creates a new in-memory DB, and the necessary tables (and mini-seeds) for the models in this file
|
6
|
+
def create_tables
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(
|
9
|
+
adapter: 'sqlite3',
|
10
|
+
dbfile: ':memory:',
|
11
|
+
database: ':memory:'
|
12
|
+
)
|
13
|
+
|
14
|
+
ActiveRecord::Schema.define do
|
15
|
+
ActiveRecord::Migration.suppress_messages do
|
16
|
+
create_table :active_books do |table|
|
17
|
+
table.column :simple_name, :string
|
18
|
+
table.column :added_column, :string
|
19
|
+
table.column :category_uuid, :string
|
20
|
+
table.column :author_id, :integer
|
21
|
+
end
|
22
|
+
|
23
|
+
create_table :active_authors do |table|
|
24
|
+
table.column :name, :string
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table :active_categories do |table|
|
28
|
+
table.column :uuid, :string
|
29
|
+
table.column :name, :string
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table :active_tags do |table|
|
33
|
+
table.column :name, :string
|
34
|
+
end
|
35
|
+
|
36
|
+
create_table :active_taggings do |table|
|
37
|
+
table.column :book_id, :integer
|
38
|
+
table.column :tag_id, :integer
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
create_tables
|
45
|
+
|
46
|
+
class ActiveBook < ActiveRecord::Base
|
47
|
+
include Praxis::Mapper::ActiveModelCompat
|
48
|
+
|
49
|
+
belongs_to :category, class_name: 'ActiveCategory', foreign_key: :category_uuid, primary_key: :uuid
|
50
|
+
belongs_to :author, class_name: 'ActiveAuthor'
|
51
|
+
has_many :taggings, class_name: 'ActiveTagging', foreign_key: :book_id
|
52
|
+
has_many :tags, class_name: 'ActiveTag', through: :taggings
|
53
|
+
end
|
54
|
+
|
55
|
+
class ActiveAuthor < ActiveRecord::Base
|
56
|
+
include Praxis::Mapper::ActiveModelCompat
|
57
|
+
has_many :books, class_name: 'ActiveBook', foreign_key: :author_id
|
58
|
+
end
|
59
|
+
|
60
|
+
class ActiveCategory < ActiveRecord::Base
|
61
|
+
include Praxis::Mapper::ActiveModelCompat
|
62
|
+
has_many :books, class_name: 'ActiveBook', primary_key: :uuid, foreign_key: :category_uuid
|
63
|
+
end
|
64
|
+
|
65
|
+
class ActiveTag < ActiveRecord::Base
|
66
|
+
include Praxis::Mapper::ActiveModelCompat
|
67
|
+
end
|
68
|
+
|
69
|
+
class ActiveTagging < ActiveRecord::Base
|
70
|
+
include Praxis::Mapper::ActiveModelCompat
|
71
|
+
belongs_to :book, class_name: 'ActiveBook', foreign_key: :book_id
|
72
|
+
belongs_to :tag, class_name: 'ActiveTag', foreign_key: :tag_id
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# A set of resource classes for use in specs
|
77
|
+
class ActiveBaseResource < Praxis::Mapper::Resource
|
78
|
+
end
|
79
|
+
|
80
|
+
class ActiveAuthorResource < ActiveBaseResource
|
81
|
+
model ActiveAuthor
|
82
|
+
|
83
|
+
property :display_name, dependencies: [:name]
|
84
|
+
end
|
85
|
+
|
86
|
+
class ActiveCategoryResource < ActiveBaseResource
|
87
|
+
model ActiveCategory
|
88
|
+
end
|
89
|
+
|
90
|
+
class ActiveTagResource < ActiveBaseResource
|
91
|
+
model ActiveTag
|
92
|
+
end
|
93
|
+
|
94
|
+
class ActiveBookResource < ActiveBaseResource
|
95
|
+
model ActiveBook
|
96
|
+
|
97
|
+
# Forces to add an extra column (added_column)...and yet another (author_id) that will serve
|
98
|
+
# to check that if that's already automatically added due to an association, it won't interfere or duplicate
|
99
|
+
property :author, dependencies: [:author, :added_column, :author_id]
|
100
|
+
|
101
|
+
property :name, dependencies: [:simple_name]
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def seed_data
|
106
|
+
cat1 = ActiveCategory.create( id: 1 , uuid: 'deadbeef1', name: 'cat1' )
|
107
|
+
cat2 = ActiveCategory.create( id: 2 , uuid: 'deadbeef2', name: 'cat2' )
|
108
|
+
|
109
|
+
author1 = ActiveAuthor.create( id: 11, name: 'author1' )
|
110
|
+
author2 = ActiveAuthor.create( id: 22, name: 'author2' )
|
111
|
+
|
112
|
+
tag_blue = ActiveTag.create(id: 1 , name: 'blue' )
|
113
|
+
tag_red = ActiveTag.create(id: 2 , name: 'red' )
|
114
|
+
|
115
|
+
book1 = ActiveBook.create( id: 1 , simple_name: 'Book1', category_uuid: 'deadbeef1')
|
116
|
+
book1.author = author1
|
117
|
+
book1.category = cat1
|
118
|
+
book1.save
|
119
|
+
ActiveTagging.create(book: book1, tag: tag_blue)
|
120
|
+
ActiveTagging.create(book: book1, tag: tag_red)
|
121
|
+
|
122
|
+
|
123
|
+
book2 = ActiveBook.create( id: 2 , simple_name: 'Book2', category_uuid: 'deadbeef1')
|
124
|
+
book2.author = author2
|
125
|
+
book2.category = cat2
|
126
|
+
book2.save
|
127
|
+
ActiveTagging.create(book: book2, tag: tag_red)
|
128
|
+
end
|
129
|
+
|
130
|
+
seed_data
|