pg_rails 7.0.8.pre.alpha.13 → 7.0.8.pre.alpha.14

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/pg_engine/app/controllers/admin/accounts_controller.rb +2 -0
  3. data/pg_engine/app/controllers/admin/user_accounts_controller.rb +2 -0
  4. data/pg_engine/app/controllers/admin/users_controller.rb +2 -0
  5. data/pg_engine/app/controllers/concerns/pg_engine/resource.rb +3 -3
  6. data/pg_engine/app/controllers/pg_engine/base_controller.rb +8 -7
  7. data/pg_engine/app/helpers/pg_engine/pg_rails_helper.rb +23 -0
  8. data/pg_engine/app/models/user.rb +18 -3
  9. data/pg_engine/config/initializers/rollbar.rb +78 -0
  10. data/pg_engine/db/seeds.rb +1 -1
  11. data/pg_engine/lib/pg_engine/utils/pg_logger.rb +47 -25
  12. data/pg_engine/lib/tasks/auto_anotar_modelos.rake +1 -3
  13. data/pg_engine/spec/controllers/admin/user_accounts_controller_spec.rb +2 -2
  14. data/pg_engine/spec/controllers/concerns/pg_engine/resource_helper_spec.rb +67 -0
  15. data/pg_engine/spec/factories/users.rb +1 -8
  16. data/pg_engine/spec/helpers/pg_engine/pg_rails_helper_spec.rb +38 -0
  17. data/pg_engine/spec/lib/pg_engine/utils/pg_engine/pg_logger_spec.rb +38 -0
  18. data/pg_engine/spec/models/user_spec.rb +39 -0
  19. data/pg_layout/app/assets/stylesheets/animations.scss +26 -0
  20. data/pg_layout/app/javascript/fadein_onload_controller.js +20 -0
  21. data/pg_layout/app/javascript/utils.ts +24 -0
  22. data/pg_layout/app/views/layouts/pg_layout/layout.html.slim +2 -0
  23. data/pg_layout/app/views/pg_layout/_rollbar.html.erb +28 -0
  24. data/pg_layout/index.js +2 -0
  25. data/pg_rails/lib/pg_rails/vcr_support.rb +21 -0
  26. data/pg_rails/lib/version.rb +1 -1
  27. data/pg_rails/scss/pg_rails.scss +1 -0
  28. data/pg_scaffold/lib/generators/pg_scaffold/templates/controller.rb +2 -0
  29. data/pg_scaffold/spec/generators_spec.rb +1 -1
  30. metadata +11 -3
  31. /data/pg_engine/spec/{controllers/concerns → lib}/pg_engine/error_helper_spec.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbc26d936f0bc92d89ee7125589969f980d4f4916bb49da05772425291b94306
4
- data.tar.gz: d144737ca5915119e635c8cf5b9cf9f78ad466c68ed7be79c0487f2e39e59c41
3
+ metadata.gz: a52205bc09a4d6de1b93e6a4272110bc740bfc72a1bb9cb306d0e4f3d7684e19
4
+ data.tar.gz: 49606131fec5b3933957c43d8ab29cf853a0b1a8944a27fa7527c8669a3ca46c
5
5
  SHA512:
6
- metadata.gz: d050775c0f3f3fec73e49eb17a25b9eb4aae187839045fa5323cefe842cf664806e1aaf9c43e896f517a8ec4a4a9d736c3a988c3398995feeb85fb35564820be
7
- data.tar.gz: cb652380ec0912c029522440634ce22016a7d6edf7d6b073e559664d96e181fcc229a3aa6e1b9861ab47107f217348b7e7be275874babb35f3a6494d0080653b
6
+ metadata.gz: b8beb180af71e265ec1d027666267628fa699904a560ed6e224deaf3009b7230f8b72520799e2b8465ed709b92f33f0eac81f4e61cd2777489e2a59f5a6e25cc
7
+ data.tar.gz: 5e4b1e3869550430832bae5bd5f0c205a94a5a16a41dd898e14f47fd9b786fa4c7d097f9c94a771379757b5b85752cc3adf823e7b294a74e51321f58a779124c
@@ -4,6 +4,8 @@
4
4
 
5
5
  module Admin
6
6
  class AccountsController < AdminController
7
+ include PgEngine::Resource
8
+
7
9
  before_action { @clase_modelo = Account }
8
10
 
9
11
  before_action(only: :index) { authorize Account }
@@ -4,6 +4,8 @@
4
4
 
5
5
  module Admin
6
6
  class UserAccountsController < AdminController
7
+ include PgEngine::Resource
8
+
7
9
  before_action { @clase_modelo = UserAccount }
8
10
 
9
11
  before_action(only: :index) { authorize UserAccount }
@@ -4,6 +4,8 @@
4
4
 
5
5
  module Admin
6
6
  class UsersController < AdminController
7
+ include PgEngine::Resource
8
+
7
9
  before_action { @clase_modelo = User }
8
10
 
9
11
  before_action(only: :index) { authorize User }
@@ -210,7 +210,7 @@ module PgEngine
210
210
  def buscar_instancia
211
211
  if Object.const_defined?('FriendlyId') && @clase_modelo.is_a?(FriendlyId)
212
212
  @clase_modelo.friendly.find(params[:id])
213
- elsif @clase_modelo.respond_to? :find_by_hashid
213
+ elsif @clase_modelo.respond_to? :find_by_hashid!
214
214
  # rubocop:disable Rails/DynamicFindBy
215
215
  @clase_modelo.find_by_hashid!(params[:id])
216
216
  # rubocop:enable Rails/DynamicFindBy
@@ -273,7 +273,7 @@ module PgEngine
273
273
 
274
274
  def do_sort(scope, field, direction)
275
275
  unless scope.model.column_names.include? field.to_s
276
- PgLogger.warning("No existe el campo \"#{field}\"")
276
+ PgLogger.warn("No existe el campo \"#{field}\"", :warn)
277
277
  return scope
278
278
  end
279
279
  scope = scope.order(field => direction)
@@ -281,7 +281,7 @@ module PgEngine
281
281
  instance_variable_set(:@direction, direction)
282
282
  scope
283
283
  rescue ArgumentError => e
284
- PgLogger.warning(e.to_s)
284
+ PgLogger.warn(e, :warn)
285
285
  scope
286
286
  end
287
287
 
@@ -40,13 +40,14 @@ module PgEngine
40
40
 
41
41
  protected
42
42
 
43
- def default_url_options(options = {})
44
- if Rails.env.production?
45
- options.merge(protocol: 'https')
46
- else
47
- options
48
- end
49
- end
43
+ # TODO: ver qué pasa en producción
44
+ # def default_url_options(options = {})
45
+ # if Rails.env.production?
46
+ # options.merge(protocol: 'https')
47
+ # else
48
+ # options
49
+ # end
50
+ # end
50
51
 
51
52
  def fecha_invalida
52
53
  respond_to do |format|
@@ -7,5 +7,28 @@ module PgEngine
7
7
  def current_account
8
8
  current_user&.current_account
9
9
  end
10
+
11
+ def img_placeholder(src = nil, width: '100%', height: '100%', fade_in: false, **img_opts)
12
+ if fade_in || src.nil?
13
+ img_opts = img_opts.merge(style: [img_opts[:style], 'display:none'].compact.join(';'))
14
+ do_placeholder(src, width:, height:, **img_opts)
15
+ else
16
+ image_tag src, **img_opts
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def do_placeholder(src = nil, width: '100%', height: '100%', **img_opts)
23
+ content_tag('div', class: 'placeholder-glow') do
24
+ content_tag('div', class: 'placeholder', style: "width: #{width}; height: #{height}") do
25
+ if src.present?
26
+ image_tag src,
27
+ 'data-controller': 'fadein_onload',
28
+ **img_opts
29
+ end
30
+ end
31
+ end
32
+ end
10
33
  end
11
34
  end
@@ -27,10 +27,8 @@
27
27
  # created_at :datetime not null
28
28
  # updated_at :datetime not null
29
29
  #
30
- class User < ApplicationRecord
31
- # ApplicationRecord should be defined on Application
32
30
 
33
- # Include default devise modules. Others available are:
31
+ class User < ApplicationRecord
34
32
  devise :database_authenticatable, :registerable,
35
33
  :recoverable, :rememberable,
36
34
  :lockable, :timeoutable, :trackable, :confirmable
@@ -51,6 +49,19 @@ class User < ApplicationRecord
51
49
  validates_length_of :password, if: :password_required?, within: 6..128,
52
50
  message: 'es demasiado corta (6 caracteres mínimo)'
53
51
 
52
+ attr_accessor :orphan
53
+
54
+ after_create do
55
+ # TODO: si fue invitado, sumar a la account del invitador
56
+ create_account unless orphan
57
+ end
58
+
59
+ def create_account
60
+ account = Account.create(nombre: email, plan: 0)
61
+ ua = user_accounts.create(account:)
62
+ raise(ActiveRecord::Rollback) unless ua.persisted?
63
+ end
64
+
54
65
  def password_required?
55
66
  !persisted? || !password.nil? || !password_confirmation.nil?
56
67
  end
@@ -65,7 +76,11 @@ class User < ApplicationRecord
65
76
  "#{nombre} #{apellido}"
66
77
  end
67
78
 
79
+ class Error < StandardError; end
80
+
68
81
  def current_account
82
+ raise Error, 'El usuario debe tener cuenta' if accounts.empty?
83
+
69
84
  accounts.first
70
85
  end
71
86
  end
@@ -0,0 +1,78 @@
1
+ Rollbar.configure do |config|
2
+ # Without configuration, Rollbar is enabled in all environments.
3
+ # To disable in specific environments, set config.enabled=false.
4
+
5
+ config.access_token = ENV['ROLLBAR_ACCESS_TOKEN']
6
+
7
+ # Here we'll disable in 'test':
8
+ if Rails.env.test?
9
+ config.enabled = false
10
+ end
11
+
12
+ # By default, Rollbar will try to call the `current_user` controller method
13
+ # to fetch the logged-in user object, and then call that object's `id`
14
+ # method to fetch this property. To customize:
15
+ # config.person_method = "my_current_user"
16
+ # config.person_id_method = "my_id"
17
+
18
+ # Additionally, you may specify the following:
19
+ # config.person_username_method = "username"
20
+ # config.person_email_method = "email"
21
+
22
+ # If you want to attach custom data to all exception and message reports,
23
+ # provide a lambda like the following. It should return a hash.
24
+ # config.custom_data_method = lambda { {:some_key => "some_value" } }
25
+
26
+ # Add exception class names to the exception_level_filters hash to
27
+ # change the level that exception is reported at. Note that if an exception
28
+ # has already been reported and logged the level will need to be changed
29
+ # via the rollbar interface.
30
+ # Valid levels: 'critical', 'error', 'warning', 'info', 'debug', 'ignore'
31
+ # 'ignore' will cause the exception to not be reported at all.
32
+ # config.exception_level_filters.merge!('MyCriticalException' => 'critical')
33
+ #
34
+ # You can also specify a callable, which will be called with the exception instance.
35
+ # config.exception_level_filters.merge!('MyCriticalException' => lambda { |e| 'critical' })
36
+
37
+ config.exception_level_filters.merge!({
38
+ 'ActionController::RoutingError' => 'ignore',
39
+ 'ActionDispatch::Http::MimeNegotiation::InvalidType' => 'ignore',
40
+ 'MailDeliveryTemporalError' => 'warning',
41
+ })
42
+
43
+
44
+ # Enable asynchronous reporting (uses girl_friday or Threading if girl_friday
45
+ # is not installed)
46
+ # config.use_async = true
47
+ # Supply your own async handler:
48
+ # config.async_handler = Proc.new { |payload|
49
+ # Thread.new { Rollbar.process_from_async_handler(payload) }
50
+ # }
51
+
52
+ # Enable asynchronous reporting (using sucker_punch)
53
+ # config.use_sucker_punch
54
+
55
+ # Enable delayed reporting (using Sidekiq)
56
+ # config.use_sidekiq
57
+ # You can supply custom Sidekiq options:
58
+ # config.use_sidekiq 'queue' => 'default'
59
+
60
+ # If your application runs behind a proxy server, you can set proxy parameters here.
61
+ # If https_proxy is set in your environment, that will be used. Settings here have precedence.
62
+ # The :host key is mandatory and must include the URL scheme (e.g. 'http://'), all other fields
63
+ # are optional.
64
+ #
65
+ # config.proxy = {
66
+ # host: 'http://some.proxy.server',
67
+ # port: 80,
68
+ # user: 'username_if_auth_required',
69
+ # password: 'password_if_auth_required'
70
+ # }
71
+
72
+ # If you run your staging application instance in production environment then
73
+ # you'll want to override the environment reported by `Rails.env` with an
74
+ # environment variable like this: `ROLLBAR_ENV=staging`. This is a recommended
75
+ # setup for Heroku. See:
76
+ # https://devcenter.heroku.com/articles/deploying-to-a-custom-rails-environment
77
+ config.environment = ENV['ROLLBAR_ENV'].presence || Rails.env
78
+ end
@@ -1,4 +1,4 @@
1
- DatabaseCleaner.clean_with(:truncation, except: %w(ar_internal_metadata users accounts user_accounts))
1
+ DatabaseCleaner.clean_with(:truncation, except: %w(ar_internal_metadata))
2
2
 
3
3
  MAIL = 'mrosso10@gmail.com'
4
4
 
@@ -2,42 +2,64 @@
2
2
 
3
3
  require 'rainbow'
4
4
 
5
+ def pg_warn(obj, type = :error)
6
+ PgEngine::PgLogger.warn(obj, type)
7
+ end
8
+
5
9
  module PgEngine
6
10
  class PgLogger
7
11
  class << self
8
- def deprecated(mensaje)
9
- titulo = Rainbow(" WARNING en #{caller[1]}").yellow.bold
10
- detalles = Rainbow(" #{mensaje}").yellow
11
- Rails.logger.warn("#{titulo}\n#{detalles}")
12
- Rollbar.warning("#{mensaje}\n\n#{caller.join("\n")}")
12
+ # DEPRECATED
13
+ # Muestro el caller[1] para saber dónde se llamó a la función deprecada
14
+ # def deprecated(mensaje)
15
+ # titulo = Rainbow(" WARNING en #{caller[1]}").yellow.bold
16
+ # detalles = Rainbow(" #{mensaje}").yellow
17
+ # Rails.logger.warn("#{titulo}\n#{detalles}")
18
+ # Rollbar.warning("#{mensaje}\n\n#{caller.join("\n")}")
19
+ # end
20
+
21
+ def warn(obj, type = :error)
22
+ mensaje = if obj.is_a? Exception
23
+ obj.full_message.lines.first
24
+ else
25
+ obj
26
+ end
27
+ notify(mensaje, type)
28
+ end
29
+
30
+ private
31
+
32
+ def notify(mensaje, type)
33
+ Rails.logger.send(type, titulo(mensaje, type))
34
+ Rails.logger.send(type, detalles(type))
35
+ Rollbar.send(type, "#{mensaje}\n\n#{bktrc.join("\n")}")
36
+ nil
13
37
  end
14
38
 
15
- def excepcion(exception)
16
- titulo = Rainbow(" EXCEPCION #{exception.class} en #{caller.first}").red.bold
17
- detalles = Rainbow(" #{exception.message}").red
18
- Rails.logger.error("#{titulo}\n#{detalles}")
19
- Rollbar.error(exception)
39
+ def titulo(mensaje, type)
40
+ Rainbow(mensaje).bold.send(color_for(type))
20
41
  end
21
42
 
22
- def error(mensaje)
23
- titulo = Rainbow(" ERROR en #{caller.first}").red.bold
24
- detalles = Rainbow(" #{mensaje}").red
25
- Rails.logger.error("#{titulo}\n#{detalles}")
26
- Rollbar.error("#{mensaje}\n\n#{caller.join("\n")}")
43
+ def detalles(type)
44
+ Rainbow("#{type.to_s.upcase} logueado en #{bktrc[0]}").send(color_for(type))
27
45
  end
28
46
 
29
- def warning(mensaje)
30
- titulo = Rainbow(" WARNING en #{caller.first}").yellow.bold
31
- detalles = Rainbow(" #{mensaje}").yellow
32
- Rails.logger.warn("#{titulo}\n#{detalles}")
33
- Rollbar.warning("#{mensaje}\n\n#{caller.join("\n")}")
47
+ def bktrc
48
+ bc = ActiveSupport::BacktraceCleaner.new
49
+ bc.add_filter { |line| line.gsub(%r{.*pg_rails/}, '') }
50
+ bc.add_silencer { |line| /pg_logger/.match?(line) }
51
+ bc.clean(caller)
34
52
  end
35
53
 
36
- def info(mensaje)
37
- titulo = Rainbow(" INFO en #{caller.first}").blue.bold
38
- detalles = Rainbow(" #{mensaje}").blue
39
- Rails.logger.info("#{titulo}\n#{detalles}")
40
- Rollbar.info("#{mensaje}\n\n#{caller.join("\n")}")
54
+ def color_for(type)
55
+ case type
56
+ when :info
57
+ :blue
58
+ when :warn
59
+ :yellow
60
+ else # :error
61
+ :red
62
+ end
41
63
  end
42
64
  end
43
65
  end
@@ -6,7 +6,7 @@ Dotenv.load
6
6
  model_paths = begin
7
7
  JSON.parse(ENV['MODEL_PATHS'])
8
8
  rescue JSON::ParserError
9
- ENV['MODEL_PATHS']
9
+ ENV['MODEL_PATHS'] || 'app/models'
10
10
  end
11
11
 
12
12
  if Rails.env.development?
@@ -30,8 +30,6 @@ if Rails.env.development?
30
30
  'show_indexes' => 'false',
31
31
  'simple_indexes' => 'true',
32
32
  'model_dir' => model_paths,
33
- # 'model_dir' => ['app/models', 'app/overrides'],
34
- # 'model_dir' => 'app/models',
35
33
  'root_dir' => '',
36
34
  'include_version' => 'false',
37
35
  'require' => '',
@@ -29,9 +29,9 @@ require 'rails_helper'
29
29
 
30
30
  RSpec.describe Admin::UserAccountsController do
31
31
  render_views
32
- let(:user) { create :orphan_user }
32
+ let!(:user) { create :user }
33
33
 
34
- let(:account) { create :account }
34
+ let!(:account) { create :account }
35
35
 
36
36
  # This should return the minimal set of attributes required to create a valid
37
37
  # UserAccount. As you add validations to UserAccount, be sure to
@@ -0,0 +1,67 @@
1
+ require 'rails_helper'
2
+
3
+ describe PgEngine::Resource do
4
+ let(:instancia) { Admin::CategoriaDeCosasController.new }
5
+
6
+ describe '#buscar_instancia' do
7
+ subject do
8
+ instancia.send(:buscar_instancia)
9
+ end
10
+
11
+ let!(:categoria_de_cosa) { create :categoria_de_cosa }
12
+ let(:request) { double }
13
+
14
+ before do
15
+ allow(request).to receive_messages(filtered_parameters: { id: categoria_de_cosa.to_param },
16
+ parameters: { id: categoria_de_cosa.to_param })
17
+ allow(instancia).to receive(:request).and_return(request)
18
+ instancia.set_clase_modelo
19
+ end
20
+
21
+ it do
22
+ allow(CategoriaDeCosa).to receive(:find_by_hashid!)
23
+ subject
24
+ expect(CategoriaDeCosa).to have_received(:find_by_hashid!)
25
+ end
26
+
27
+ it do
28
+ expect(subject).to eq categoria_de_cosa
29
+ end
30
+ end
31
+
32
+ describe '#do_sort' do
33
+ subject do
34
+ instancia.send(:do_sort, scope, param, direction)
35
+ end
36
+
37
+ let!(:categoria_de_cosa_ult) { create :categoria_de_cosa, nombre: 'Z' }
38
+ let!(:categoria_de_cosa_pri) { create :categoria_de_cosa, nombre: 'a' }
39
+ let(:scope) { CategoriaDeCosa.all }
40
+ let(:param) { :nombre }
41
+ let(:direction) { :desc }
42
+
43
+ context 'asc' do
44
+ let(:direction) { :asc }
45
+
46
+ it do
47
+ expect(subject.to_a).to eq [categoria_de_cosa_pri, categoria_de_cosa_ult]
48
+ end
49
+ end
50
+
51
+ context 'desc' do
52
+ let(:direction) { :desc }
53
+
54
+ it do
55
+ expect(subject.to_a).to eq [categoria_de_cosa_ult, categoria_de_cosa_pri]
56
+ end
57
+ end
58
+
59
+ context 'cuando no existe el param' do
60
+ let(:param) { :inexistente }
61
+
62
+ it do
63
+ expect(subject.to_a).to eq [categoria_de_cosa_ult, categoria_de_cosa_pri]
64
+ end
65
+ end
66
+ end
67
+ end
@@ -36,7 +36,7 @@
36
36
  #
37
37
 
38
38
  FactoryBot.define do
39
- factory :orphan_user, class: 'User' do
39
+ factory :user, class: 'User' do
40
40
  nombre { Faker::Name.name }
41
41
  apellido { Faker::Name.name }
42
42
  email { Faker::Internet.email }
@@ -50,12 +50,5 @@ FactoryBot.define do
50
50
  trait :developer do
51
51
  developer { true }
52
52
  end
53
-
54
- factory :user do
55
- after(:create) do |user, _context|
56
- account = create :account
57
- create :user_account, user:, account:
58
- end
59
- end
60
53
  end
61
54
  end
@@ -0,0 +1,38 @@
1
+ require 'rails_helper'
2
+ # rubocop:disable RSpec/ExampleLength
3
+ describe PgEngine::PgRailsHelper do
4
+ describe '#img_placeholder' do
5
+ it 'si no es fade_in' do
6
+ asd = img_placeholder('bla', fade_in: false, class: 'img-fluid', style: 'color:red')
7
+ expectation = <<~HTML
8
+ <img class="img-fluid" style="color:red" src="/images/bla" />
9
+ HTML
10
+ expect(asd).to eq expectation.split("\n").map(&:strip).join
11
+ end
12
+
13
+ it 'si tiene style' do
14
+ asd = img_placeholder('bla', fade_in: true, class: 'img-fluid', style: 'color:red')
15
+ expectation = <<~HTML
16
+ <div class="placeholder-glow">
17
+ <div class="placeholder" style="width: 100%; height: 100%">
18
+ <img data-controller="fadein_onload" class="img-fluid" style="color:red;display:none" src="/images/bla" />
19
+ </div>
20
+ </div>
21
+ HTML
22
+ expect(asd).to eq expectation.split("\n").map(&:strip).join
23
+ end
24
+
25
+ it 'si no tiene style' do
26
+ asd = img_placeholder('bla', fade_in: true, class: 'img-fluid')
27
+ expectation = <<~HTML
28
+ <div class="placeholder-glow">
29
+ <div class="placeholder" style="width: 100%; height: 100%">
30
+ <img data-controller="fadein_onload" class="img-fluid" style="display:none" src="/images/bla" />
31
+ </div>
32
+ </div>
33
+ HTML
34
+ expect(asd).to eq expectation.split("\n").map(&:strip).join
35
+ end
36
+ end
37
+ end
38
+ # rubocop:enable RSpec/ExampleLength
@@ -0,0 +1,38 @@
1
+ require 'rails_helper'
2
+
3
+ TYPES = %i[error warn info].freeze
4
+
5
+ describe PgEngine::PgLogger do
6
+ describe '#pg_warn' do
7
+ before do
8
+ TYPES.each do |type|
9
+ allow(Rails.logger).to receive(type)
10
+ allow(Rollbar).to receive(type)
11
+ end
12
+ end
13
+
14
+ shared_examples 'logger' do |type|
15
+ it do
16
+ expect(Rails.logger).to have_received(type).twice
17
+ end
18
+
19
+ it do
20
+ expect(Rollbar).to have_received(type).once
21
+ end
22
+ end
23
+
24
+ TYPES.each do |type|
25
+ context "con type #{type}" do
26
+ before { pg_warn('bla', type) }
27
+
28
+ it_behaves_like 'logger', type
29
+ end
30
+ end
31
+
32
+ context 'con exception' do
33
+ before { pg_warn(StandardError.new('bla')) }
34
+
35
+ it_behaves_like 'logger', :error
36
+ end
37
+ end
38
+ end
@@ -10,4 +10,43 @@ RSpec.describe User do
10
10
  it 'se persiste' do
11
11
  expect(user).to be_persisted
12
12
  end
13
+
14
+ it do
15
+ expect(user.current_account).to be_present
16
+ end
17
+
18
+ context 'si es orphan' do
19
+ let(:user) { create(:user, orphan: true) }
20
+
21
+ it do
22
+ expect(user.accounts).to be_empty
23
+ end
24
+
25
+ it do
26
+ expect { user.current_account }.to raise_error(User::Error)
27
+ end
28
+ end
29
+
30
+ context 'Si falla la creación de cuenta, que rollbackee la transaction de create user' do
31
+ let(:user) do
32
+ build(:user)
33
+ end
34
+
35
+ before do
36
+ # rubocop:disable RSpec/MessageChain
37
+ allow(user).to receive_message_chain(:user_accounts, :create) {
38
+ instance_double(UserAccount, persisted?: false)
39
+ }
40
+ # rubocop:enable RSpec/MessageChain
41
+ end
42
+
43
+ it do
44
+ expect { user.save }.not_to change(described_class, :count)
45
+ end
46
+
47
+ it do
48
+ user.save
49
+ expect(user).not_to be_persisted
50
+ end
51
+ end
13
52
  end
@@ -0,0 +1,26 @@
1
+ // Animations
2
+ .fade-in {
3
+ animation: fadeInAnimation 0.2s ease-in-out forwards;
4
+ }
5
+
6
+ .fade-out {
7
+ animation: fadeOutAnimation 0.2s ease forwards;
8
+ }
9
+
10
+ .init-invisible {
11
+ visibility: hidden;
12
+ }
13
+
14
+ .animation-long {
15
+ animation-duration: 1s;
16
+ }
17
+
18
+ @keyframes fadeInAnimation {
19
+ 0% { opacity: 0; visibility: visible; }
20
+ 100% { opacity: 1; visibility: visible; }
21
+ }
22
+
23
+ @keyframes fadeOutAnimation {
24
+ 0% { opacity: 1; visibility: visible; }
25
+ 100% { opacity: 0; visibility: hidden; }
26
+ }
@@ -0,0 +1,20 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ // To be used by img_placeholder helper
4
+ export default class extends Controller {
5
+ connect () {
6
+ if (this.element.complete) {
7
+ this.loaded()
8
+ } else {
9
+ this.element.addEventListener('load', () => {
10
+ this.loaded()
11
+ }, { once: true })
12
+ }
13
+ }
14
+
15
+ loaded () {
16
+ this.element.classList.add('fade-in')
17
+ this.element.style.display = 'block'
18
+ this.element.parentElement.classList.remove('placeholder')
19
+ }
20
+ }
@@ -17,3 +17,27 @@ export function showPercentage (value) {
17
17
  export function numberWithDots (x) {
18
18
  return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')
19
19
  }
20
+
21
+ export function fadeOut (e) {
22
+ if (window.getComputedStyle(e).visibility !== 'hidden') {
23
+ e.classList.add('fade-out')
24
+ e.addEventListener('animationend', onAnimationEndHide, { once: true })
25
+ }
26
+ }
27
+
28
+ export function fadeIn (e) {
29
+ if (window.getComputedStyle(e).visibility !== 'visible') {
30
+ e.classList.add('fade-in')
31
+ e.addEventListener('animationend', onAnimationEndShow, { once: true })
32
+ }
33
+ }
34
+
35
+ function onAnimationEndShow (e) {
36
+ e.target.style.visibility = 'visible'
37
+ e.target.classList.remove('fade-in')
38
+ }
39
+
40
+ function onAnimationEndHide (e) {
41
+ e.target.style.visibility = 'hidden'
42
+ e.target.classList.remove('fade-out')
43
+ }
@@ -13,6 +13,7 @@ html
13
13
 
14
14
  = stylesheet_link_tag 'application', 'data-turbo-track': 'reload'
15
15
  = javascript_include_tag 'application', 'data-turbo-track': 'reload', type: 'module'
16
+ = render partial: 'pg_layout/rollbar'
16
17
  body
17
18
  div class="#{ @sidebar == false ? '' : 'with-sidebar' }"
18
19
  - unless @sidebar == false
@@ -33,4 +34,5 @@ html
33
34
  = yield(:actions)
34
35
  hr.my-0
35
36
  = yield
37
+ div style="width:100%; height: 10em"
36
38
  = render_turbo_stream_title
@@ -0,0 +1,28 @@
1
+ <% if ENV['ROLLBAR_ACCESS_TOKEN_CLIENT'].present? %>
2
+ <script>
3
+ var _rollbarConfig = {
4
+ accessToken: '<%= ENV['ROLLBAR_ACCESS_TOKEN_CLIENT'] %>',
5
+ captureUncaught: true,
6
+ captureUnhandledRejections: true,
7
+ payload: {
8
+ environment: "production",
9
+ //trace_id: 'abc',
10
+ client: {
11
+ javascript: {
12
+ code_version: '1.0.0',
13
+ //source_map_enabled: true,
14
+ //guess_uncaught_frames: true
15
+ }
16
+ },
17
+ //server: {
18
+ //root: 'http://localhost:8000/demo/',
19
+ //host: 'host-1',
20
+ //branch: 'HEAD',
21
+ //},
22
+ }
23
+ };
24
+ // Rollbar Snippet
25
+ !function(r){var e={};function o(n){if(e[n])return e[n].exports;var t=e[n]={i:n,l:!1,exports:{}};return r[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.m=r,o.c=e,o.d=function(r,e,n){o.o(r,e)||Object.defineProperty(r,e,{enumerable:!0,get:n})},o.r=function(r){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(r,"__esModule",{value:!0})},o.t=function(r,e){if(1&e&&(r=o(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var t in r)o.d(n,t,function(e){return r[e]}.bind(null,t));return n},o.n=function(r){var e=r&&r.__esModule?function(){return r.default}:function(){return r};return o.d(e,"a",e),e},o.o=function(r,e){return Object.prototype.hasOwnProperty.call(r,e)},o.p="",o(o.s=0)}([function(r,e,o){"use strict";var n=o(1),t=o(5);_rollbarConfig=_rollbarConfig||{},_rollbarConfig.rollbarJsUrl=_rollbarConfig.rollbarJsUrl||"https://cdn.rollbar.com/rollbarjs/refs/tags/v2.26.3/rollbar.min.js",_rollbarConfig.async=void 0===_rollbarConfig.async||_rollbarConfig.async;var a=n.setupShim(window,_rollbarConfig),l=t(_rollbarConfig);window.rollbar=n.Rollbar,a.loadFull(window,document,!_rollbarConfig.async,_rollbarConfig,l)},function(r,e,o){"use strict";var n=o(2),t=o(3);function a(r){return function(){try{return r.apply(this,arguments)}catch(r){try{console.error("[Rollbar]: Internal error",r)}catch(r){}}}}var l=0;function i(r,e){this.options=r,this._rollbarOldOnError=null;var o=l++;this.shimId=function(){return o},"undefined"!=typeof window&&window._rollbarShims&&(window._rollbarShims[o]={handler:e,messages:[]})}var s=o(4),d=function(r,e){return new i(r,e)},c=function(r){return new s(d,r)};function u(r){return a((function(){var e=this,o=Array.prototype.slice.call(arguments,0),n={shim:e,method:r,args:o,ts:new Date};window._rollbarShims[this.shimId()].messages.push(n)}))}i.prototype.loadFull=function(r,e,o,n,t){var l=!1,i=e.createElement("script"),s=e.getElementsByTagName("script")[0],d=s.parentNode;i.crossOrigin="",i.src=n.rollbarJsUrl,o||(i.async=!0),i.onload=i.onreadystatechange=a((function(){if(!(l||this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)){i.onload=i.onreadystatechange=null;try{d.removeChild(i)}catch(r){}l=!0,function(){var e;if(void 0===r._rollbarDidLoad){e=new Error("rollbar.js did not load");for(var o,n,a,l,i=0;o=r._rollbarShims[i++];)for(o=o.messages||[];n=o.shift();)for(a=n.args||[],i=0;i<a.length;++i)if("function"==typeof(l=a[i])){l(e);break}}"function"==typeof t&&t(e)}()}})),d.insertBefore(i,s)},i.prototype.wrap=function(r,e,o){try{var n;if(n="function"==typeof e?e:function(){return e||{}},"function"!=typeof r)return r;if(r._isWrap)return r;if(!r._rollbar_wrapped&&(r._rollbar_wrapped=function(){o&&"function"==typeof o&&o.apply(this,arguments);try{return r.apply(this,arguments)}catch(o){var e=o;throw e&&("string"==typeof e&&(e=new String(e)),e._rollbarContext=n()||{},e._rollbarContext._wrappedSource=r.toString(),window._rollbarWrappedError=e),e}},r._rollbar_wrapped._isWrap=!0,r.hasOwnProperty))for(var t in r)r.hasOwnProperty(t)&&(r._rollbar_wrapped[t]=r[t]);return r._rollbar_wrapped}catch(e){return r}};for(var p="log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad".split(","),f=0;f<p.length;++f)i.prototype[p[f]]=u(p[f]);r.exports={setupShim:function(r,e){if(r){var o=e.globalAlias||"Rollbar";if("object"==typeof r[o])return r[o];r._rollbarShims={},r._rollbarWrappedError=null;var l=new c(e);return a((function(){e.captureUncaught&&(l._rollbarOldOnError=r.onerror,n.captureUncaughtExceptions(r,l,!0),e.wrapGlobalEventHandlers&&t(r,l,!0)),e.captureUnhandledRejections&&n.captureUnhandledRejections(r,l,!0);var a=e.autoInstrument;return!1!==e.enabled&&(void 0===a||!0===a||function(r){return!("object"!=typeof r||void 0!==r.page&&!r.page)}(a))&&r.addEventListener&&(r.addEventListener("load",l.captureLoad.bind(l)),r.addEventListener("DOMContentLoaded",l.captureDomContentLoaded.bind(l))),r[o]=l,l}))()}},Rollbar:c}},function(r,e,o){"use strict";function n(r,e,o,n){r._rollbarWrappedError&&(n[4]||(n[4]=r._rollbarWrappedError),n[5]||(n[5]=r._rollbarWrappedError._rollbarContext),r._rollbarWrappedError=null);var t=e.handleUncaughtException.apply(e,n);o&&o.apply(r,n),"anonymous"===t&&(e.anonymousErrorsPending+=1)}r.exports={captureUncaughtExceptions:function(r,e,o){if(r){var t;if("function"==typeof e._rollbarOldOnError)t=e._rollbarOldOnError;else if(r.onerror){for(t=r.onerror;t._rollbarOldOnError;)t=t._rollbarOldOnError;e._rollbarOldOnError=t}e.handleAnonymousErrors();var a=function(){var o=Array.prototype.slice.call(arguments,0);n(r,e,t,o)};o&&(a._rollbarOldOnError=t),r.onerror=a}},captureUnhandledRejections:function(r,e,o){if(r){"function"==typeof r._rollbarURH&&r._rollbarURH.belongsToShim&&r.removeEventListener("unhandledrejection",r._rollbarURH);var n=function(r){var o,n,t;try{o=r.reason}catch(r){o=void 0}try{n=r.promise}catch(r){n="[unhandledrejection] error getting `promise` from event"}try{t=r.detail,!o&&t&&(o=t.reason,n=t.promise)}catch(r){}o||(o="[unhandledrejection] error getting `reason` from event"),e&&e.handleUnhandledRejection&&e.handleUnhandledRejection(o,n)};n.belongsToShim=o,r._rollbarURH=n,r.addEventListener("unhandledrejection",n)}}}},function(r,e,o){"use strict";function n(r,e,o){if(e.hasOwnProperty&&e.hasOwnProperty("addEventListener")){for(var n=e.addEventListener;n._rollbarOldAdd&&n.belongsToShim;)n=n._rollbarOldAdd;var t=function(e,o,t){n.call(this,e,r.wrap(o),t)};t._rollbarOldAdd=n,t.belongsToShim=o,e.addEventListener=t;for(var a=e.removeEventListener;a._rollbarOldRemove&&a.belongsToShim;)a=a._rollbarOldRemove;var l=function(r,e,o){a.call(this,r,e&&e._rollbar_wrapped||e,o)};l._rollbarOldRemove=a,l.belongsToShim=o,e.removeEventListener=l}}r.exports=function(r,e,o){if(r){var t,a,l="EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload".split(",");for(t=0;t<l.length;++t)r[a=l[t]]&&r[a].prototype&&n(e,r[a].prototype,o)}}},function(r,e,o){"use strict";function n(r,e){this.impl=r(e,this),this.options=e,function(r){for(var e=function(r){return function(){var e=Array.prototype.slice.call(arguments,0);if(this.impl[r])return this.impl[r].apply(this.impl,e)}},o="log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad".split(","),n=0;n<o.length;n++)r[o[n]]=e(o[n])}(n.prototype)}n.prototype._swapAndProcessMessages=function(r,e){var o,n,t;for(this.impl=r(this.options);o=e.shift();)n=o.method,t=o.args,this[n]&&"function"==typeof this[n]&&("captureDomContentLoaded"===n||"captureLoad"===n?this[n].apply(this,[t[0],o.ts]):this[n].apply(this,t));return this},r.exports=n},function(r,e,o){"use strict";r.exports=function(r){return function(e){if(!e&&!window._rollbarInitialized){for(var o,n,t=(r=r||{}).globalAlias||"Rollbar",a=window.rollbar,l=function(r){return new a(r)},i=0;o=window._rollbarShims[i++];)n||(n=o.handler),o.handler._swapAndProcessMessages(l,o.messages);window[t]=n,window._rollbarInitialized=!0}}}}]);
26
+ // End Rollbar Snippet
27
+ </script>
28
+ <% end %>
data/pg_layout/index.js CHANGED
@@ -2,12 +2,14 @@
2
2
  import NavbarController from './app/javascript/navbar_controller'
3
3
  import NestedController from './app/javascript/nested_controller'
4
4
  import PgFormController from './app/javascript/pg_form_controller'
5
+ import FadeinOnloadController from './app/javascript/fadein_onload_controller'
5
6
  // Bootstrap's toasts
6
7
  import * as bootstrap from 'bootstrap'
7
8
 
8
9
  window.Stimulus.register('navbar', NavbarController)
9
10
  window.Stimulus.register('nested', NestedController)
10
11
  window.Stimulus.register('pg_form', PgFormController)
12
+ window.Stimulus.register('fadein_onload', FadeinOnloadController)
11
13
 
12
14
  document.addEventListener('turbo:load', function () {
13
15
  const toastElList = document.querySelectorAll('.toast:not(.hide):not(.show)')
@@ -0,0 +1,21 @@
1
+ require 'vcr'
2
+ require 'webmock/rspec'
3
+
4
+ VCR.configure do |config|
5
+ config.cassette_library_dir = 'spec/cassettes'
6
+ config.default_cassette_options = {
7
+ match_requests_on: %i[uri method],
8
+ record: :once
9
+ }
10
+ config.hook_into :webmock
11
+ config.configure_rspec_metadata!
12
+ end
13
+
14
+ RSpec.configure do |config|
15
+ config.around(:example, :vcr_cassettes) do |example|
16
+ cassettes = example.metadata[:vcr_cassettes].map { |cas_name| { name: cas_name } }
17
+ VCR.use_cassettes(cassettes) do
18
+ example.run
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgRails
4
- VERSION = '7.0.8-alpha.13'
4
+ VERSION = '7.0.8-alpha.14'
5
5
  end
@@ -1,3 +1,4 @@
1
1
  @import './../../pg_engine/app/assets/stylesheets/pg_rails_b5';
2
2
  @import './../../pg_associable/app/assets/stylesheets/pg_associable';
3
3
  @import './../../pg_layout/app/assets/stylesheets/sidebar';
4
+ @import './../../pg_layout/app/assets/stylesheets/animations';
@@ -9,6 +9,8 @@ require_dependency "<%= namespaced_path %>/application_controller"
9
9
  <% module_namespacing do -%>
10
10
  <% module_namespacing_2 do -%>
11
11
  class <%= controller_class_name.split('::').last %>Controller < <%= parent_controller %>
12
+ include PgEngine::Resource
13
+
12
14
  before_action { @clase_modelo = <%= class_name.split('::').last %> }
13
15
 
14
16
  before_action(only: :index) { authorize <%= class_name.split('::').last %> }
@@ -6,7 +6,7 @@ require 'generators/pg_active_record/model/model_generator'
6
6
 
7
7
  DESTINATION_PATH = File.expand_path('./../../tmp/generator_testing', __dir__)
8
8
 
9
- describe 'Generators' do
9
+ describe 'Generators', type: :generator do
10
10
  describe 'PgDecoratorGenerator' do
11
11
  destination DESTINATION_PATH
12
12
  tests PgDecoratorGenerator
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.8.pre.alpha.13
4
+ version: 7.0.8.pre.alpha.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martín Rosso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-14 00:00:00.000000000 Z
11
+ date: 2024-03-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Rails goodies.
14
14
  email:
@@ -90,6 +90,7 @@ files:
90
90
  - pg_engine/app/views/pg_engine/base/new.html.slim
91
91
  - pg_engine/config/initializers/active_admin.rb
92
92
  - pg_engine/config/initializers/devise.rb
93
+ - pg_engine/config/initializers/rollbar.rb
93
94
  - pg_engine/config/initializers/simple_form_monkey_patch.rb
94
95
  - pg_engine/config/locales/devise.en.yml
95
96
  - pg_engine/config/locales/es.yml
@@ -117,21 +118,26 @@ files:
117
118
  - pg_engine/spec/controllers/admin/accounts_controller_spec.rb
118
119
  - pg_engine/spec/controllers/admin/user_accounts_controller_spec.rb
119
120
  - pg_engine/spec/controllers/admin/users_controller_spec.rb
120
- - pg_engine/spec/controllers/concerns/pg_engine/error_helper_spec.rb
121
+ - pg_engine/spec/controllers/concerns/pg_engine/resource_helper_spec.rb
121
122
  - pg_engine/spec/controllers/devise/registrations_controller_spec.rb
122
123
  - pg_engine/spec/controllers/devise/sessions_controller_spec.rb
123
124
  - pg_engine/spec/factories/accounts.rb
124
125
  - pg_engine/spec/factories/user_accounts.rb
125
126
  - pg_engine/spec/factories/users.rb
126
127
  - pg_engine/spec/fixtures/test.pdf
128
+ - pg_engine/spec/helpers/pg_engine/pg_rails_helper_spec.rb
129
+ - pg_engine/spec/lib/pg_engine/error_helper_spec.rb
130
+ - pg_engine/spec/lib/pg_engine/utils/pg_engine/pg_logger_spec.rb
127
131
  - pg_engine/spec/lib/pg_form_builder_spec.rb
128
132
  - pg_engine/spec/models/account_spec.rb
129
133
  - pg_engine/spec/models/pg_engine/base_record_spec.rb
130
134
  - pg_engine/spec/models/user_account_spec.rb
131
135
  - pg_engine/spec/models/user_spec.rb
132
136
  - pg_engine/spec/pg_engine/pdf_preview_generator_spec.rb
137
+ - pg_layout/app/assets/stylesheets/animations.scss
133
138
  - pg_layout/app/assets/stylesheets/sidebar.scss
134
139
  - pg_layout/app/javascript/cookies.js
140
+ - pg_layout/app/javascript/fadein_onload_controller.js
135
141
  - pg_layout/app/javascript/navbar_controller.js
136
142
  - pg_layout/app/javascript/nested_controller.js
137
143
  - pg_layout/app/javascript/pg_form_controller.js
@@ -162,6 +168,7 @@ files:
162
168
  - pg_layout/app/views/layouts/pg_layout/layout.html.slim
163
169
  - pg_layout/app/views/pg_layout/_flash.html.slim
164
170
  - pg_layout/app/views/pg_layout/_navbar.html.erb
171
+ - pg_layout/app/views/pg_layout/_rollbar.html.erb
165
172
  - pg_layout/app/views/pg_layout/_sidebar.html.erb
166
173
  - pg_layout/index.js
167
174
  - pg_layout/lib/pg_layout.rb
@@ -169,6 +176,7 @@ files:
169
176
  - pg_rails/js/index.js
170
177
  - pg_rails/lib/pg_rails.rb
171
178
  - pg_rails/lib/pg_rails/capybara_support.rb
179
+ - pg_rails/lib/pg_rails/vcr_support.rb
172
180
  - pg_rails/lib/version.rb
173
181
  - pg_rails/scss/pg_rails.scss
174
182
  - pg_scaffold/lib/generators/pg_active_record/model/model_generator.rb