Module: DutyFree::Extensions
- Defined in:
- lib/duty_free/extensions.rb
Overview
rubocop:disable Style/CommentedKeyword
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- MAX_ID =
Arel.sql('MAX(id)')
- IS_AMOEBA =
Gem.loaded_specs['amoeba']
Class Method Summary collapse
- ._fk_from(assoc) ⇒ Object
-
._recurse_def(klass, cols, import_template, build_tables = nil, order_by = nil, assocs = [], joins = [], pre_prefix = '', prefix = '') ⇒ Object
Recurse and return three arrays – one with all columns in sequence, and one a hierarchy of nested hashes to be used with ActiveRecord’s .joins() to facilitate export, and finally one that lists tables that need to be built along the way.
-
._save_pending(to_be_saved) ⇒ Object
Called before building any object linked through a has_one or has_many so that foreign key IDs can be added properly to those new objects.
-
._template_columns(klass, import_template = nil) ⇒ Object
The snake-cased column alias names used in the query to export data.
-
.import(obj_klass, data, import_template = nil, insert_only) ⇒ Object
With an array of incoming data, the first row having column names, perform the import.
- .included(base) ⇒ Object
Class Method Details
._fk_from(assoc) ⇒ Object
819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 |
# File 'lib/duty_free/extensions.rb', line 819 def self._fk_from(assoc) # Try first to trust whatever they've marked as being the foreign_key, and then look # at the inverse's foreign key setting if available. In all cases don't accept # anything that's not backed with a real column in the table. col_names = assoc.klass.column_names if ( (fk_name = assoc.[:foreign_key]) || (fk_name = assoc.inverse_of&.&.fetch(:foreign_key) { nil }) || (assoc.respond_to?(:foreign_key) && (fk_name = assoc.foreign_key)) || (fk_name = assoc.inverse_of&.foreign_key) || (fk_name = assoc.inverse_of&.association_foreign_key) ) && col_names.include?(fk_name.to_s) return fk_name end # Don't let this fool you -- we really are in search of the foreign key name here, # and Rails 3.0 and older used some fairly interesting conventions, calling it instead # the "primary_key_name"! if assoc.respond_to?(:primary_key_name) if (fk_name = assoc.primary_key_name) && col_names.include?(fk_name.to_s) return fk_name end if (fk_name = assoc.inverse_of.primary_key_name) && col_names.include?(fk_name.to_s) return fk_name end end puts "* Wow, had no luck at all finding a foreign key for #{assoc.inspect}" end |
._recurse_def(klass, cols, import_template, build_tables = nil, order_by = nil, assocs = [], joins = [], pre_prefix = '', prefix = '') ⇒ Object
Recurse and return three arrays – one with all columns in sequence, and one a hierarchy of nested hashes to be used with ActiveRecord’s .joins() to facilitate export, and finally one that lists tables that need to be built along the way.
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 |
# File 'lib/duty_free/extensions.rb', line 914 def self._recurse_def(klass, cols, import_template, build_tables = nil, order_by = nil, assocs = [], joins = [], pre_prefix = '', prefix = '') prefix = prefix[0..-2] if (is_build_table = prefix.end_with?('!')) prefixes = ::DutyFree::Util._prefix_join([pre_prefix, prefix]) if prefix.present? # An indication to build this table and model if it doesn't exist? if is_build_table && build_tables namespaces = prefix.split('::') model_name = namespaces.map { |part| part.singularize.camelize }.join('::') prefix = namespaces.last # %%% If the model already exists, take belongs_to cues from it for building the table if (is_need_model = Object.const_defined?(model_name)) && (is_need_table = ActiveRecord::ConnectionAdapters::SchemaStatements.table_exists?( (path_name = ::DutyFree::Util._prefix_join([prefixes, prefix.pluralize])) )) is_build_table = false else build_tables[path_name] = [namespaces, is_need_model, is_need_table] unless build_tables.include?(path_name) end end unless is_build_table && build_tables # Confirm we can actually navigate through this association prefix_assoc = (assocs.last&.klass || klass).reflect_on_association(prefix) if prefix.present? if prefix_assoc assocs = assocs.dup << prefix_assoc if order_by && [:has_many, :has_and_belongs_to_many].include?(prefix_assoc.macro) && (pk = prefix_assoc.active_record.primary_key) order_by << ["#{prefixes.tr('.', '_')}_", pk] end end end end cols = cols.inject([]) do |s, col| s + if col.is_a?(Hash) col.inject([]) do |s2, v| if order_by # Find what the type is for this guy next_klass = (assocs.last&.klass || klass).reflect_on_association(v.first)&.klass # Used to be: { v.first.to_sym => (joins_array = []) } joins << { v.first.to_sym => (joins_array = [next_klass]) } end s2 + _recurse_def(klass, (v.last.is_a?(Array) ? v.last : [v.last]), import_template, build_tables, order_by, assocs, joins_array, prefixes, v.first.to_sym).first end elsif col.nil? if assocs.empty? [] else # Bring in from another class # Used to be: { prefix => (joins_array = []) } # %%% Probably need a next_klass thing like above joins << { prefix => (joins_array = [klass]) } if order_by # %%% Also bring in uniques and requireds _recurse_def(klass, assocs.last.klass::IMPORT_TEMPLATE[:all], import_template, build_tables, order_by, assocs, joins_array, prefixes).first end else [::DutyFree::Column.new(col, pre_prefix, prefix, assocs, klass, import_template[:as])] end end [cols, joins] end |
._save_pending(to_be_saved) ⇒ Object
Called before building any object linked through a has_one or has_many so that foreign key IDs can be added properly to those new objects. Finally at the end also called to save everything.
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 |
# File 'lib/duty_free/extensions.rb', line 854 def self._save_pending(to_be_saved) while (tbs = to_be_saved.pop) # puts "Will be calling #{tbs[1].class.name} #{tbs[1]&.id} .#{tbs[2]} #{tbs[3].class.name} #{tbs[3]&.id}" # Wire this one up if it had come from a has_one if tbs[0] == tbs[1] tbs[1].class.has_one(tbs[2]) unless tbs[1].respond_to?(tbs[2]) tbs[1].send(tbs[2], tbs[3]) end ais = (tbs.first.class.respond_to?(:around_import_save) && tbs.first.class.method(:around_import_save)) || (respond_to?(:around_import_save) && method(:around_import_save)) if ais # Send them the sub_obj even if it might be invalid so they can choose # to make it valid if they wish. ais.call(tbs.first) do |modded_obj = nil| modded_obj = (modded_obj || tbs.first) modded_obj.save if modded_obj&.valid? end elsif tbs.first.valid? tbs.first.save else puts "* Unable to save #{tbs.first.inspect}" end next if tbs[1].nil? || # From a has_many? tbs[0] == tbs[1] || # From a has_one? tbs.first.new_record? if tbs[1] == :has_and_belongs_to_many # also used by has_many :through associations collection = tbs[3].send(tbs[2]) being_shoveled_id = tbs[0].send(tbs[0].class.primary_key) if collection.empty? || !collection.pluck("#{(klass = collection.first.class).table_name}.#{klass.primary_key}").include?(being_shoveled_id) collection << tbs[0] # puts collection.inspect end else # Traditional belongs_to tbs[1].send(tbs[2], tbs[3]) end end end |
._template_columns(klass, import_template = nil) ⇒ Object
The snake-cased column alias names used in the query to export data
897 898 899 900 901 902 903 904 905 906 907 908 909 |
# File 'lib/duty_free/extensions.rb', line 897 def self._template_columns(klass, import_template = nil) template_detail_columns = klass.instance_variable_get(:@template_detail_columns) if klass.instance_variable_get(:@template_import_columns) != import_template klass.instance_variable_set(:@template_import_columns, import_template) klass.instance_variable_set(:@template_detail_columns, (template_detail_columns = nil)) end unless template_detail_columns # puts "* Redoing *" template_detail_columns = _recurse_def(klass, import_template[:all], import_template).first.map(&:to_sym) klass.instance_variable_set(:@template_detail_columns, template_detail_columns) end template_detail_columns end |
.import(obj_klass, data, import_template = nil, insert_only) ⇒ Object
With an array of incoming data, the first row having column names, perform the import
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 |
# File 'lib/duty_free/extensions.rb', line 421 def self.import(obj_klass, data, import_template = nil, insert_only) instance_variable_set(:@defined_uniques, nil) instance_variable_set(:@valid_uniques, nil) import_template ||= if obj_klass.constants.include?(:IMPORT_TEMPLATE) obj_klass::IMPORT_TEMPLATE else obj_klass.suggest_template(0, false, false) end # puts "Chose #{import_template}" inserts = [] updates = [] counts = Hash.new { |h, k| h[k] = [] } errors = [] is_first = true uniques = nil cols = nil starred = [] partials = [] # See if we can find the model if given only a string if obj_klass.is_a?(String) obj_klass_camelized = obj_klass.camelize.singularize obj_klass = Object.const_get(obj_klass_camelized) if Object.const_defined?(obj_klass_camelized) end table_name = obj_klass.table_name unless obj_klass.is_a?(String) is_build_table = if import_template.include?(:all!) # Search for presence of this table table_name = obj_klass.underscore.pluralize if obj_klass.is_a?(String) !ActiveRecord::ConnectionAdapters::SchemaStatements.table_exists?(table_name) else false end # If we do need to build the table then we require an :all!, otherwise in the case that the table # does not need to be built or was already built then try :all, and fall back on :all!. all = import_template[is_build_table ? :all! : :all] || import_template[:all!] keepers = {} valid_unique = nil existing = {} devise_class = '' ret = nil # Multi-tenancy gem Apartment can be used if there are separate schemas per tenant reference_models = if Object.const_defined?('Apartment') Apartment.excluded_models else [] end if Object.const_defined?('Devise') Object.const_get('Devise') # If this fails, devise_class will remain a blank string. devise_class = Devise.mappings.values.first.class_name reference_models -= [devise_class] else devise_class = '' end # Did they give us a filename? if data.is_a?(String) # Filenames with full paths can not be longer than 4096 characters, and can not # include newline characters data = if data.length <= 4096 && !data.index('\n') File.open(data) else # Any multi-line string is likely CSV data # %%% Test to see if TAB characters are present on the first line, instead of commas CSV.new(data) end end # Or perhaps us a file? if data.is_a?(File) # Use the "roo" gem if it's available # When we're ready to try parsing this thing on our own, shared strings and all, then use # the rubyzip gem also along with it: # https://github.com/rubyzip/rubyzip # require 'zip' data = if Object.const_defined?('Roo::Spreadsheet', { csv_options: { encoding: 'bom|utf-8' } }) Roo::Spreadsheet.open(data) else # Otherwise generic CSV parsing require 'csv' unless Object.const_defined?('CSV') CSV.open(data) end end # Will show as just one transaction when using auditing solutions such as PaperTrail ActiveRecord::Base.transaction do # Check to see if they want to do anything before the whole import # First if defined in the import_template, then if there is a method in the class, # and finally (not yet implemented) a generic global before_import my_before_import = import_template[:before_import] my_before_import ||= respond_to?(:before_import) && method(:before_import) # my_before_import ||= some generic my_before_import if my_before_import last_arg_idx = my_before_import.parameters.length - 1 arguments = [data, import_template][0..last_arg_idx] data = ret if (ret = my_before_import.call(*arguments)).is_a?(Enumerable) end build_tables = nil data.each_with_index do |row, row_num| row_errors = {} if is_first # Anticipate that first row has column names uniques = import_template[:uniques] # Look for UTF-8 BOM in very first cell row[0] = row[0][3..-1] if row[0].start_with?([239, 187, 191].pack('U*')) # How about a first character of FEFF or FFFE to support UTF-16 BOMs? # FE FF big-endian (standard) # FF FE little-endian row[0] = row[0][1..-1] if [65_279, 65_534].include?(row[0][0].ord) cols = row.map { |col| (col || '').strip } # Unique column combinations can be called out explicitly in models using uniques: {...}, or to just # define one column at a time simply mark with an asterisk. # Track and clean up stars starred = cols.select do |col| if col[0] == '*' col.slice!(0) col.strip! end end partials = cols.select do |col| if col[0] == '~' col.slice!(0) col.strip! end end cols.map! { |col| ::DutyFree::Util._clean_name(col, import_template[:as]) } obj_klass.send(:_defined_uniques, uniques, cols, cols.join('|'), starred) # Main object asking for table to be built? build_tables = {} build_tables[path_name] = [namespaces, is_need_model, is_need_table] if is_build_table # Make sure that at least half of them match what we know as being good column names template_column_objects = ::DutyFree::Extensions._recurse_def(obj_klass, import_template[:all], import_template, build_tables).first cols.each_with_index do |col, idx| # prefixes = col_detail.pre_prefix + (col_detail.prefix.blank? ? [] : [col_detail.prefix]) # %%% Would be great here if when one comes back nil, try to find the closest match # and indicate to the user a "did you mean?" about it. keepers[idx] = template_column_objects.find { |col_obj| col_obj.titleize == col } # puts "Could not find a match for column #{idx + 1}, #{col}" if keepers[idx].nil? end raise ::DutyFree::LessThanHalfAreMatchingColumnsError, I18n.t('import.altered_import_template_coumns') if keepers.length < (cols.length / 2) - 1 # Returns just the first valid unique lookup set if there are multiple valid_unique, bt_criteria = obj_klass.send(:_find_existing, uniques, cols, starred, import_template, keepers, false, insert_only) # Make a lookup from unique values to specific IDs existing = obj_klass.pluck(*([:id] + valid_unique.keys)).each_with_object(existing) do |v, s| s[v[1..-1].map(&:to_s)] = v.first s end is_first = false else # Normal row of data is_insert = false existing_unique = valid_unique.inject([]) do |s, v| s << if v.last.is_a?(Array) v.last[0].where(v.last[1] => row[v.last[2]]).limit(1).pluck(MAX_ID).first.to_s else row[v.last].to_s end end to_be_saved = [] # Check to see if they want to preprocess anything existing_unique = @before_process.call(valid_unique, existing_unique) if @before_process ||= import_template[:before_process] obj = if (criteria = existing[existing_unique]) obj_klass.find(criteria) else is_insert = true # unless build_tables.empty? # include?() # binding.pry # x = 5 # end obj_klass.new end to_be_saved << [obj] unless criteria # || this one has any belongs_to that will be modified here sub_obj = nil polymorphics = [] sub_objects = {} this_path = nil keepers.each do |key, v| next if v.nil? sub_obj = obj this_path = +'' # puts "p: #{v.path}" v.path.each_with_index do |path_part, idx| this_path << (this_path.blank? ? path_part.to_s : ",#{path_part}") unless (sub_next = sub_objects[this_path]) # Check if we're hitting reference data / a lookup thing assoc = v.prefix_assocs[idx] # belongs_to some lookup (reference) data if assoc && reference_models.include?(assoc.class_name) lookup_match = assoc.klass.find_by(v.name => row[key]) # Do a partial match if this column allows for it # and we only find one matching result. if lookup_match.nil? && partials.include?(v.titleize) lookup_match ||= assoc.klass.where("#{v.name} LIKE '#{row[key]}%'") lookup_match = (lookup_match.length == 1 ? lookup_match.first : nil) end sub_obj.send("#{path_part}=", lookup_match) unless lookup_match.nil? # Reference data from the public level means we stop here sub_obj = nil break end # Get existing related object, or create a new one # This first part works for belongs_to. has_many and has_one get sorted below. # start = (v.pre_prefix.blank? ? 0 : v.pre_prefix.length) start = 0 trim_prefix = v.titleize[start..-(v.name.length + 2)] trim_prefix << ' ' unless trim_prefix.blank? if assoc.belongs_to? klass = Object.const_get(assoc&.class_name) # Try to find a unique item if one is referenced sub_next = nil begin sub_next, criteria, bt_criteria = klass.send(:_find_existing, uniques, cols, starred, import_template, keepers, nil, false, # insert_only row, klass, all, trim_prefix, assoc) rescue ::DutyFree::NoUniqueColumnError end bt_name = "#{path_part}=" # Not yet wired up to the right one, or going to the parent of a self-referencing model? # puts "#{v.path} #{criteria.inspect}" unless sub_next || (klass == sub_obj.class && (all_criteria = criteria.merge(bt_criteria)).empty?) sub_next = klass.new(all_criteria || {}) to_be_saved << [sub_next, sub_obj, bt_name, sub_next] end # This wires it up in memory, but doesn't yet put the proper foreign key ID in # place when the primary object is a new one (and thus not having yet been saved # doesn't yet have an ID). # binding.pry if !sub_next.new_record? && sub_next.name == 'Squidget Widget' # !sub_obj.changed? # # %%% Question is -- does the above set changed? when a foreign key is not yet set # # and only the in-memory object has changed? is_yet_changed = sub_obj.changed? sub_obj.send(bt_name, sub_next) # We need this in the case of updating the primary object across a belongs_to when # the foreign one already exists and is not otherwise changing, such as when it is # not a new one, so wouldn't otherwise be getting saved. to_be_saved << [sub_obj] if !sub_obj.new_record? && !is_yet_changed && sub_obj.changed? # From a has_many or has_one? # Rails 4.0 and later can do: sub_next.is_a?(ActiveRecord::Associations::CollectionProxy) elsif [:has_many, :has_one, :has_and_belongs_to_many].include?(assoc.macro) # && !assoc.options[:through] ::DutyFree::Extensions._save_pending(to_be_saved) sub_next = sub_obj.send(path_part) # Try to find a unique item if one is referenced # %%% There is possibility that when bringing in related classes using a nil # in IMPORT_TEMPLATE[:all] that this will break. Need to test deeply nested things. # assoc.inverse_of is the belongs_to side of the has_many train we came in here on. sub_hm, criteria, bt_criteria = assoc.klass.send(:_find_existing, uniques, cols, starred, import_template, keepers, assoc.inverse_of, false, # insert_only row, sub_next, all, trim_prefix, assoc, # Just in case we're running Rails < 4.0 and this is a has_* sub_obj) # If still not found then create a new related object using this has_many collection # (criteria.empty? ? nil : sub_next.new(criteria)) if sub_hm sub_next = sub_hm elsif assoc.macro == :has_one # assoc.active_record.name.underscore is only there to support older Rails # that doesn't do automatic inverse_of ho_name = "#{assoc.inverse_of&.name || assoc.active_record.name.underscore}=" sub_next = assoc.klass.new(criteria) to_be_saved << [sub_next, sub_next, ho_name, sub_obj] elsif assoc.macro == :has_and_belongs_to_many || (assoc.macro == :has_many && assoc.[:through]) # sub_next = sub_next.new(criteria) # Search for one to wire up if it might already exist, otherwise create one sub_next = assoc.klass.find_by(criteria) || assoc.klass.new(criteria) to_be_saved << [sub_next, :has_and_belongs_to_many, assoc.name, sub_obj] else # Two other methods that are possible to check for here are :conditions and # :sanitized_conditions, which do not exist in Rails 4.0 and later. sub_next = if assoc.respond_to?(:require_association) # With Rails < 4.0, sub_next could come in as an Array or a broken CollectionProxy assoc.klass.new({ ::DutyFree::Extensions._fk_from(assoc) => sub_obj.send(sub_obj.class.primary_key) }.merge(criteria)) else sub_next.proxy_association.reflection.instance_variable_set(:@foreign_key, ::DutyFree::Extensions._fk_from(assoc)) sub_next.new(criteria) end to_be_saved << [sub_next] end # else # belongs_to for a found object, or a HMT end # # Incompatible with Rails < 4.2 # # Look for possible missing polymorphic detail # # Maybe can test for this via assoc.through_reflection # if assoc.is_a?(ActiveRecord::Reflection::ThroughReflection) && # (delegate = assoc.send(:delegate_reflection)&.active_record&.reflect_on_association(assoc.source_reflection_name)) && # delegate.options[:polymorphic] # polymorphics << { parent: sub_next, child: sub_obj, type_col: delegate.foreign_type, id_col: delegate.foreign_key.to_s } # end # rubocop:disable Style/SoleNestedConditional unless sub_next.nil? # if sub_next.class.name == devise_class && # only for Devise users # sub_next.email =~ Devise.email_regexp # if existing.include?([sub_next.email]) # User already exists # else # sub_next.invite! # end # end sub_objects[this_path] = sub_next if this_path.present? end # rubocop:enable Style/SoleNestedConditional end sub_obj = sub_next end next if sub_obj.nil? next unless sub_obj.respond_to?(sym = "#{v.name}=".to_sym) col_type = sub_obj.class.columns_hash[v.name.to_s]&.type if col_type.nil? && (virtual_columns = import_template[:virtual_columns]) && (virtual_columns = virtual_columns[this_path] || virtual_columns) col_type = virtual_columns[v.name] end if col_type == :boolean if row[key].nil? # Do nothing when it's nil elsif %w[true t yes y].include?(row[key]&.strip&.downcase) # Used to cover 'on' row[key] = true elsif %w[false f no n].include?(row[key]&.strip&.downcase) # Used to cover 'off' row[key] = false else row_errors[v.name] ||= [] row_errors[v.name] << "Boolean value \"#{row[key]}\" in column #{key + 1} not recognized" end end is_yet_changed = sub_obj.changed? sub_obj.send(sym, row[key]) # If this one is transitioning from having not changed to now having been changed, # and is not a new one anyway that would already be lined up to get saved, then we # mark it to now be saved. to_be_saved << [sub_obj] if !sub_obj.new_record? && !is_yet_changed && sub_obj.changed? # else # puts " #{sub_obj.class.name} doesn't respond to #{sym}" end # Try to save final sub-object(s) if any exist ::DutyFree::Extensions._save_pending(to_be_saved) # Reinstate any missing polymorphic _type and _id values polymorphics.each do |poly| if !poly[:parent].new_record? || poly[:parent].save poly[:child].send("#{poly[:type_col]}=".to_sym, poly[:parent].class.name) poly[:child].send("#{poly[:id_col]}=".to_sym, poly[:parent].id) end end # Give a window of opportunity to tweak user objects controlled by Devise obj_class = obj.class is_do_save = if obj_class.respond_to?(:before_devise_save) && obj_class.name == devise_class obj_class.before_devise_save(obj, existing) else true end if obj.valid? obj.save if is_do_save # Snag back any changes to the unique columns. (For instance, Devise downcases email addresses.) existing_unique = valid_unique.keys.inject([]) { |s, v| s << obj.send(v).to_s } # Update the duplicate counts and inserted / updated results counts[existing_unique] << row_num (is_insert ? inserts : updates) << { row_num => existing_unique } if is_do_save # Track this new object so we can properly sense any duplicates later existing[existing_unique] = obj.id else row_errors.merge! obj.errors. end errors << { row_num => row_errors } unless row_errors.empty? end end duplicates = counts.each_with_object([]) do |v, s| s += v.last[1..-1].map { |line_num| { line_num => v.first } } if v.last.count > 1 s end ret = { inserted: inserts, updated: updates, duplicates: duplicates, errors: errors } # Check to see if they want to do anything after the import # First if defined in the import_template, then if there is a method in the class, # and finally (not yet implemented) a generic global after_import my_after_import = import_template[:after_import] my_after_import ||= respond_to?(:after_import) && method(:after_import) # my_after_import ||= some generic my_after_import if my_after_import last_arg_idx = my_after_import.parameters.length - 1 arguments = [ret][0..last_arg_idx] # rubocop:disable Lint/UselessAssignment ret = ret2 if (ret2 = my_after_import.call(*arguments)).is_a?(Hash) # rubocop:enable Lint/UselessAssignment end end ret end |
.included(base) ⇒ Object
14 15 16 17 |
# File 'lib/duty_free/extensions.rb', line 14 def self.included(base) base.send :extend, ClassMethods base.send :extend, ::DutyFree::SuggestTemplate::ClassMethods end |