renshuu 0.1.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +15 -0
- data/README.md +123 -0
- data/lib/renshuu/models/grammar.rb +82 -0
- data/lib/renshuu/models/kanji.rb +80 -0
- data/lib/renshuu/models/list.rb +138 -0
- data/lib/renshuu/models/mixins/listable.rb +69 -0
- data/lib/renshuu/models/mixins/schedulable.rb +69 -0
- data/lib/renshuu/models/model.rb +2 -8
- data/lib/renshuu/models/presence.rb +66 -0
- data/lib/renshuu/models/profile.rb +158 -0
- data/lib/renshuu/models/schedule.rb +208 -0
- data/lib/renshuu/models/sentence.rb +56 -0
- data/lib/renshuu/models/types.rb +26 -0
- data/lib/renshuu/models/user_data.rb +53 -0
- data/lib/renshuu/models/word.rb +90 -0
- data/lib/renshuu/models.rb +17 -1
- data/lib/renshuu/version.rb +1 -1
- data/lib/renshuu.rb +1 -1
- metadata +18 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df6e1af568b7bb56df71d9bd55a92b9a64e0319e4a0daa6df5afec0a4c2d905e
|
|
4
|
+
data.tar.gz: 06fe422633574facfb49f0c9fbc53b52969026fe9df4fd8f50cf7904768ab379
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 73dd997e5c0ef3fcd15de076192d660ee36158f7ac8bc54a54d3ce98281358595ae5b9523a8918fd66d2e5a9ebedd04d24c345137fe54e3b2f851ae5733f6813
|
|
7
|
+
data.tar.gz: 2c639cbe92edd74939c8b9ca2785c64a68b0d827c61f50d638e26605dff70169c1354893fc2a05ff976e4d6a40e5530145889520691555d18719caf714a1050d
|
data/.rubocop.yml
CHANGED
|
@@ -9,6 +9,10 @@ AllCops:
|
|
|
9
9
|
NewCops: enable
|
|
10
10
|
TargetRubyVersion: 3.3
|
|
11
11
|
|
|
12
|
+
Layout/CaseIndentation:
|
|
13
|
+
EnforcedStyle: end
|
|
14
|
+
Layout/EndAlignment:
|
|
15
|
+
EnforcedStyleAlignWith: start_of_line
|
|
12
16
|
Layout/LineLength:
|
|
13
17
|
Max: 80
|
|
14
18
|
Layout/MultilineMethodCallIndentation:
|
|
@@ -22,4 +26,4 @@ RSpec/NestedGroups:
|
|
|
22
26
|
Max: 4
|
|
23
27
|
RSpec/SpecFilePathFormat:
|
|
24
28
|
Exclude:
|
|
25
|
-
- spec/renshuu/models
|
|
29
|
+
- spec/renshuu/models/**/*_spec.rb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0] - 2025-12-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Schedules
|
|
8
|
+
- Lists
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2025-12-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Word dictionary and lookup
|
|
15
|
+
- Grammar dictionary and lookup
|
|
16
|
+
- Sentence dictionary and lookup
|
|
17
|
+
|
|
3
18
|
## [0.1.0] - 2025-07-31
|
|
4
19
|
|
|
5
20
|
### Added
|
data/README.md
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Ruby wrapper for [Renshuu], the Japanese learning platform.
|
|
4
4
|
|
|
5
|
+
[![Pipeline status badge][pipeline]][pipelines]
|
|
6
|
+
[![Coverage badge][coverage]][pipelines]
|
|
7
|
+
[![Latest release badge][latest-release]][releases]
|
|
8
|
+
|
|
5
9
|
[Renshuu]: https://renshuu.org
|
|
10
|
+
[pipeline]: https://gitlab.com/Richard-Degenne/renshuu/badges/dev/pipeline.svg?ignore_skipped=true
|
|
11
|
+
[pipelines]: https://gitlab.com/Richard-Degenne/renshuu/-/pipelines/
|
|
12
|
+
[coverage]: https://gitlab.com/Richard-Degenne/renshuu/badges/dev/coverage.svg
|
|
13
|
+
[latest-release]: https://gitlab.com/Richard-Degenne/renshuu/-/badges/release.svg
|
|
14
|
+
[releases]: https://gitlab.com/Richard-Degenne/renshuu/-/releases/
|
|
6
15
|
|
|
7
16
|
## Installation
|
|
8
17
|
|
|
@@ -61,6 +70,120 @@ Renshuu::Kanji.search('たべる')
|
|
|
61
70
|
Renshuu::Kanji.get('紅')
|
|
62
71
|
```
|
|
63
72
|
|
|
73
|
+
### Vocabulary dictionary
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# Searches the word dictionary
|
|
77
|
+
Renshuu::Word.search('to eat')
|
|
78
|
+
Renshuu::Word.search('taberu')
|
|
79
|
+
Renshuu::Word.search('食べる')
|
|
80
|
+
|
|
81
|
+
# Retrieves a word from its identifier
|
|
82
|
+
Renshuu::Word.get(370)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Grammar dictionary
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# Searches the grammar dictionary
|
|
89
|
+
Renshuu::Grammar.search('without')
|
|
90
|
+
Renshuu::Grammar.search('ましょう')
|
|
91
|
+
|
|
92
|
+
# Retrieves a specific grammar entry from its identifier
|
|
93
|
+
Renshuu::Grammar.get(6)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Sentence dictionary
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Searches the sentence dictionary
|
|
100
|
+
Renshuu::Sentence.search('to eat')
|
|
101
|
+
Renshuu::Sentence.search('ケーキ')
|
|
102
|
+
|
|
103
|
+
# Retrieves sentences for a given word.
|
|
104
|
+
Renshuu::Sentence.word_search(370)
|
|
105
|
+
|
|
106
|
+
# Syntactic sugar for `Renshuu::Word`.
|
|
107
|
+
word = Renshuu::Word.get(370)
|
|
108
|
+
word.sentences
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Study lists
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
# Lists the user's study lists
|
|
115
|
+
Renshuu::Schedule.list
|
|
116
|
+
|
|
117
|
+
# Retrieves a specific list
|
|
118
|
+
Renshuu::Schedule.get(36555)
|
|
119
|
+
|
|
120
|
+
# Retrieves the contents of a list
|
|
121
|
+
list = Renshuu::Schedule.get(36555)
|
|
122
|
+
list.contents
|
|
123
|
+
list.contents(page: 2, group: :review_today)
|
|
124
|
+
|
|
125
|
+
# Manage list content
|
|
126
|
+
kanji = Renshuu::Kanji.get('食')
|
|
127
|
+
kanji.add_to_list(list)
|
|
128
|
+
kanji.remove_from_list(list)
|
|
129
|
+
# Or
|
|
130
|
+
list.add(kanji)
|
|
131
|
+
list.remove(kanji)
|
|
132
|
+
|
|
133
|
+
word = Renshuu::Word.get(370)
|
|
134
|
+
word.add_to_list(list)
|
|
135
|
+
word.remove_from_list(list)
|
|
136
|
+
# Or
|
|
137
|
+
list.add(word)
|
|
138
|
+
list.remove(word)
|
|
139
|
+
|
|
140
|
+
grammar = Renshuu::Grammar.get(927)
|
|
141
|
+
grammar.add_to_list(list)
|
|
142
|
+
grammar.remove_from_list(list)
|
|
143
|
+
# Or
|
|
144
|
+
list.add(grammar)
|
|
145
|
+
list.remove(grammar)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Lists
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
# Lists the user's lists
|
|
152
|
+
Renshuu::List.list
|
|
153
|
+
|
|
154
|
+
# Retrieves the contents of a list
|
|
155
|
+
list = Renshuu::List.list.first.lists.first # Browsing the list groups
|
|
156
|
+
list.contents
|
|
157
|
+
list.contents(page: 2)
|
|
158
|
+
|
|
159
|
+
# Manage list content
|
|
160
|
+
kanji = Renshuu::Kanji.get('食')
|
|
161
|
+
kanji.add_to_list(list)
|
|
162
|
+
kanji.remove_from_list(list)
|
|
163
|
+
# Or
|
|
164
|
+
list.add(kanji)
|
|
165
|
+
list.remove(kanji)
|
|
166
|
+
|
|
167
|
+
word = Renshuu::Word.get(370)
|
|
168
|
+
word.add_to_list(list)
|
|
169
|
+
word.remove_from_list(list)
|
|
170
|
+
# Or
|
|
171
|
+
list.add(word)
|
|
172
|
+
list.remove(word)
|
|
173
|
+
|
|
174
|
+
grammar = Renshuu::Grammar.get(927)
|
|
175
|
+
grammar.add_to_list(list)
|
|
176
|
+
grammar.remove_from_list(list)
|
|
177
|
+
# Or
|
|
178
|
+
list.add(grammar)
|
|
179
|
+
list.remove(grammar)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Documentation
|
|
183
|
+
|
|
184
|
+
A comprehensive documenation is available at
|
|
185
|
+
<https://renshuu.richarddegenne.fr>.
|
|
186
|
+
|
|
64
187
|
## Development
|
|
65
188
|
|
|
66
189
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Renshuu
|
|
4
|
+
##
|
|
5
|
+
# Model class for grammar entries.
|
|
6
|
+
class Grammar < Model
|
|
7
|
+
include Schedulable
|
|
8
|
+
include Listable
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# Searches the grammar dictionary.
|
|
12
|
+
#
|
|
13
|
+
# @param [String] value
|
|
14
|
+
#
|
|
15
|
+
# @return [Array<Grammar>]
|
|
16
|
+
def self.search(value)
|
|
17
|
+
Renshuu.client.query(:get, 'v1/grammar/search', params: { value: })
|
|
18
|
+
.fetch(:grammar).map { new(_1) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Retrieves a grammar entry fom the dictionary.
|
|
23
|
+
#
|
|
24
|
+
# @param [Integer] id
|
|
25
|
+
#
|
|
26
|
+
# @return [Grammar]
|
|
27
|
+
def self.get(id)
|
|
28
|
+
Renshuu.client.query(:get, "v1/grammar/#{id}").then { new(_1) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# @!attribute [r] id
|
|
33
|
+
# @return [String]
|
|
34
|
+
attribute :id, Types::String
|
|
35
|
+
##
|
|
36
|
+
# @!attribute [r] title_english
|
|
37
|
+
# @return [String]
|
|
38
|
+
attribute :title_english, Types::String
|
|
39
|
+
##
|
|
40
|
+
# @!attribute [r] title_japanese
|
|
41
|
+
# @return [String]
|
|
42
|
+
attribute :title_japanese, Types::String
|
|
43
|
+
##
|
|
44
|
+
# @!attribute [r] meaning
|
|
45
|
+
# @return [Hash{String => String}]
|
|
46
|
+
attribute :meaning, Types::Hash.map(Types::String, Types::String)
|
|
47
|
+
##
|
|
48
|
+
# @!attribute [r] meaning_long
|
|
49
|
+
# @return [Hash{String => String}]
|
|
50
|
+
attribute :meaning_long, Types::Hash.map(Types::String, Types::String)
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Model class for grammar models.
|
|
54
|
+
class Model < Model
|
|
55
|
+
##
|
|
56
|
+
# @!attribute [r] japanese
|
|
57
|
+
# @return [String]
|
|
58
|
+
attribute :japanese, Types::String
|
|
59
|
+
##
|
|
60
|
+
# @!attribute [r] hiragana
|
|
61
|
+
# @return [String]
|
|
62
|
+
attribute :hiragana, Types::String
|
|
63
|
+
##
|
|
64
|
+
# @!attribute [r] meanings
|
|
65
|
+
# @return [Hash{String => String}]
|
|
66
|
+
attribute :meanings, Types::Hash.map(Types::String, Types::String)
|
|
67
|
+
end
|
|
68
|
+
##
|
|
69
|
+
# @!attribute [r] models
|
|
70
|
+
# @return [Array<Model>]
|
|
71
|
+
attribute(:models, Types::Array.of(Model).default { [] })
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# @!attribute [r] user_data
|
|
75
|
+
# @return [UserData, nil]
|
|
76
|
+
attribute? :user_data, UserData
|
|
77
|
+
##
|
|
78
|
+
# @!attribute [r] presence
|
|
79
|
+
# @return [Presence, nil]
|
|
80
|
+
attribute? :presence, Presence
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/renshuu/models/kanji.rb
CHANGED
|
@@ -6,6 +6,9 @@ module Renshuu
|
|
|
6
6
|
#
|
|
7
7
|
# @see https://api.renshuu.org/docs/#/Kanji
|
|
8
8
|
class Kanji < Model
|
|
9
|
+
include Schedulable
|
|
10
|
+
include Listable
|
|
11
|
+
|
|
9
12
|
##
|
|
10
13
|
# Searches the Renshuu kanji dictionary.
|
|
11
14
|
#
|
|
@@ -29,5 +32,82 @@ module Renshuu
|
|
|
29
32
|
body = Renshuu.client.query(:get, "v1/kanji/#{cgi_character}")
|
|
30
33
|
new(body)
|
|
31
34
|
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# @!attribute [r] id
|
|
38
|
+
# @return [String]
|
|
39
|
+
attribute :id, Types::String
|
|
40
|
+
##
|
|
41
|
+
# @!attribute [r] kanji
|
|
42
|
+
# @return [String]
|
|
43
|
+
attribute :kanji, Types::String
|
|
44
|
+
##
|
|
45
|
+
# @!attribute [r] definition
|
|
46
|
+
# @return [String]
|
|
47
|
+
attribute :definition, Types::String
|
|
48
|
+
##
|
|
49
|
+
# @!attribute [r] scount
|
|
50
|
+
# @return [Integer, nil]
|
|
51
|
+
attribute? :scount, Types::Integer
|
|
52
|
+
alias stroke_count scount
|
|
53
|
+
##
|
|
54
|
+
# @!attribute [r] radical
|
|
55
|
+
# @return [String, nil]
|
|
56
|
+
attribute? :radical, Types::String
|
|
57
|
+
##
|
|
58
|
+
# @!attribute [r] radical_name
|
|
59
|
+
# @return [String, nil]
|
|
60
|
+
attribute? :radical_name, Types::String
|
|
61
|
+
##
|
|
62
|
+
# @!attribute [r] onyomi
|
|
63
|
+
# @return [String, nil]
|
|
64
|
+
attribute? :onyomi, Types::String
|
|
65
|
+
##
|
|
66
|
+
# @!attribute [r] kunyomi
|
|
67
|
+
# @return [String, nil]
|
|
68
|
+
attribute? :kunyomi, Types::String
|
|
69
|
+
##
|
|
70
|
+
# @!attribute [r] kanken
|
|
71
|
+
# @return [String, nil]
|
|
72
|
+
attribute? :kanken, Types::String
|
|
73
|
+
##
|
|
74
|
+
# @!attribute [r] jlpt
|
|
75
|
+
# @return [String, nil]
|
|
76
|
+
attribute? :jlpt, Types::String
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Model class for kanji's related words.
|
|
80
|
+
class RelatedWord < Model
|
|
81
|
+
##
|
|
82
|
+
# @!attribute [r] reading
|
|
83
|
+
# @return [String]
|
|
84
|
+
attribute :reading, Types::String
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# Model class for kanji's related words' word.
|
|
88
|
+
class Word < Model
|
|
89
|
+
##
|
|
90
|
+
# @!attribute [r] def
|
|
91
|
+
# @return [String]
|
|
92
|
+
attribute :def, Types::String
|
|
93
|
+
##
|
|
94
|
+
# @!attribute [r] term
|
|
95
|
+
# @return [String]
|
|
96
|
+
attribute :term, Types::String
|
|
97
|
+
end
|
|
98
|
+
attribute :words, Types::Array.of(Word)
|
|
99
|
+
end
|
|
100
|
+
##
|
|
101
|
+
# @!attribute [r] rwords
|
|
102
|
+
# @return [Array<RelatedWord>, nil]
|
|
103
|
+
attribute? :rwords, Types::Array.of(RelatedWord)
|
|
104
|
+
##
|
|
105
|
+
# @!attribute [r] user_data
|
|
106
|
+
# @return [UserData, nil]
|
|
107
|
+
attribute? :user_data, UserData
|
|
108
|
+
##
|
|
109
|
+
# @!attribute [r] presence
|
|
110
|
+
# @return [Presence, nil]
|
|
111
|
+
attribute? :presence, Presence
|
|
32
112
|
end
|
|
33
113
|
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Renshuu
|
|
4
|
+
##
|
|
5
|
+
# Model class for lists.
|
|
6
|
+
#
|
|
7
|
+
# @see https://api.renshuu.org/docs/#/User
|
|
8
|
+
class List < Model
|
|
9
|
+
##
|
|
10
|
+
# Mapping between list termtypes and model classes.
|
|
11
|
+
#
|
|
12
|
+
# @see #term_class
|
|
13
|
+
TERMTYPES = {
|
|
14
|
+
'kanji' => Kanji, 'vocab' => Word, 'grammar' => Grammar,
|
|
15
|
+
'sent' => Sentence
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# Retrives list groups from the API.
|
|
20
|
+
#
|
|
21
|
+
# @return [Array<Group>]
|
|
22
|
+
def self.list
|
|
23
|
+
body = Renshuu.client.query(:get, 'v1/lists')
|
|
24
|
+
|
|
25
|
+
body.fetch(:termtype_groups).flat_map do |termtype_group|
|
|
26
|
+
termtype = termtype_group.fetch(:termtype)
|
|
27
|
+
termtype_group.fetch(:groups).map { Group.new(termtype:, **_1) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Model class for list groups, _a.k.a._ sets.
|
|
33
|
+
class Group < Model
|
|
34
|
+
##
|
|
35
|
+
# @!attribute [r] group_title
|
|
36
|
+
# @return [String]
|
|
37
|
+
attribute :group_title, Types::String
|
|
38
|
+
alias title group_title
|
|
39
|
+
##
|
|
40
|
+
# @!attribute [r] termtype
|
|
41
|
+
# @return [String]
|
|
42
|
+
attribute :termtype, Types::String
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# @!attribute [r] lists
|
|
46
|
+
# @return [Array<List>]
|
|
47
|
+
attribute :lists, Types::Array.of(List)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# @!attribute [r] list_id
|
|
52
|
+
# @return [String]
|
|
53
|
+
attribute :list_id, Types::String
|
|
54
|
+
alias id list_id
|
|
55
|
+
##
|
|
56
|
+
# @!attribute [r] termtype
|
|
57
|
+
# @return [String]
|
|
58
|
+
attribute :termtype, Types::String
|
|
59
|
+
##
|
|
60
|
+
# @!attribute [r] title
|
|
61
|
+
# @return [String]
|
|
62
|
+
attribute :title, Types::String
|
|
63
|
+
##
|
|
64
|
+
# @!attribute [r] description
|
|
65
|
+
# @return [String]
|
|
66
|
+
attribute :description, Types::String
|
|
67
|
+
##
|
|
68
|
+
# @!attribute [r] num_terms
|
|
69
|
+
# @return [Integer]
|
|
70
|
+
attribute :num_terms, Types::Integer
|
|
71
|
+
##
|
|
72
|
+
# @!attribute [r] privacy
|
|
73
|
+
# @return [String]
|
|
74
|
+
attribute :privacy, Types::String
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Retrieves terms contained in the list.
|
|
78
|
+
#
|
|
79
|
+
# @param [Integer, nil] page
|
|
80
|
+
#
|
|
81
|
+
# @return [Array<Kanji,Word,Grammar,Sentence>] Depending on the type of list
|
|
82
|
+
def contents(page: nil)
|
|
83
|
+
params = { pg: page }.compact
|
|
84
|
+
body = Renshuu.client.query(:get, "v1/list/#{id}", params:)
|
|
85
|
+
|
|
86
|
+
body.dig(:contents, :terms).map { term_class.new(_1) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Adds the given item to the list.
|
|
91
|
+
#
|
|
92
|
+
# @param [Listable] item
|
|
93
|
+
#
|
|
94
|
+
# @return [Void]
|
|
95
|
+
# @raise [ArgumentError] If the type of the item does not match the
|
|
96
|
+
# list's type
|
|
97
|
+
#
|
|
98
|
+
# @see Listable#add_to_list
|
|
99
|
+
def add(item)
|
|
100
|
+
unless item.is_a?(term_class)
|
|
101
|
+
raise ArgumentError, "Expected a `#{term_class}`, got a `#{item.class}`"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
item.add_to_list(self)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
##
|
|
108
|
+
# Removes the given item from the list.
|
|
109
|
+
#
|
|
110
|
+
# @param [Listable] item
|
|
111
|
+
#
|
|
112
|
+
# @return [Void]
|
|
113
|
+
# @raise [ArgumentError] If the type of the item does not match the
|
|
114
|
+
# list's type
|
|
115
|
+
#
|
|
116
|
+
# @see Listable#remove_from_list
|
|
117
|
+
def remove(item)
|
|
118
|
+
unless item.is_a?(term_class)
|
|
119
|
+
raise ArgumentError, "Expected a `#{term_class}`, got a `#{item.class}`"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
item.remove_from_list(self)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
##
|
|
126
|
+
# Model class to use for this list's terms.
|
|
127
|
+
#
|
|
128
|
+
# @return [Class]
|
|
129
|
+
# @raise [KeyError]
|
|
130
|
+
#
|
|
131
|
+
# @see ::TERMTYPES
|
|
132
|
+
def term_class
|
|
133
|
+
@term_class ||= TERMTYPES.fetch(termtype) do |key|
|
|
134
|
+
raise KeyError, "Unkown list type `#{key}`"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Renshuu
|
|
4
|
+
##
|
|
5
|
+
# Mixin for items that can be added to study {List}s.
|
|
6
|
+
#
|
|
7
|
+
# = Interface
|
|
8
|
+
#
|
|
9
|
+
# This mixin rely on the following interface:
|
|
10
|
+
#
|
|
11
|
+
# * +#id+: Identifier of the listable item;
|
|
12
|
+
# * +.base_route+: Path part used in URL construction.
|
|
13
|
+
module Listable
|
|
14
|
+
def self.included(base) # :nodoc:
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
base.extend(ClassMethods)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Adds the item to the given list.
|
|
22
|
+
#
|
|
23
|
+
# @param [List, String] list
|
|
24
|
+
#
|
|
25
|
+
# @return [Void]
|
|
26
|
+
def add_to_list(list)
|
|
27
|
+
list_id = case list
|
|
28
|
+
when List then list.id
|
|
29
|
+
else list
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Renshuu.client.query(
|
|
33
|
+
:put, "v1/#{self.class.base_route}/#{id}", params: { list_id: }
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Removes the item from the given list.
|
|
39
|
+
#
|
|
40
|
+
# @param [List, String] list
|
|
41
|
+
#
|
|
42
|
+
# @return [Void]
|
|
43
|
+
def remove_from_list(list)
|
|
44
|
+
list_id = case list
|
|
45
|
+
when List then list.id
|
|
46
|
+
else list
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Renshuu.client.query(
|
|
50
|
+
:delete, "v1/#{self.class.base_route}/#{id}", params: { list_id: }
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Module for class methods provided with the mixin.
|
|
56
|
+
module ClassMethods
|
|
57
|
+
##
|
|
58
|
+
# Path part used in URL construction.
|
|
59
|
+
#
|
|
60
|
+
# Defaults to the class name, demodulized and underscored. Override to
|
|
61
|
+
# change behavior.
|
|
62
|
+
#
|
|
63
|
+
# @return [String]
|
|
64
|
+
def base_route
|
|
65
|
+
name.demodulize.underscore
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Renshuu
|
|
4
|
+
##
|
|
5
|
+
# Mixin for items that can be added to study {Schedule}s.
|
|
6
|
+
#
|
|
7
|
+
# = Interface
|
|
8
|
+
#
|
|
9
|
+
# This mixin rely on the following interface:
|
|
10
|
+
#
|
|
11
|
+
# * +#id+: Identifier of the schedulable item;
|
|
12
|
+
# * +.base_route+: Path part used in URL construction.
|
|
13
|
+
module Schedulable
|
|
14
|
+
def self.included(base)
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
base.extend(ClassMethods)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Adds the item to the given schedule.
|
|
22
|
+
#
|
|
23
|
+
# @param [Schedule, String] schedule
|
|
24
|
+
#
|
|
25
|
+
# @return [Void]
|
|
26
|
+
def add_to_schedule(schedule)
|
|
27
|
+
sched_id = case schedule
|
|
28
|
+
when Schedule then schedule.id
|
|
29
|
+
else schedule
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Renshuu.client.query(
|
|
33
|
+
:put, "v1/#{self.class.base_route}/#{id}", params: { sched_id: }
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Removes the item from the given schedule.
|
|
39
|
+
#
|
|
40
|
+
# @param [Schedule, String] schedule
|
|
41
|
+
#
|
|
42
|
+
# @return [Void]
|
|
43
|
+
def remove_from_schedule(schedule)
|
|
44
|
+
sched_id = case schedule
|
|
45
|
+
when Schedule then schedule.id
|
|
46
|
+
else schedule
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Renshuu.client.query(
|
|
50
|
+
:delete, "v1/#{self.class.base_route}/#{id}", params: { sched_id: }
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Module for class methods provided with the mixin.
|
|
56
|
+
module ClassMethods
|
|
57
|
+
##
|
|
58
|
+
# Path part used in URL construction.
|
|
59
|
+
#
|
|
60
|
+
# Defaults to the class name, demodulized and underscored. Override to
|
|
61
|
+
# change behavior.
|
|
62
|
+
#
|
|
63
|
+
# @return [String]
|
|
64
|
+
def base_route
|
|
65
|
+
name.demodulize.underscore
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/renshuu/models/model.rb
CHANGED
|
@@ -3,13 +3,7 @@
|
|
|
3
3
|
module Renshuu
|
|
4
4
|
##
|
|
5
5
|
# Base class for model objects.
|
|
6
|
-
class Model <
|
|
7
|
-
|
|
8
|
-
# Default options for new objects.
|
|
9
|
-
#
|
|
10
|
-
# @return [Hash{Symbol => Object}]
|
|
11
|
-
def self.default_options
|
|
12
|
-
super.merge(raise_on_missing: true, recurse_over_arrays: true)
|
|
13
|
-
end
|
|
6
|
+
class Model < Dry::Struct
|
|
7
|
+
transform_keys { _1.to_s.underscore.to_sym }
|
|
14
8
|
end
|
|
15
9
|
end
|