lca 0.2.1 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9484c550eb1523766573047c009f2081526636c4918aeb29609fbc40885990b1
4
- data.tar.gz: cd3266a10c9238189d327e7f92928c7c734094ae23d8dca999beb12342a13c75
3
+ metadata.gz: 62afb1561f978b69743ea334e439cc449375f0b4bc1cad29601b174a5d231a41
4
+ data.tar.gz: 1f18a6226d1fee18f3e2d226616c34dd3cae9ba99389ef9ffb17cf828c586e34
5
5
  SHA512:
6
- metadata.gz: 6858541887aae952fad81721253a4b51cf64d2a880dd8cc311d4afc1b672ae7938331fbd9e1d2bbcd77d8d860db2bb4dbf2e438271db4b8ce0843b7b5206ca55
7
- data.tar.gz: 9bf817a23784db18f4c60df38087c3a4409c938ee49d4634270a5aaaed0b50be0182a60ad03ddcba904e6964d65580b6d5ef4fe3c52de4698fdc85ac2297f511
6
+ metadata.gz: e190b6449f86a075d6582276747f6f17b1d617cf404f1db04ef7600f7ac9a84490d60535901a9b1e3fc212fb855b381fb80f78d583db391a159b27c75b63e9a3
7
+ data.tar.gz: b2fc67ead55300e549192c8f1b37e2971e11bc4da9ed08956ea46a601fc6aba1596a56106822f1b085e2c82d94560975bcc301ef8df3946121a331ddcd925a7f
data/Gemfile CHANGED
@@ -9,4 +9,7 @@ gem "rspec", "~> 3.0"
9
9
 
10
10
  gem "activerecord", "~> 6.0"
11
11
  gem "activestorage", "~> 6.0"
12
- gem "actiontext", "~> 6.0"
12
+ gem "actiontext", "~> 6.0"
13
+
14
+ gem "jwt"
15
+ gem "pg_ltree"
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Storing, processing and working with life-cycle assessment data has always been challenging. A multitude of data models and implementations exist already but every one of them makes huge compromises or lacks functionality.
6
6
 
7
- This gem implements a denormalized database model for life-cycle assessment data as well as several innovative query interfaces.
7
+ This gem implements a denormalized database model for life-cycle assessment data as well as several vectors for convenient querying.
8
8
 
9
9
  ## Installation
10
10
 
@@ -42,42 +42,42 @@ Include the LCA concern into one of your models which will own lifecycle trees (
42
42
  This will extend your model and enable the following functionality:
43
43
 
44
44
  ```ruby
45
- # query with ActiveRecord syntax
46
- User.last.lca_cycles
47
- User.find_by(name: "Acme").lca_cycles.impacts.select("impact_unit, sum(impact_amount) as total_impact").group(:impact_unit)
48
- User.last.lca_cycles.where(...)
49
- User.last.lca_cycles.where(...).group(...)
50
- User.last.lca_cycles.where(...).limit(...).offset(...)
45
+ # query with ActiveRecord syntax
46
+ User.last.lca_cycles
47
+ User.find_by(name: "Acme").lca_cycles.impacts.select("impact_unit, sum(impact_amount) as total_impact").group(:impact_unit)
48
+ User.last.lca_cycles.where(...)
49
+ User.last.lca_cycles.where(...).group(...)
50
+ User.last.lca_cycles.where(...).limit(...).offset(...)
51
51
 
52
- # AR query with ltree syntax
53
- User.last.lca_cycles.disabled.match_path("*.CustomProcess.*")
54
- User.last.lca_cycles.match_path("*{5,10}.CustomProcess.*.Ecosphere.*")
55
- User.last.lca_cycles.active.match_path("*.WhateverProcess.*.Ecosphere.*.CO2Emission.*").where( impact_amount: [100..150]).sum(:impact_amount)
56
-
57
- Lca::Cycle.match_path("Top.Electric Vehicle.*").impacts.match_path("*.CO2*").where( impact_amount: [ 1000..10000 ]).average(:impact_amount)
58
- Lca::Cycle::Product.where(name: "Electric Vehicle Battery").impacts.match_path("*.Cobalt.*").sum(:impact_amount)
59
- Lca::Process.where(owner: User.last).match_path("*{10,20}.*Assembly, automated.*")
60
- Lca::Impact.match_path("*.Transport by truck.*")
61
- Lca::Exchange.match_path("*.Oil.*.Ecosphere.*").impacts.sum(:impact_amount)
62
- Lca::Product.match_path("*.ElectricVehicle.*").processes.match_path("*.Processing.*").where(location: "EU").impacts.match_path("*.Lithium.*").where(location: ["CN", "Africa"]).sum(:impact_amount)
52
+ # AR query with ltree syntax
53
+ User.last.lca_cycles.disabled.match_path("*.CustomProcess.*")
54
+ User.last.lca_cycles.match_path("*{5,10}.CustomProcess.*.Ecosphere.*")
55
+ User.last.lca_cycles.active.match_path("*.WhateverProcess.*.Ecosphere.*.CO2Emission.*").where( impact_amount: [100..150]).sum(:impact_amount)
56
+
57
+ Lca::Cycle.match_path("Top.Electric Vehicle.*").impacts.match_path("*.CO2*").where( impact_amount: [ 1000..10000 ]).average(:impact_amount)
58
+ Lca::Cycle::Product.where(name: "Electric Vehicle Battery").impacts.match_path("*.Cobalt.*").sum(:impact_amount)
59
+ Lca::Process.where(owner: User.last).match_path("*{10,20}.*Assembly, automated.*")
60
+ Lca::Impact.match_path("*.Transport by truck.*")
61
+ Lca::Exchange.match_path("*.Oil.*.Ecosphere.*").impacts.sum(:impact_amount)
62
+ Lca::Product.match_path("*.ElectricVehicle.*").processes.match_path("*.Processing.*").where(location: "EU").impacts.match_path("*.Lithium.*").where(location: ["CN", "Africa"]).sum(:impact_amount)
63
63
 
64
- # pg_ltree queries
65
- User.last.lca_cycles.last.parent
66
- Lca::Process.match_path("*.Manual assembly.*").children
64
+ # pg_ltree queries
65
+ User.last.lca_cycles.last.parent
66
+ Lca::Process.match_path("*.Manual assembly.*").children
67
67
 
68
- # pg_ltree combined with AR syntax
69
- User.last.lca_cycles(type: "Lca::Product").children.match_path("*.Retail").children.exchanges
68
+ # pg_ltree combined with AR syntax
69
+ User.last.lca_cycles(type: "Lca::Product").children.match_path("*.Retail").children.exchanges
70
70
 
71
- # all queries can be directed to a specific partition:
72
- Lca::Process.owned_by( owner_id ).match_path("*.Recycling.*").where(impact_unit: "tons CO2/year").impacts.sum(:impact_amount)
71
+ # all queries can be directed to a specific partition:
72
+ Lca::Process.owned_by( owner_id ).match_path("*.Recycling.*").where(impact_unit: "tons CO2/year").impacts.sum(:impact_amount)
73
73
 
74
74
  ```
75
75
 
76
76
  The gem also creates some default models:
77
77
 
78
78
  ```ruby
79
- Lca::Process::Transport::ByAir.match_path("*.CO2Emission.*").impacts.sum(:impact_amount)
80
- Lca::Impact::Ecosphere::Fauna.match_path("*.ResourceExtraction.*").where(impact_unit: "Species killed/year").sum(:impact_amount)
79
+ Lca::Process::Transport::ByAir.match_path("*.CO2Emission.*").impacts.sum(:impact_amount)
80
+ Lca::Impact::Ecosphere::Fauna.match_path("*.ResourceExtraction.*").where(impact_unit: "Species killed/year").sum(:impact_amount)
81
81
  ```
82
82
 
83
83
  To see what syntax to use for path traversal please check out the following resources:
@@ -88,14 +88,30 @@ The LCA gem is designed to be compatible with PostgREST. PostgREST is an amazing
88
88
 
89
89
  If the `create_postgrest_roles` setting is on each new owner will be assigned a Postgres role allowing them to access data within their partition using PostgREST. Your owner model will be extended with a `.generate_jwt` method you can use to generate the PostgREST authentication token.
90
90
 
91
+ ## Caveats
92
+
93
+ `pg_ltree` .child / .parent queries do not work across different models due to an ActiveRecord limitation that requires results to be related via inheritance.
94
+
95
+ The following behaviors have been observed:
96
+
97
+ ```ruby
98
+
99
+ Lca::Process.last.children.impacts.where(impact_amount: 50)
100
+ # => nil
101
+
102
+ Lca::Process.last.children.unscope(where: :type).impacts.where(impact_amount: 50)
103
+ # => ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: Lca::Impact is not a subclass of Lca::Process)
104
+ ```
105
+
91
106
  ## Roadmap
92
107
 
93
- * more models for various LCA cycles, processes and impacts
108
+ * .child / .parent query support across models unrelated through inheritance
109
+ * more model templates for various LCA cycles, processes and impacts
94
110
  * model validations
95
- * query objects, including postgres RECURSIVE queries
111
+ * query objects
112
+ * postgres RECURSIVE queries
96
113
  * builders
97
114
  * seeds/fixtures
98
- * automated tests
99
115
 
100
116
 
101
117
  ## Contributing
@@ -14,19 +14,23 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
14
14
  id serial,
15
15
 
16
16
  owner_id integer,
17
- owner_type text,
17
+ owner_type character varying,
18
+
19
+ status integer,
18
20
 
19
- data_external_id text,
20
- data_provider text,
21
+ data_external_id character varying,
22
+ data_provider character varying,
21
23
 
22
- type text,
24
+ type character varying,
23
25
  name text,
24
26
 
25
- parent_id integer,
26
- parent_type text,
27
+ parent_entity_id integer,
28
+ parent_entity_type character varying,
27
29
 
28
30
  path ltree,
29
31
  path_slug text,
32
+
33
+ metadata_inline jsonb,
30
34
 
31
35
  impact_amount decimal,
32
36
  impact_amount_unit text,
@@ -44,7 +48,7 @@ SQL
44
48
  add_index LCA_OPTIONS[:table_name], :id
45
49
  add_index LCA_OPTIONS[:table_name], :owner_id
46
50
  add_index LCA_OPTIONS[:table_name], :type
47
- add_index LCA_OPTIONS[:table_name], :parent_id
51
+ add_index LCA_OPTIONS[:table_name], :parent_entity_id
48
52
 
49
53
  # next two indexes unfortunately can't be unique since a cycle can appear several times under an owner
50
54
  add_index LCA_OPTIONS[:table_name], :path, using: :gist
@@ -57,5 +61,19 @@ SQL
57
61
  execute "create role postgrest_anon nologin"
58
62
  execute "grant postgrest_anon to #{ LCA_OPTIONS[:database_user] }"
59
63
  end
64
+
65
+
66
+
67
+ #
68
+ create_table "#{ LCA_OPTIONS[:table_name] }_metadata" do |t|
69
+ t.string :model_type
70
+ t.integer :model_id
71
+ t.string :key
72
+ t.text :value
73
+
74
+ t.timestamps
75
+ end
76
+ add_index "#{ LCA_OPTIONS[:table_name] }_metadata", [ :model_type, :model_id ]
77
+ add_index "#{ LCA_OPTIONS[:table_name] }_metadata", [ :key ]
60
78
  end
61
79
  end
@@ -1,6 +1,6 @@
1
- # this still needed?
2
1
  # we include the concern manually so there should be no need for this anymore
3
- ActiveSupport.on_load(:active_record) do
4
- puts "tf is AR still being extended?"
5
- extend Lca::Lcable
6
- end
2
+ # besides, no need to polute other classes with our stuff.
3
+
4
+ # ActiveSupport.on_load(:active_record) do
5
+ # extend Lca::Lcable
6
+ # end
@@ -12,7 +12,7 @@ module Lca
12
12
 
13
13
  included do
14
14
 
15
- has_many :lca_cycles, class_name: "::Lca::Cycle", foreign_key: :owner_id, as: :owner
15
+ has_many :lca_cycles, class_name: "::Lca::Model", foreign_key: :owner_id, as: :owner
16
16
  after_create :lca_create_storage
17
17
  before_destroy :lca_delete_storage
18
18
 
@@ -13,8 +13,13 @@ module Lca::Statusable
13
13
  alias_method :disable!, :inactive!
14
14
  alias_method :off!, :inactive!
15
15
  alias_method :toggle?, :toggle_status!
16
+
17
+ before_create :set_default_status
16
18
  end
17
19
 
20
+ def set_default_status
21
+ self.status ||= 1
22
+ end
18
23
 
19
24
  def toggle_status!
20
25
  if active?
@@ -1,6 +1,6 @@
1
1
  class Lca::Cycle < Lca::Model
2
2
 
3
- has_many :stages, class_name: "::Lca::Stage", foreign_key: :parent_id, dependent: :destroy
3
+ has_many :stages, class_name: "::Lca::Stage", foreign_key: :parent_entity_id, dependent: :destroy
4
4
 
5
5
  def subcycles
6
6
  ::Lca::Cycle.match_path("#{path}.*")
@@ -1,4 +1,4 @@
1
1
  class Lca::Exchange < Lca::Model
2
- belongs_to :process, class_name: "::Lca::Process", foreign_key: :parent_id
3
- has_many :impacts, class_name: "::Lca::Impact", foreign_key: :parent_id, dependent: :destroy
2
+ belongs_to :process, class_name: "::Lca::Process", foreign_key: :parent_entity_id, required: true
3
+ has_many :impacts, class_name: "::Lca::Impact", foreign_key: :parent_entity_id, dependent: :destroy
4
4
  end
@@ -1,6 +1,6 @@
1
1
  class Lca::Impact < Lca::Model
2
- belongs_to :exchange, class_name: "::Lca::Exchange", foreign_key: :parent_id
3
-
2
+ belongs_to :exchange, class_name: "::Lca::Exchange", foreign_key: :parent_entity_id, required: true
3
+
4
4
  validates_presence_of :impact_amount
5
5
  validates_presence_of :impact_amount_unit, allow_blank: false
6
6
  validates_presence_of :impact_factor
@@ -0,0 +1,12 @@
1
+ class Lca::Metadata < ActiveRecord::Base
2
+
3
+ belongs_to :model, polymorphic: true, required: true
4
+
5
+ def self.table_name
6
+ return "#{ ::LCA_OPTIONS[:table_name] }_metadata" if defined? ::LCA_OPTIONS
7
+ return "lca_models_metadata"
8
+ end
9
+
10
+ validates_presence_of :key
11
+
12
+ end
@@ -4,12 +4,14 @@ class Lca::Model < ActiveRecord::Base
4
4
 
5
5
  self.primary_key = :id
6
6
 
7
+ ltree :path
8
+
7
9
  def self.table_name
8
10
  return ::LCA_OPTIONS[:table_name] if defined? ::LCA_OPTIONS
9
11
  return "lca_models"
10
12
  end
11
13
 
12
- belongs_to :owner, polymorphic: :true
14
+ belongs_to :owner, polymorphic: :true, required: true
13
15
 
14
16
  scope :match_path, -> (some_path) { where("path ~ ?", "#{some_path}") }
15
17
 
@@ -19,10 +21,13 @@ class Lca::Model < ActiveRecord::Base
19
21
  validates_presence_of :name, allow_blank: false
20
22
  validates_presence_of :path, allow_blank: false
21
23
 
22
- before_save :set_defaults
24
+ has_many :metadata, class_name: "::Lca::Metadata", dependent: :destroy, as: :model
25
+
26
+ before_validation :set_defaults
23
27
  def set_defaults
24
- self.path ||= name.parameterize.gsub("-", ".")
25
- self.path_slug = path.parameterize
28
+ self.path ||= name.delete(" ").gsub(/[^0-9a-z ]/i, '') if name
29
+ self.path_slug = path.parameterize if path
30
+ self.metadata_inline ||= {}
26
31
  end
27
32
 
28
33
 
@@ -57,6 +62,10 @@ class Lca::Model < ActiveRecord::Base
57
62
  class_name
58
63
  end
59
64
 
65
+ model_class.define_singleton_method(:sti_name) do
66
+ original_class_name
67
+ end
68
+
60
69
  # override the STI name lmfao
61
70
  model_class.define_singleton_method(:find_sti_class) do |p|
62
71
  original_class_name.constantize
@@ -1,10 +1,9 @@
1
1
  class Lca::Process < Lca::Model
2
2
 
3
- has_many :exchanges, class_name: "::Lca::Exchange", foreign_key: :parent_id, dependent: :destroy
3
+ has_many :exchanges, class_name: "::Lca::Exchange", foreign_key: :parent_entity_id, dependent: :destroy
4
4
  has_many :impacts, through: :exchanges
5
- belongs_to :stage, class_name: "::Lca::Stage", foreign_key: :parent_id
6
5
 
7
- def subprocesses
8
- ::Lca::Process.where("path ~ ?", "#{path}.*")
9
- end
6
+ has_many :subprocesses, class_name: "::Lca::Process", foreign_key: :parent_entity_id, dependent: :destroy, as: :parent_entity
7
+ belongs_to :parent_entity, polymorphic: true, required: true
8
+
10
9
  end
@@ -1,6 +1,6 @@
1
1
  class Lca::Stage < Lca::Model
2
2
 
3
- belongs_to :cycle, class_name: "::Lca::Cycle", foreign_key: :parent_id
4
- has_many :processes, class_name: "::Lca::Process", foreign_key: :parent_id
3
+ belongs_to :cycle, class_name: "::Lca::Cycle", foreign_key: :parent_entity_id, required: true
4
+ has_many :processes, class_name: "::Lca::Process", foreign_key: :parent_entity_id
5
5
 
6
6
  end
data/lib/lca/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lca
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.5"
5
5
  end
data/lib/lca.rb CHANGED
@@ -3,9 +3,12 @@ require "active_support/all"
3
3
 
4
4
  require "active_record"
5
5
 
6
+ require "pg_ltree"
7
+
6
8
  require_relative "lca/version"
7
9
 
8
10
  require_relative "lca/models/concerns/statusable"
11
+ require_relative "lca/models/metadata"
9
12
  require_relative "lca/models/model"
10
13
  require_relative "lca/models/cycle"
11
14
  require_relative "lca/models/stage"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick @ Earthster
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-25 00:00:00.000000000 Z
11
+ date: 2021-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -118,6 +118,7 @@ files:
118
118
  - lib/lca/models/impact/human_health/cancer.rb
119
119
  - lib/lca/models/impact/technosphere.rb
120
120
  - lib/lca/models/impact/technosphere/resource_availability.rb
121
+ - lib/lca/models/metadata.rb
121
122
  - lib/lca/models/model.rb
122
123
  - lib/lca/models/process.rb
123
124
  - lib/lca/models/process/raw_resource.rb