diff options
| author | Miquel Sabaté Solà <msabate@suse.com> | 2024-03-13 18:03:43 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <msabate@suse.com> | 2024-03-13 18:03:43 +0100 |
| commit | a7791635aa98f012d35f97c3c5288494bbb28e38 (patch) | |
| tree | bad987e6b4d10ad2f7cd02e40658d464907595ee /app/models | |
| download | operum-a7791635aa98f012d35f97c3c5288494bbb28e38.tar.gz operum-a7791635aa98f012d35f97c3c5288494bbb28e38.zip | |
Initial commit
Signed-off-by: Miquel Sabaté Solà <msabate@suse.com>
Diffstat (limited to 'app/models')
| -rw-r--r-- | app/models/application_record.rb | 5 | ||||
| -rw-r--r-- | app/models/comment.rb | 18 | ||||
| -rw-r--r-- | app/models/concerns/.keep | 0 | ||||
| -rw-r--r-- | app/models/search.rb | 99 | ||||
| -rw-r--r-- | app/models/tag.rb | 7 | ||||
| -rw-r--r-- | app/models/tag_reference.rb | 6 | ||||
| -rw-r--r-- | app/models/thing.rb | 32 | ||||
| -rw-r--r-- | app/models/user.rb | 9 |
8 files changed, 176 insertions, 0 deletions
diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000..08dc537 --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 0000000..deb6ad7 --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Comment < ApplicationRecord + validates :content, presence: true + + belongs_to :thing + + has_many :tag_references, as: :taggable, dependent: :destroy + has_many :tags, through: :tag_references + + has_rich_text :content + + # Returns all comments which have a content matching the given text. + def self.like(text:) + Comment.joins(:rich_text_content) + .where('action_text_rich_texts.body LIKE ?', "%#{text}%") + end +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/models/concerns/.keep diff --git a/app/models/search.rb b/app/models/search.rb new file mode 100644 index 0000000..81a1c34 --- /dev/null +++ b/app/models/search.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +class Search < ApplicationRecord + validates :name, presence: true, uniqueness: true + validates :body, presence: true, uniqueness: true + + belongs_to :user + + # Returns all the results that can be fetched with the current `body`. It's + # returned into a hash which groups the taggable types. + def results + # This can happen as part of an empty Search.new instance. That is, it's + # still invalid, but it's the object being initialized for the default + # search (i.e. just show me everything). + return { things: Thing.order('rate DESC, created_at'), comments: [] } if body.blank? + + # Parse the body of the search, and return early if nothing was able to be + # parsed (e.g. user wrote unknown identifiers). + parsed = parse_body + return { things: [], comments: [] } if parsed.all? { |_, v| v.blank? } + + # And add everything into the `res` hash with the results. + res = find_by_text(plain: parsed[:plain]) + find_by_tags(res:, tags: parsed[:tag]) + end + + protected + + # Returns a hash which contains the tags that has been specified (`tag`) and + # the plain text (`plain`) to check on the different fields. + def parse_body + res = { tag: [], plain: [] } + + body.split.each do |part| + if part.include?(':') + parts = part.split(':', 2) + parts[1] = clean_clause(part: parts[1]) + + next if parts[0] != 'tag' + + res[parts[0].to_sym] << parts[1] + else + res[:plain] << part + end + end + + res + end + + # Returns the argument without any leading/trailing quotes. + def clean_clause(part:) + part.gsub(/^("|')+/, '').gsub(/("|')+$/, '') + end + + # Returns a hash which groups into taggable types the results by looking the + # `plain` text into different fields. + def find_by_text(plain:) + res = { things: [], comments: [] } + + plain.each do |text| + res[:things] = if res[:things].any? + res[:things].and(Thing.like(text:)).order('rate DESC, created_at') + else + Thing.like(text:).order('rate DESC, created_at') + end + res[:comments] = if res[:comments].any? + res[:comments].and(Comment.like(text:)).order(:created_at) + else + Comment.like(text:).order(:created_at) + end + end + + res + end + + # Returns a hash which groups into taggable types the results by matching the + # given `tags` which their references. It expects `res` to be already + # initialized by `find_by_text` (yeah, great design, I know), which is also + # the structure that will be returned. + def find_by_tags(res:, tags:) + return res if tags.blank? + + tags = tags.map { |name| Tag.find_by(name:) } + + query = TagReference.where(tag: tags, taggable_type: 'Thing') + query = query.where(taggable_id: res[:things].pluck(:id)) if res[:things].present? + res[:things] = query.group(:taggable_id) + .having('count(taggable_id) = ?', tags.size) + .map(&:taggable) + + query = TagReference.where(tag: tags, taggable_type: 'Comment') + query = query.where(taggable_id: res[:comments].pluck(:id)) if res[:comments].present? + res[:comments] = query.group(:taggable_id) + .having('count(taggable_id) = ?', tags.size) + .map(&:taggable) + + res + end +end diff --git a/app/models/tag.rb b/app/models/tag.rb new file mode 100644 index 0000000..1f359d6 --- /dev/null +++ b/app/models/tag.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Tag < ApplicationRecord + validates :name, presence: true, uniqueness: true + + has_many :tag_references, dependent: :destroy +end diff --git a/app/models/tag_reference.rb b/app/models/tag_reference.rb new file mode 100644 index 0000000..bd72267 --- /dev/null +++ b/app/models/tag_reference.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class TagReference < ApplicationRecord + belongs_to :tag + belongs_to :taggable, polymorphic: true +end diff --git a/app/models/thing.rb b/app/models/thing.rb new file mode 100644 index 0000000..5c758fb --- /dev/null +++ b/app/models/thing.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Thing < ApplicationRecord + validates :title, presence: true, uniqueness: true + validates :target, presence: true, uniqueness: true + validates :authors, presence: true + validates :rate, numericality: { in: 0..10 } + + belongs_to :user + + has_many :comments, dependent: :destroy + has_many :tag_references, as: :taggable, dependent: :destroy + has_many :tags, through: :tag_references + + enum :status, %i[read notread tobepublished], validate: true + enum :kind, %i[other poetry theater essay shorts novel paper], validate: true + + # Returns all things which match the given text for any of the string columns + # from the table. + def self.like(text:) + Thing.where('target LIKE ?', "%#{text}%") + .or(where('title LIKE ?', "%#{text}%")) + .or(where('publisher LIKE ?', "%#{text}%")) + .or(where('address LIKE ?', "%#{text}%")) + .or(where('url LIKE ?', "%#{text}%")) + .or(where('location LIKE ?', "%#{text}%")) + .or(where('insideof LIKE ?', "%#{text}%")) + .or(where('pages LIKE ?', "%#{text}%")) + .or(where('note LIKE ?', "%#{text}%")) + .or(where('authors LIKE ?', "%#{text}%")) + end +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..7eaba55 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class User < ApplicationRecord + has_secure_password + + validates :username, presence: true, uniqueness: true + validates :password, presence: true, on: :create + validates :password_confirmation, presence: true, on: :create +end |
