reaction.rb 7.12 KB
Newer Older
1
class Reaction < ActiveRecord::Base
2
  acts_as_paranoid
3
  include ElementUIStateScopes
4
5
  include PgSearch
  include Collectable
6
  include ElementCodes
An Nguyen's avatar
An Nguyen committed
7
  include Taggable
8

9
  serialize :temperature, Hash
An Nguyen's avatar
An Nguyen committed
10
  serialize :description, Hash
11
  serialize :observation, Hash
12

13
  multisearchable against: :name
14
  multisearchable against: :short_label
15
16

  # search scopes for exact matching
17
  pg_search_scope :search_by_reaction_name, against: :name
18
  pg_search_scope :search_by_reaction_short_label, against: :short_label
19

20
21
22
  pg_search_scope :search_by_sample_name, associated_against: {
    starting_materials: :name,
    reactants: :name,
23
    solvents: :name,
24
25
26
27
28
29
    products: :name
  }

  pg_search_scope :search_by_iupac_name, associated_against: {
    starting_material_molecules: :iupac_name,
    reactant_molecules: :iupac_name,
30
    solvent_molecules: :iupac_name,
31
32
33
    product_molecules: :iupac_name
  }

CamAnNguyen's avatar
CamAnNguyen committed
34
35
36
  pg_search_scope :search_by_inchistring, associated_against: {
    starting_material_molecules: :inchistring,
    reactant_molecules: :inchistring,
37
    solvent_molecules: :inchistring,
CamAnNguyen's avatar
CamAnNguyen committed
38
39
40
41
42
43
    product_molecules: :inchistring
  }

  pg_search_scope :search_by_cano_smiles, associated_against: {
    starting_material_molecules: :cano_smiles,
    reactant_molecules: :cano_smiles,
44
    solvent_molecules: :cano_smiles,
CamAnNguyen's avatar
CamAnNguyen committed
45
46
47
    product_molecules: :cano_smiles
  }

48
49
50
51
  pg_search_scope :search_by_substring, against: :name,
                                        associated_against: {
                                          starting_materials: :name,
                                          reactants: :name,
52
                                          solvents: :name,
53
54
55
                                          products: :name,
                                          starting_material_molecules: :iupac_name,
                                          reactant_molecules: :iupac_name,
56
                                          solvent_molecules: :iupac_name,
57
58
59
60
61
                                          product_molecules: :iupac_name
                                        },
                                        using: {trigram: {threshold:  0.0001}}

  # scopes for suggestions
62
  scope :by_name, ->(query) { where('name ILIKE ?', "%#{query}%") }
63
  scope :by_short_label, ->(query) { where('short_label ILIKE ?', "%#{query}%") }
64
  scope :by_material_ids, ->(ids) { joins(:starting_materials).where('samples.id IN (?)', ids) }
65
  scope :by_solvent_ids, ->(ids) { joins(:solvents).where('samples.id IN (?)', ids) }
66
  scope :by_reactant_ids, ->(ids) { joins(:reactants).where('samples.id IN (?)', ids) }
67
  scope :by_product_ids,  ->(ids) { joins(:products).where('samples.id IN (?)', ids) }
68

69
  has_many :collections_reactions, dependent: :destroy
70
  has_many :collections, through: :collections_reactions
71
  accepts_nested_attributes_for :collections_reactions
72

73
  has_many :reactions_starting_material_samples, dependent: :destroy
74
  has_many :starting_materials, through: :reactions_starting_material_samples, source: :sample
75
  has_many :starting_material_molecules, through: :starting_materials, source: :molecule
76

77
78
79
80
  has_many :reactions_solvent_samples, dependent: :destroy
  has_many :solvents, through: :reactions_solvent_samples, source: :sample
  has_many :solvent_molecules, through: :solvents, source: :molecule

81
  has_many :reactions_reactant_samples, dependent: :destroy
82
  has_many :reactants, through: :reactions_reactant_samples, source: :sample
83
  has_many :reactant_molecules, through: :reactants, source: :molecule
84

85
  has_many :reactions_product_samples, dependent: :destroy
86
  has_many :products, through: :reactions_product_samples, source: :sample
87
  has_many :product_molecules, through: :products, source: :molecule
88

89
  has_many :literatures, dependent: :destroy
90

91
92
  has_many :sync_collections_users, through: :collections

An Nguyen's avatar
An Nguyen committed
93
94
95
  belongs_to :creator, foreign_key: :created_by, class_name: 'User'
  validates :creator, presence: true

Marco Sehrer's avatar
Marco Sehrer committed
96
  before_save :update_svg_file!
97
  before_save :cleanup_array_fields
Fernando D'Agostino's avatar
Fernando D'Agostino committed
98
  before_save :auto_format_temperature!
99
  before_create :auto_set_short_label
An Nguyen's avatar
An Nguyen committed
100
101

  after_create :update_counter
Marco Sehrer's avatar
Marco Sehrer committed
102

jpotthoff's avatar
jpotthoff committed
103
  has_one :container, :as => :containable
jpotthoff's avatar
jpotthoff committed
104

105
106
107
108
109
110
111
  def self.get_associated_samples(reaction_ids)
    ( ReactionsProductSample.get_samples(reaction_ids) +
      ReactionsStartingMaterialSample.get_samples(reaction_ids) +
      ReactionsReactantSample.get_samples(reaction_ids)
    ).compact
  end

112
  def samples
113
    starting_materials + reactants + products + solvents
114
  end
115

116
117
118
  def analyses
    self.container ? self.container.analyses : []
  end
Marco Sehrer's avatar
Marco Sehrer committed
119

Fernando D'Agostino's avatar
Fernando D'Agostino committed
120
  def auto_format_temperature!
121
122
123
124
125
126
127
    valueUnitCheck = (temperature["valueUnit"] =~ /^(°C|°F|K)$/).present?
    temperature["valueUnit"] = "°C" if (!valueUnitCheck)

    temperature["data"].each do |t|
      valid_time = (t["time"] =~ /^((?:\d\d):[0-5]\d:[0-5]\d$)/i).present?
      t["time"] = "00:00:00" if (!valid_time)
      t["value"] = t["value"].gsub(/[^0-9.-]/, '')
128
129
130
    end
  end

131
132
133
134
135
136
137
138
139
140
141
  def temperature_display
    userText = temperature["userText"]
    return userText if (userText != "")

    return "" if (temperature["data"].length == 0)

    arrayData = temperature["data"]
    maxTemp = (arrayData.max_by { |x| x["value"] })["value"]
    minTemp = (arrayData.min_by { |x| x["value"] })["value"]

    return ""  if (minTemp == nil || maxTemp == nil)
142
    return minTemp + " ~ " + maxTemp
143
144
  end

jasonych99's avatar
jasonych99 committed
145
146
  def temperature_display_with_unit
    tp = temperature_display
147
    tp.length != 0 ? tp + " " + temperature["valueUnit"] : ""
jasonych99's avatar
jasonych99 committed
148
149
  end

An Nguyen's avatar
An Nguyen committed
150
151
152
153
  def description_contents
    return description["ops"].map{|s| s["insert"]}.join()
  end

154
155
156
157
  def observation_contents
    return observation["ops"].map{|s| s["insert"]}.join()
  end

Marco Sehrer's avatar
Marco Sehrer committed
158
  def update_svg_file!
Serhii Kotov's avatar
Serhii Kotov committed
159
160
161
    paths = {}
    %i(starting_materials reactants products).each do |prop|
      d = self.send(prop).includes(:molecule)
162
163
      paths[prop]= d.pluck(:id, :sample_svg_file, :'molecules.molecule_svg_file').map do |item|
        prop == :products ? [svg_path(item[1], item[2]), yield_amount(item[0])] : svg_path(item[1], item[2])
Serhii Kotov's avatar
Serhii Kotov committed
164
165
166
      end
    end

Marco Sehrer's avatar
Marco Sehrer committed
167
    begin
jasonych99's avatar
jasonych99 committed
168
      composer = SVG::ReactionComposer.new(paths, temperature: temperature_display_with_unit,
169
170
                                                  solvents: solvents_in_svg,
                                                  show_yield: true)
Marco Sehrer's avatar
Marco Sehrer committed
171
172
      self.reaction_svg_file = composer.compose_reaction_svg_and_save
    rescue Exception => e
173
      Rails.logger.info("**** SVG::ReactionComposer failed ***")
Marco Sehrer's avatar
Marco Sehrer committed
174
    end
Marco Sehrer's avatar
Marco Sehrer committed
175
176
  end

177
178
179
180
181
182
183
184
  def svg_path(sample_svg, molecule_svg)
    sample_svg.present? ? "/images/samples/#{sample_svg}" : "/images/molecules/#{molecule_svg}"
  end

  def yield_amount(sample_id)
    ReactionsProductSample.find_by(reaction_id: self.id, sample_id: sample_id).try(:equivalent)
  end

185
186
187
188
189
  def solvents_in_svg
    names = solvents.map{ |s| s.preferred_tag }
    return names && names.length > 0 ? names : [solvent]
  end

190
191
192
193
  def cleanup_array_fields
    self.dangerous_products = dangerous_products.reject(&:blank?)
    self.purification = purification.reject(&:blank?)
  end
An Nguyen's avatar
An Nguyen committed
194
195

  def auto_set_short_label
Serhii Kotov's avatar
Serhii Kotov committed
196
197
198
    prefix = creator.reaction_name_prefix
    counter = creator.counters['reactions'].succ
    self.short_label = "#{creator.initials}-#{prefix}#{counter}"
An Nguyen's avatar
An Nguyen committed
199
200
201
202
203
  end

  def update_counter
    self.creator.increment_counter 'reactions'
  end
204
end