api_maker 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/api_maker/api_helpers/api_maker_helpers.rb +5 -0
- data/app/api_maker/services/can_can/load_abilities.rb +30 -0
- data/app/api_maker/services/devise/sign_in.rb +64 -0
- data/app/api_maker/services/devise/sign_out.rb +9 -0
- data/app/api_maker/services/models/find_or_create_by.rb +18 -0
- data/app/channels/api_maker/subscriptions_channel.rb +33 -2
- data/app/controllers/api_maker/base_controller.rb +7 -3
- data/app/controllers/api_maker/commands_controller.rb +26 -4
- data/app/controllers/api_maker/session_statuses_controller.rb +1 -1
- data/app/services/api_maker/abilities_loader.rb +104 -0
- data/app/services/api_maker/application_service.rb +2 -1
- data/app/services/api_maker/base_command.rb +248 -0
- data/app/services/api_maker/collection_command_service.rb +29 -15
- data/app/services/api_maker/collection_loader.rb +124 -0
- data/app/services/api_maker/command_failed_error.rb +3 -0
- data/app/services/api_maker/command_response.rb +17 -6
- data/app/services/api_maker/command_service.rb +3 -3
- data/app/services/api_maker/create_command.rb +11 -26
- data/app/services/api_maker/create_command_service.rb +3 -3
- data/app/services/api_maker/database_type.rb +9 -0
- data/app/services/api_maker/deep_merge_params.rb +26 -0
- data/app/services/api_maker/deserializer.rb +35 -0
- data/app/services/api_maker/destroy_command.rb +15 -21
- data/app/services/api_maker/destroy_command_service.rb +3 -3
- data/app/services/api_maker/generate_react_native_api_service.rb +3 -19
- data/app/services/api_maker/include_helpers.rb +17 -0
- data/app/services/api_maker/index_command.rb +8 -88
- data/app/services/api_maker/index_command_service.rb +5 -5
- data/app/services/api_maker/js_method_namer_service.rb +1 -1
- data/app/services/api_maker/locals_from_controller.rb +14 -0
- data/app/services/api_maker/member_command_service.rb +15 -13
- data/app/services/api_maker/model_classes_java_script_generator_service.rb +37 -0
- data/app/services/api_maker/model_content_generator_service.rb +17 -21
- data/app/services/api_maker/models/save.rb +29 -0
- data/app/services/api_maker/models_finder_service.rb +6 -2
- data/app/services/api_maker/models_generator_service.rb +6 -43
- data/app/services/api_maker/move_components_to_routes.rb +50 -0
- data/app/services/api_maker/primary_id_for_model.rb +6 -0
- data/app/services/api_maker/reset_indexed_db_service.rb +36 -0
- data/app/services/api_maker/routes_file_reloader.rb +20 -0
- data/app/services/api_maker/select_columns_on_collection.rb +78 -0
- data/app/services/api_maker/select_parser.rb +32 -0
- data/app/services/api_maker/service_command.rb +27 -0
- data/app/services/api_maker/service_command_service.rb +14 -0
- data/app/services/api_maker/simple_model_errors.rb +52 -0
- data/app/services/api_maker/update_command.rb +8 -24
- data/app/services/api_maker/update_command_service.rb +3 -3
- data/app/services/api_maker/valid_command.rb +4 -13
- data/app/services/api_maker/valid_command_service.rb +3 -3
- data/app/services/api_maker/validation_errors_generator_service.rb +146 -0
- data/app/views/api_maker/_data.html.erb +17 -11
- data/config/routes.rb +0 -2
- data/lib/api_maker/ability.rb +22 -7
- data/lib/api_maker/ability_loader.rb +9 -6
- data/lib/api_maker/base_collection_instance.rb +15 -0
- data/lib/api_maker/base_resource.rb +135 -9
- data/lib/api_maker/base_service.rb +14 -0
- data/lib/api_maker/collection_serializer.rb +95 -34
- data/lib/api_maker/command_spec_helper.rb +41 -11
- data/lib/api_maker/configuration.rb +31 -4
- data/lib/api_maker/expect_to_able_to_helper.rb +31 -0
- data/lib/api_maker/individual_command.rb +24 -9
- data/lib/api_maker/javascript/model-template.js.erb +39 -25
- data/lib/api_maker/javascript/models.js.erb +6 -0
- data/lib/api_maker/loader.rb +1 -1
- data/lib/api_maker/memory_storage.rb +1 -1
- data/lib/api_maker/model_extensions.rb +34 -18
- data/lib/api_maker/permitted_params_argument.rb +5 -1
- data/lib/api_maker/preloader.rb +71 -32
- data/lib/api_maker/preloader_base.rb +108 -0
- data/lib/api_maker/preloader_belongs_to.rb +34 -33
- data/lib/api_maker/preloader_has_many.rb +45 -39
- data/lib/api_maker/preloader_has_one.rb +30 -47
- data/lib/api_maker/railtie.rb +3 -11
- data/lib/api_maker/relationship_preloader.rb +42 -0
- data/lib/api_maker/resource_routing.rb +18 -4
- data/lib/api_maker/result_parser.rb +34 -20
- data/lib/api_maker/serializer.rb +53 -22
- data/lib/api_maker/spec_helper/browser_logs.rb +14 -0
- data/lib/api_maker/spec_helper/execute_collection_command.rb +46 -0
- data/lib/api_maker/spec_helper/execute_member_command.rb +52 -0
- data/lib/api_maker/spec_helper/expect_no_browser_errors.rb +18 -0
- data/lib/api_maker/spec_helper/wait_for_expect.rb +20 -0
- data/lib/api_maker/spec_helper/wait_for_flash_message.rb +21 -0
- data/lib/api_maker/spec_helper.rb +112 -48
- data/lib/api_maker/version.rb +1 -1
- data/lib/api_maker.rb +7 -3
- metadata +108 -89
- data/README.md +0 -476
- data/app/controllers/api_maker/devise_controller.rb +0 -60
- data/lib/api_maker/base_command.rb +0 -81
- data/lib/api_maker/javascript/api.js +0 -92
- data/lib/api_maker/javascript/base-model.js +0 -543
- data/lib/api_maker/javascript/bootstrap/attribute-row.jsx +0 -16
- data/lib/api_maker/javascript/bootstrap/attribute-rows.jsx +0 -47
- data/lib/api_maker/javascript/bootstrap/card.jsx +0 -79
- data/lib/api_maker/javascript/bootstrap/checkbox.jsx +0 -127
- data/lib/api_maker/javascript/bootstrap/checkboxes.jsx +0 -105
- data/lib/api_maker/javascript/bootstrap/live-table.jsx +0 -168
- data/lib/api_maker/javascript/bootstrap/money-input.jsx +0 -136
- data/lib/api_maker/javascript/bootstrap/radio-buttons.jsx +0 -80
- data/lib/api_maker/javascript/bootstrap/select.jsx +0 -168
- data/lib/api_maker/javascript/bootstrap/string-input.jsx +0 -203
- data/lib/api_maker/javascript/cable-connection-pool.js +0 -169
- data/lib/api_maker/javascript/cable-subscription-pool.js +0 -111
- data/lib/api_maker/javascript/cable-subscription.js +0 -33
- data/lib/api_maker/javascript/collection.js +0 -186
- data/lib/api_maker/javascript/commands-pool.js +0 -123
- data/lib/api_maker/javascript/custom-error.js +0 -14
- data/lib/api_maker/javascript/deserializer.js +0 -35
- data/lib/api_maker/javascript/devise.js.erb +0 -113
- data/lib/api_maker/javascript/error-logger.js +0 -119
- data/lib/api_maker/javascript/event-connection.jsx +0 -24
- data/lib/api_maker/javascript/event-created.jsx +0 -26
- data/lib/api_maker/javascript/event-destroyed.jsx +0 -26
- data/lib/api_maker/javascript/event-emitter-listener.jsx +0 -32
- data/lib/api_maker/javascript/event-listener.jsx +0 -41
- data/lib/api_maker/javascript/event-updated.jsx +0 -26
- data/lib/api_maker/javascript/form-data-to-object.js +0 -70
- data/lib/api_maker/javascript/included.js +0 -39
- data/lib/api_maker/javascript/key-value-store.js +0 -47
- data/lib/api_maker/javascript/logger.js +0 -23
- data/lib/api_maker/javascript/model-name.js +0 -21
- data/lib/api_maker/javascript/models-response-reader.js +0 -43
- data/lib/api_maker/javascript/paginate.jsx +0 -128
- data/lib/api_maker/javascript/params.js +0 -68
- data/lib/api_maker/javascript/resource-route.jsx +0 -75
- data/lib/api_maker/javascript/resource-routes.jsx +0 -36
- data/lib/api_maker/javascript/result.js +0 -25
- data/lib/api_maker/javascript/session-status-updater.js +0 -113
- data/lib/api_maker/javascript/sort-link.jsx +0 -88
- data/lib/api_maker/javascript/updated-attribute.jsx +0 -60
- data/lib/api_maker/preloader_through.rb +0 -101
- data/lib/api_maker/relationship_includer.rb +0 -42
data/lib/api_maker/preloader.rb
CHANGED
@@ -1,62 +1,101 @@
|
|
1
1
|
class ApiMaker::Preloader
|
2
|
-
|
2
|
+
attr_reader :api_maker_args, :key_path, :locals, :model_class, :preload_param
|
3
|
+
|
4
|
+
def initialize(
|
5
|
+
ability: nil,
|
6
|
+
api_maker_args: nil,
|
7
|
+
collection:,
|
8
|
+
data:,
|
9
|
+
key_path: [],
|
10
|
+
locals:,
|
11
|
+
preload_param:,
|
12
|
+
model_class: nil,
|
13
|
+
records:,
|
14
|
+
select:,
|
15
|
+
select_columns:
|
16
|
+
)
|
3
17
|
@ability = ability
|
4
|
-
@
|
18
|
+
@api_maker_args = api_maker_args
|
5
19
|
@collection = collection
|
6
20
|
@data = data
|
7
|
-
@
|
21
|
+
@key_path = key_path
|
22
|
+
@locals = locals
|
23
|
+
@preload_param = preload_param
|
24
|
+
@model_class = model_class || @collection.model
|
8
25
|
@records = records
|
9
26
|
@select = select
|
27
|
+
@select_columns = select_columns
|
10
28
|
end
|
11
29
|
|
12
|
-
def fill_data
|
13
|
-
parsed = ApiMaker::
|
30
|
+
def fill_data # rubocop:disable Metrics/AbcSize
|
31
|
+
parsed = ApiMaker::RelationshipPreloader.parse(preload_param)
|
14
32
|
return unless parsed
|
15
33
|
|
16
34
|
parsed.each do |key, value|
|
17
35
|
next unless key
|
18
36
|
|
19
|
-
|
37
|
+
key_path << key
|
38
|
+
|
39
|
+
reflection = model_class.reflections[key]
|
20
40
|
raise "Unknown reflection: #{@collection.model.name}##{key}" unless reflection
|
21
41
|
|
22
42
|
fill_empty_relationships_for_key(reflection, key)
|
23
43
|
preload_class = preload_class_for_key(reflection)
|
24
44
|
|
25
|
-
|
26
|
-
|
45
|
+
Rails.logger.debug { "API maker: Preloading #{model_class}: #{key_path.join(".")}" }
|
46
|
+
|
47
|
+
preload_result = ApiMaker::Configuration.profile(-> { "Preloading #{reflection.klass.name} with #{preload_class.name}" }) do
|
48
|
+
preload_class
|
49
|
+
.new(
|
50
|
+
ability: @ability,
|
51
|
+
api_maker_args: api_maker_args,
|
52
|
+
collection: @collection,
|
53
|
+
data: @data,
|
54
|
+
locals: locals,
|
55
|
+
records: @records,
|
56
|
+
reflection: reflection,
|
57
|
+
select: @select,
|
58
|
+
select_columns: @select_columns
|
59
|
+
)
|
60
|
+
.preload
|
61
|
+
end
|
62
|
+
|
63
|
+
if value.blank? || preload_result.empty?
|
64
|
+
key_path.pop
|
65
|
+
next
|
66
|
+
end
|
67
|
+
|
68
|
+
ApiMaker::Preloader
|
69
|
+
.new(
|
27
70
|
ability: @ability,
|
28
|
-
|
29
|
-
collection: @collection,
|
71
|
+
api_maker_args: api_maker_args,
|
30
72
|
data: @data,
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
73
|
+
collection: preload_result,
|
74
|
+
key_path: key_path.dup,
|
75
|
+
locals: locals,
|
76
|
+
preload_param: value,
|
77
|
+
model_class: reflection.klass,
|
78
|
+
records: @data.fetch(:preloaded),
|
79
|
+
select: @select,
|
80
|
+
select_columns: @select_columns
|
81
|
+
)
|
82
|
+
.fill_data
|
36
83
|
|
37
|
-
|
38
|
-
|
39
|
-
ApiMaker::Preloader.new(
|
40
|
-
ability: @ability,
|
41
|
-
args: @args,
|
42
|
-
data: @data,
|
43
|
-
collection: preload_result.fetch(:collection),
|
44
|
-
include_param: value,
|
45
|
-
records: @data.fetch(:included),
|
46
|
-
select: @select
|
47
|
-
).fill_data
|
84
|
+
key_path.pop
|
48
85
|
end
|
49
86
|
end
|
50
87
|
|
51
88
|
private
|
52
89
|
|
90
|
+
# Smoke test to make sure we aren't doing any additional and unnecessary queries
|
91
|
+
def check_collection_loaded!
|
92
|
+
raise "Collection wasn't loaded?" if @collection.is_a?(ActiveRecord::Relation) && !@collection.loaded?
|
93
|
+
end
|
94
|
+
|
53
95
|
def fill_empty_relationships_for_key(reflection, key)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
else
|
58
|
-
records_to_set = @records.select { |record| record.model.class == reflection.active_record }
|
59
|
-
end
|
96
|
+
check_collection_loaded!
|
97
|
+
collection_name = ApiMaker::MemoryStorage.current.resource_for_model(reflection.active_record).collection_name
|
98
|
+
records_to_set = @collection.map { |model| @records.dig(collection_name, model.id) }
|
60
99
|
|
61
100
|
case reflection.macro
|
62
101
|
when :has_many
|
@@ -0,0 +1,108 @@
|
|
1
|
+
class ApiMaker::PreloaderBase
|
2
|
+
attr_reader :ability, :api_maker_args, :collection, :data, :locals, :records, :reflection, :reflection_name, :select, :select_columns
|
3
|
+
|
4
|
+
def initialize(ability:, api_maker_args:, data:, collection:, locals:, records:, reflection:, select:, select_columns:)
|
5
|
+
@ability = ability
|
6
|
+
@api_maker_args = api_maker_args
|
7
|
+
@data = data
|
8
|
+
@collection = collection
|
9
|
+
@locals = locals
|
10
|
+
@reflection = reflection
|
11
|
+
@reflection_name = @reflection.name
|
12
|
+
@records = records
|
13
|
+
@select = select
|
14
|
+
@select_columns = select_columns
|
15
|
+
end
|
16
|
+
|
17
|
+
def collection_ids
|
18
|
+
@collection_ids ||= collection.map do |collection_model|
|
19
|
+
collection_model[reflection.active_record.primary_key]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def underscore_name
|
24
|
+
@underscore_name ||= ApiMaker::MemoryStorage.current.resource_for_model(reflection.active_record).underscore_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def models_with_join
|
28
|
+
@models_with_join ||= reflection.klass.find_by_sql(join_query.to_sql)
|
29
|
+
end
|
30
|
+
|
31
|
+
# ActiveRecord might have joined the relationship by a predictable alias. If so we need to use that alias
|
32
|
+
def joined_name
|
33
|
+
"#{reflection.name.to_s.pluralize}_#{reflection.klass.name.underscore.pluralize}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def join_query
|
37
|
+
if initial_join_query.to_sql.include?(joined_name)
|
38
|
+
collection = join_query_with_joined_name
|
39
|
+
table_name = joined_name
|
40
|
+
else
|
41
|
+
collection = join_query_with_normal_name
|
42
|
+
end
|
43
|
+
|
44
|
+
ApiMaker::SelectColumnsOnCollection.execute!(
|
45
|
+
collection: collection,
|
46
|
+
model_class: reflection.klass,
|
47
|
+
select_attributes: select,
|
48
|
+
select_columns: select_columns,
|
49
|
+
table_name: table_name
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def initial_join_query
|
54
|
+
reflection.active_record.joins(reflection.name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def join_query_with_joined_name
|
58
|
+
# Since the joined table is using a different name, we don't need to double join the original table with an alias like under 'join_query_with_normal_name'
|
59
|
+
# The "WHERE id IN sub_query version looks like this: # .where(joined_name => {reflection.klass.primary_key => accessible_query})
|
60
|
+
|
61
|
+
exists_query = accessible_query
|
62
|
+
.select("1")
|
63
|
+
.where("#{accessible_query.klass.table_name}.#{accessible_query.klass.primary_key} = #{joined_name}.#{reflection.klass.primary_key}")
|
64
|
+
|
65
|
+
initial_join_query
|
66
|
+
.select(reflection.active_record.arel_table[reflection.active_record.primary_key].as("api_maker_origin_id"))
|
67
|
+
.where(reflection.active_record.primary_key => collection_ids)
|
68
|
+
.where("EXISTS (#{exists_query.to_sql})")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Join a copy of the original table to be able to access previous table (by a copy) in the accessible query
|
72
|
+
# This is done to avoid "WHERE id IN sub_query" which is much slower than "WHERE EXISTS sub_query"
|
73
|
+
# The "WHERE id IN sub_query" version looks this simple line: .where(reflection.klass.table_name => {reflection.klass.primary_key => accessible_query})
|
74
|
+
def join_query_with_normal_name # rubocop:disable Metrics/AbcSize
|
75
|
+
query = initial_join_query
|
76
|
+
.select(reflection.active_record.arel_table[reflection.active_record.primary_key].as("api_maker_origin_id"))
|
77
|
+
.where(reflection.active_record.primary_key => collection_ids)
|
78
|
+
|
79
|
+
# No reason to add all the extra SQL if the ability has unconditioned read access
|
80
|
+
unless unconditioned_read_access?
|
81
|
+
exists_query = accessible_query
|
82
|
+
.select("1")
|
83
|
+
.where("#{accessible_query.klass.table_name}.#{accessible_query.klass.primary_key} = accessible_table.#{reflection.klass.primary_key}")
|
84
|
+
|
85
|
+
query = query
|
86
|
+
.joins(
|
87
|
+
"JOIN #{reflection.klass.table_name} AS accessible_table ON " \
|
88
|
+
"accessible_table.id = #{reflection.klass.table_name}.#{reflection.klass.primary_key}"
|
89
|
+
)
|
90
|
+
.where("EXISTS (#{exists_query.to_sql})")
|
91
|
+
end
|
92
|
+
|
93
|
+
query
|
94
|
+
end
|
95
|
+
|
96
|
+
def unconditioned_read_access?
|
97
|
+
relevant_rules = ability.__send__(:relevant_rules, :read, reflection.klass)
|
98
|
+
relevant_rules.each do |can_can_rule|
|
99
|
+
return true if can_can_rule.__send__(:conditions_empty?)
|
100
|
+
end
|
101
|
+
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def accessible_query
|
106
|
+
reflection.klass.accessible_by(ability)
|
107
|
+
end
|
108
|
+
end
|
@@ -1,58 +1,59 @@
|
|
1
|
-
class ApiMaker::PreloaderBelongsTo
|
2
|
-
def initialize(ability:, args:, data:, collection:, records:, reflection:, select:)
|
3
|
-
@ability = ability
|
4
|
-
@args = args
|
5
|
-
@data = data
|
6
|
-
@collection = collection
|
7
|
-
@reflection = reflection
|
8
|
-
@reflection_name = @reflection.name
|
9
|
-
@records = records
|
10
|
-
@select = select
|
11
|
-
end
|
12
|
-
|
1
|
+
class ApiMaker::PreloaderBelongsTo < ApiMaker::PreloaderBase
|
13
2
|
def preload
|
14
3
|
models.each do |model|
|
4
|
+
model_id = ApiMaker::PrimaryIdForModel.get(model)
|
5
|
+
|
15
6
|
records_for_model(model).each do |record|
|
16
|
-
record.relationships[
|
7
|
+
record.relationships[reflection_name] = model_id
|
17
8
|
end
|
18
9
|
|
19
|
-
serializer = ApiMaker::Serializer.new(ability:
|
20
|
-
|
10
|
+
serializer = ApiMaker::Serializer.new(ability: ability, api_maker_args: api_maker_args, locals: locals, model: model, select: select&.dig(model.class))
|
11
|
+
underscore_name = serializer.resource.underscore_name
|
21
12
|
|
22
|
-
|
23
|
-
|
13
|
+
data.fetch(:preloaded)[underscore_name] ||= {}
|
14
|
+
data.fetch(:preloaded).fetch(underscore_name)[model_id] ||= serializer
|
24
15
|
end
|
25
16
|
|
26
|
-
|
17
|
+
models
|
27
18
|
end
|
28
19
|
|
29
20
|
private
|
30
21
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def model_class
|
36
|
-
@model_class ||= @reflection.klass
|
22
|
+
# Collects all the parent foreign keys like "users.organization_id" when preloading organizations and removes the blank (if a user doesn't have an org.)
|
23
|
+
def look_up_values
|
24
|
+
@look_up_values ||= collection.map(&reflection.foreign_key.to_sym).reject(&:blank?)
|
37
25
|
end
|
38
26
|
|
39
27
|
def models
|
40
|
-
@models ||=
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
28
|
+
@models ||= if look_up_values.empty?
|
29
|
+
# There is nothing to preload
|
30
|
+
[]
|
31
|
+
else
|
32
|
+
query = reflection.klass.where(look_up_key => look_up_values)
|
33
|
+
query = query.instance_eval(&reflection.scope) if reflection.scope
|
34
|
+
query = query.accessible_by(ability) if ability
|
35
|
+
query = ApiMaker::SelectColumnsOnCollection.execute!(
|
36
|
+
collection: query,
|
37
|
+
model_class: reflection.klass,
|
38
|
+
select_attributes: select,
|
39
|
+
select_columns: select_columns,
|
40
|
+
table_name: query.klass.table_name
|
41
|
+
)
|
42
|
+
|
43
|
+
query.load
|
44
|
+
query
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
def look_up_key
|
49
|
-
@look_up_key ||=
|
49
|
+
@look_up_key ||= reflection.options[:primary_key] || reflection.klass.primary_key
|
50
50
|
end
|
51
51
|
|
52
52
|
def records_for_model(model)
|
53
|
-
|
54
|
-
|
53
|
+
# Force to string if one column is an integer and another is a string
|
54
|
+
records
|
55
|
+
.fetch(underscore_name)
|
55
56
|
.values
|
56
|
-
.select { |record| record.model
|
57
|
+
.select { |record| record.model[reflection.foreign_key].to_s == model[look_up_key].to_s }
|
57
58
|
end
|
58
59
|
end
|
@@ -1,68 +1,74 @@
|
|
1
|
-
class ApiMaker::PreloaderHasMany
|
2
|
-
def initialize(ability:, args:, data:, collection:, reflection:, records:, select:)
|
3
|
-
@ability = ability
|
4
|
-
@args = args
|
5
|
-
@data = data
|
6
|
-
@collection = collection
|
7
|
-
@reflection = reflection
|
8
|
-
@records = records
|
9
|
-
@select = select
|
10
|
-
|
11
|
-
raise "No inverse of for #{@reflection.active_record.name}##{@reflection.name}" unless @reflection.inverse_of
|
12
|
-
end
|
13
|
-
|
1
|
+
class ApiMaker::PreloaderHasMany < ApiMaker::PreloaderBase
|
14
2
|
def preload
|
15
3
|
models.each do |model|
|
16
4
|
preload_model(model)
|
17
5
|
end
|
18
6
|
|
19
|
-
|
7
|
+
models
|
20
8
|
end
|
21
9
|
|
22
10
|
private
|
23
11
|
|
24
12
|
def models
|
25
|
-
@models ||=
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
primary_key_column = @reflection.options[:primary_key]&.to_sym || @collection.primary_key.to_sym
|
30
|
-
query = @reflection.klass.where(@reflection.foreign_key => @collection.map(&primary_key_column))
|
31
|
-
query = query.joins(@reflection.inverse_of.name)
|
32
|
-
end
|
13
|
+
@models ||= if use_joined_query?
|
14
|
+
models_with_join
|
15
|
+
else
|
16
|
+
primary_key_arel_column = reflection.active_record.arel_table[reflection.active_record.primary_key]
|
33
17
|
|
34
|
-
query =
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
18
|
+
query = models_initial_query.select(primary_key_arel_column.as("api_maker_origin_id"))
|
19
|
+
query = query.instance_eval(&reflection.scope) if reflection.scope
|
20
|
+
query = query.accessible_by(ability) if ability
|
21
|
+
query = ApiMaker::SelectColumnsOnCollection.execute!(
|
22
|
+
collection: query,
|
23
|
+
model_class: reflection.klass,
|
24
|
+
select_attributes: select,
|
25
|
+
select_columns: select_columns,
|
26
|
+
table_name: query.klass.table_name
|
27
|
+
)
|
40
28
|
query
|
41
29
|
end
|
42
30
|
end
|
43
31
|
|
44
|
-
def
|
45
|
-
|
32
|
+
def use_joined_query?
|
33
|
+
reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) || reflection.options[:as].present?
|
34
|
+
end
|
35
|
+
|
36
|
+
def models_initial_query
|
37
|
+
raise "#{reflection.active_record.name}.#{reflection.name} didn't have an `inverse_of` instruction" unless reflection.inverse_of
|
38
|
+
|
39
|
+
query = reflection.klass.where(reflection.foreign_key => collection.map(&primary_key_column))
|
40
|
+
query.joins(reflection.inverse_of.name)
|
46
41
|
end
|
47
42
|
|
48
43
|
def preload_model(model)
|
49
44
|
origin_data = find_origin_data_for_model(model)
|
45
|
+
model_id = ApiMaker::PrimaryIdForModel.get(model)
|
50
46
|
|
51
|
-
origin_data.fetch(:r)[
|
52
|
-
|
47
|
+
reflection_data = origin_data.fetch(:r)[reflection.name] ||= []
|
48
|
+
reflection_data << model_id unless reflection_data.include?(model_id)
|
53
49
|
|
54
|
-
serializer = ApiMaker::Serializer.new(ability:
|
55
|
-
|
50
|
+
serializer = ApiMaker::Serializer.new(ability: ability, api_maker_args: api_maker_args, locals: locals, model: model, select: select&.dig(model.class))
|
51
|
+
underscore_name = serializer.resource.underscore_name
|
56
52
|
|
57
|
-
|
58
|
-
|
53
|
+
data.fetch(:preloaded)[underscore_name] ||= {}
|
54
|
+
data.fetch(:preloaded).fetch(underscore_name)[model_id] ||= serializer
|
55
|
+
end
|
56
|
+
|
57
|
+
def primary_key_column
|
58
|
+
@primary_key_column ||= if reflection.options[:primary_key]
|
59
|
+
reflection.options[:primary_key]&.to_sym
|
60
|
+
elsif collection.is_a?(Array)
|
61
|
+
collection.first.class.primary_key.to_sym
|
62
|
+
else
|
63
|
+
collection.primary_key.to_sym
|
64
|
+
end
|
59
65
|
end
|
60
66
|
|
61
67
|
def find_origin_data_for_model(model)
|
62
|
-
origin_id = model
|
63
|
-
origin_data =
|
68
|
+
origin_id = model[:api_maker_origin_id]
|
69
|
+
origin_data = records.fetch(underscore_name).fetch(origin_id)
|
64
70
|
|
65
|
-
raise "Couldn't find any origin data by that type (#{
|
71
|
+
raise "Couldn't find any origin data by that type (#{underscore_name}) and ID (#{origin_id})" unless origin_data
|
66
72
|
|
67
73
|
origin_data
|
68
74
|
end
|
@@ -1,70 +1,53 @@
|
|
1
|
-
class ApiMaker::PreloaderHasOne
|
2
|
-
def initialize(ability:, args:, data:, collection:, reflection:, records:, select: @select)
|
3
|
-
@ability = ability
|
4
|
-
@args = args
|
5
|
-
@data = data
|
6
|
-
@collection = collection
|
7
|
-
@reflection = reflection
|
8
|
-
@records = records
|
9
|
-
@select = select
|
10
|
-
|
11
|
-
raise "Records was nil" unless records
|
12
|
-
end
|
13
|
-
|
14
|
-
def collection_name
|
15
|
-
@collection_name ||= ApiMaker::MemoryStorage.current.resource_for_model(@reflection.active_record).collection_name
|
16
|
-
end
|
17
|
-
|
1
|
+
class ApiMaker::PreloaderHasOne < ApiMaker::PreloaderBase
|
18
2
|
def preload
|
19
3
|
models.each do |model|
|
20
|
-
ApiMaker::
|
4
|
+
model_id = ApiMaker::PrimaryIdForModel.get(model)
|
5
|
+
|
6
|
+
ApiMaker::Configuration.profile(-> { "Preloading #{model.class.name}##{model_id}" }) do
|
21
7
|
origin_data = origin_data_for_model(model)
|
22
|
-
origin_data.fetch(:r)[
|
8
|
+
origin_data.fetch(:r)[reflection.name] = model_id
|
23
9
|
|
24
|
-
serializer = ApiMaker::Serializer.new(ability:
|
25
|
-
|
10
|
+
serializer = ApiMaker::Serializer.new(ability: ability, api_maker_args: api_maker_args, locals: locals, model: model, select: select&.dig(model.class))
|
11
|
+
underscore_name = serializer.resource.underscore_name
|
26
12
|
|
27
|
-
|
28
|
-
|
13
|
+
data.fetch(:preloaded)[underscore_name] ||= {}
|
14
|
+
data.fetch(:preloaded).fetch(underscore_name)[model_id] ||= serializer
|
29
15
|
end
|
30
16
|
end
|
31
17
|
|
32
|
-
|
18
|
+
models
|
33
19
|
end
|
34
20
|
|
35
21
|
def models
|
36
|
-
@models ||=
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
22
|
+
@models ||= if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
23
|
+
models_with_join
|
24
|
+
else
|
25
|
+
query = query_normal
|
26
|
+
query = query.instance_eval(&reflection.scope) if reflection.scope
|
27
|
+
query = query.accessible_by(ability) if ability
|
28
|
+
query = ApiMaker::SelectColumnsOnCollection.execute!(
|
29
|
+
collection: query,
|
30
|
+
model_class: reflection.klass,
|
31
|
+
select_attributes: select,
|
32
|
+
select_columns: select_columns,
|
33
|
+
table_name: query.klass.table_name
|
34
|
+
)
|
35
|
+
query = query.fix if ApiMaker::DatabaseType.postgres?
|
45
36
|
query.load
|
46
37
|
query
|
47
38
|
end
|
48
39
|
end
|
49
40
|
|
50
41
|
def origin_data_for_model(model)
|
51
|
-
origin_id = model
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
def query_through
|
56
|
-
ApiMaker::PreloaderThrough.new(collection: @collection, reflection: @reflection).models_query_through_reflection
|
57
|
-
.select(@reflection.klass.arel_table[Arel.star])
|
58
|
-
.select(@reflection.active_record.arel_table[@reflection.active_record.primary_key].as("api_maker_origin_id"))
|
42
|
+
origin_id = model[:api_maker_origin_id]
|
43
|
+
data.dig!(:preloaded, underscore_name, origin_id)
|
59
44
|
end
|
60
45
|
|
61
46
|
def query_normal
|
62
|
-
|
63
|
-
.select(
|
64
|
-
.select(@reflection.klass.arel_table[@reflection.foreign_key].as("api_maker_origin_id"))
|
65
|
-
end
|
47
|
+
query = reflection.klass.where(reflection.foreign_key => collection.map(&:id))
|
48
|
+
.select(reflection.klass.arel_table[reflection.foreign_key].as("api_maker_origin_id"))
|
66
49
|
|
67
|
-
|
68
|
-
|
50
|
+
query = query.where("#{reflection.options.fetch(:as)}_type" => reflection.active_record.name) if reflection.options[:as]
|
51
|
+
query
|
69
52
|
end
|
70
53
|
end
|
data/lib/api_maker/railtie.rb
CHANGED
@@ -1,14 +1,6 @@
|
|
1
1
|
class ApiMaker::Railtie < Rails::Railtie
|
2
|
-
initializer "watch routes.json for changes and reload Rails routes if changed" do |
|
3
|
-
file_path = Rails.root.join("app
|
4
|
-
|
5
|
-
reloader = app.config.file_watcher.new([file_path]) do
|
6
|
-
app.reload_routes!
|
7
|
-
end
|
8
|
-
|
9
|
-
app.reloaders << reloader
|
10
|
-
app.reloader.to_run do
|
11
|
-
reloader.execute_if_updated
|
12
|
-
end
|
2
|
+
initializer "watch routes.json for changes and reload Rails routes if changed" do |_app|
|
3
|
+
file_path = Rails.root.join("app/javascript/shared/routes.json")
|
4
|
+
ApiMaker::RoutesFileReloader.execute!(file_paths: [file_path]) if File.exist?(file_path)
|
13
5
|
end
|
14
6
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class ApiMaker::RelationshipPreloader
|
2
|
+
def self.parse(preload_param)
|
3
|
+
if preload_param.nil?
|
4
|
+
nil
|
5
|
+
elsif preload_param.is_a?(String)
|
6
|
+
ApiMaker::RelationshipPreloader.parse_string(preload_param)
|
7
|
+
elsif preload_param.is_a?(Array)
|
8
|
+
ApiMaker::RelationshipPreloader.parse_array(preload_param)
|
9
|
+
else
|
10
|
+
raise "Unexpected parameter given (#{preload_param.class.name}): #{preload_param}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse_string(preload_param)
|
15
|
+
splitted = preload_param.split(".")
|
16
|
+
initial = splitted.shift
|
17
|
+
rest = splitted.join(".")
|
18
|
+
{initial => rest}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.parse_array(preload_param)
|
22
|
+
result = {}
|
23
|
+
preload_param.each do |preload_param_i|
|
24
|
+
parsed = ApiMaker::RelationshipPreloader.parse(preload_param_i)
|
25
|
+
parsed.each do |key, value|
|
26
|
+
if result.key?(key)
|
27
|
+
if result[key].is_a?(String)
|
28
|
+
result[key] = [result[key], value]
|
29
|
+
elsif result[key].is_a?(Array)
|
30
|
+
result[key] << value
|
31
|
+
else
|
32
|
+
raise "Unknown object: #{result[key].class.name}"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
result[key] = value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
@@ -1,8 +1,22 @@
|
|
1
1
|
class ApiMaker::ResourceRouting
|
2
|
-
def self.install_resource_routes(rails_routes, layout: "react",
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
def self.install_resource_routes(rails_routes, layout: "react", route_definitions:)
|
3
|
+
rails_routes.instance_variable_set(:@api_maker_installed_routes, {}) unless rails_routes.instance_variable_get(:@api_maker_installed_routes)
|
4
|
+
installed_routes = rails_routes.instance_variable_get(:@api_maker_installed_routes)
|
5
|
+
|
6
|
+
route_definitions.fetch("routes").each do |route|
|
7
|
+
route_name = route.fetch("name").to_sym
|
8
|
+
route_as = route_name
|
9
|
+
route_path = route.fetch("path")
|
10
|
+
|
11
|
+
if installed_routes.key?(route_name)
|
12
|
+
route_duplicate_count = installed_routes.fetch(route_name)
|
13
|
+
route_as = "#{route_as}_duplicate_#{route_duplicate_count}"
|
14
|
+
end
|
15
|
+
|
16
|
+
rails_routes.get route_path => "#{layout}#show", as: route_as
|
17
|
+
|
18
|
+
installed_routes[route_name] ||= 0
|
19
|
+
installed_routes[route_name] += 1
|
6
20
|
end
|
7
21
|
end
|
8
22
|
end
|