static-record 1.1.0 → 1.2.0

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
  SHA1:
3
- metadata.gz: 4d06c5fc59b117a47f86a218489bd6d95f68cfbe
4
- data.tar.gz: 68da7ab138c5dbd25db2cbd359acc30296f0eb16
3
+ metadata.gz: 54e3e66a230098bdc375ec07c84e7a7d266872df
4
+ data.tar.gz: 461d0be8afac337dc13b5cd68fa62729bf2c3842
5
5
  SHA512:
6
- metadata.gz: 5c2535dc8d88779a4d1489dc21dfd8a3c00ef13ff6c7d98869b1c87eb30c68edba56d9d01529cc17f582e946969fff2ff26858d38cdf7e5c37e1344febc8b1b1
7
- data.tar.gz: 46ccc4996e2c053e29e153cb8101c44b3aaa58e5a6b4cf1e14a37404f5f36cf8bf24aea4bc1d5224239b411bc7817f891b03a26a5f494a7a696c7262bd6b68d5
6
+ metadata.gz: 835d40e2bf9e440641cf082d2ba9b9ba33ffbc9bca037e92714fffeaef83ff718c661cf26f74a5d14ca02fc2c43eb69cb35fd051a6000bc4c6bc70a7a72f961b
7
+ data.tar.gz: 5c6e71af8b1698e0bd03fceb0d9705f3d88815d9a143eac21586fdaa8e37ed78be71ac61cc820956b3d382fda5aab904437b8afc55d8e2b2ba9cb50ab9d15f03
data/README.rdoc CHANGED
@@ -26,7 +26,7 @@ The 'require' part is important so that Rails autoloads the library correctly.
26
26
  Create your base class inheriting from StaticRecord::Base.
27
27
 
28
28
  class Article < StaticRecord::Base
29
- # Declare in which SQLite3 file "articles" will be store (db/static_<table>.sqlite3)
29
+ # Declare in which table Articles will be store (always in file db/static_records.sqlite3)
30
30
  table :articles
31
31
 
32
32
  # Declare at which path "article" files can be found
@@ -37,23 +37,24 @@ Create your base class inheriting from StaticRecord::Base.
37
37
  primary_key :name
38
38
 
39
39
  # You can specify default values for each column of your static records
40
- # Just create a method with name: default_<attribute_name>
41
- def default_author
40
+ # Just create a class method with name: default_<attribute_name>
41
+ def self.default_author
42
42
  'Default author'
43
43
  end
44
44
 
45
45
  # You can override any attribute value
46
- # Just create a method with name: override_<attribute_name>
47
- def override_cover(cover)
46
+ # Just create a class method with name: override_<attribute_name>
47
+ def self.override_cover(cover)
48
48
  return cover if cover.to_s.include?('/')
49
49
  Rails.root.join('public', 'default_cover_path', cover)
50
50
  end
51
51
 
52
52
  # Specify which "article" attributes can be queried over with their types
53
- columns name: :string,
54
- author: :string,
55
- rank: :integer,
56
- cover: :string
53
+ columns name: :string,
54
+ author: :string,
55
+ rank: :integer,
56
+ cover: :string,
57
+ category :static_record
57
58
  end
58
59
 
59
60
  At each application startup, an SQLite3 database will be created to store this class' children.
@@ -63,6 +64,7 @@ Available column types are
63
64
  - :integer
64
65
  - :boolean
65
66
  - :float
67
+ - :static_record
66
68
 
67
69
  === Child class
68
70
 
@@ -75,6 +77,10 @@ Create has many child class as you want.
75
77
  attribute :rank, 2
76
78
  attribute :cover, Rails.root.join('public', 'articles', 'one.jpg')
77
79
 
80
+ # Define the references to other StaticRecords
81
+ # These ones can be queried over after a jointure
82
+ reference :category, Category.find('Category One')
83
+
78
84
  # Your class can be used as any other Ruby class
79
85
  def initialize
80
86
  @an_instance_variable
@@ -94,25 +100,51 @@ In your code, you can perform queries like this one:
94
100
 
95
101
  Article.where(name: 'Article Two').or.where('rank >= 2').limit(2).offset(3)
96
102
 
97
- I tried to implement as many SQL functions wrappers that ActiveRecord provides as I could.
103
+ I tried to implement as much of ActiveRecord's query interface as I could.
98
104
 
99
- There is still a lot of work before everything is available, but I chose to release the 1.0.0.pre nevertheless.
105
+ There is still a lot of work before everything is available, but I chose to release the gem nevertheless.
100
106
 
101
107
  Here is a full list:
102
108
  * where
103
- * supports Hash -> where(name: 'Name', author: 'Author')
104
- * supports String -> where("name = 'Name'") or where("name = ?", 'Name') or where("name = :name", name: 'Name')
109
+ * Article.where(name: 'Article One', author: 'The author')
110
+ * Article.where(name: ['Article One', 'Article Two'])
111
+ * Article.where(name: 'Article One').where(author: 'The author')
112
+ * Article.where("name = 'Article One'")
113
+ * Article.where("name = ?", 'Article One')
114
+ * Article.where("name = :name", name: 'Article One')
105
115
  * find (only if a primary key has been set)
116
+ * Article.find('Article One')
106
117
  * find_by
118
+ * Article.find_by(author: 'The author')
119
+ * joins
120
+ * Article.joins(:categories).where("categories.name = 'Category One'")
107
121
  * not
122
+ * Article.where.not(author: 'The author')
108
123
  * or
124
+ * Article.where(author: 'The author').or.where(author: 'Another author')
125
+ * Article.where.not(name: 'Article One').or.where.not(author: 'The author')
109
126
  * all
127
+ * Article.where(author: 'The author').all
128
+ * Article.all
110
129
  * take
130
+ * Article.take
131
+ * Article.take(3)
111
132
  * first
133
+ * Article.first
134
+ * Article.first(3)
112
135
  * last
136
+ * Article.last
137
+ * Article.last(3)
113
138
  * limit
139
+ * Article.limit(5)
114
140
  * offset
141
+ * Article.limit(3).offset(2)
115
142
  * order
143
+ * Article.order(:rank) # Use ASC ordering
144
+ * Article.order([:rank, :name]) # Use ASC ordering for both columns
145
+ * Article.order(rank: :asc)
146
+ * Article.order(rank: :desc, name: :asc)
147
+ * Article.order("rank DESC")
116
148
 
117
149
  == IDs
118
150
 
@@ -122,6 +154,8 @@ As the database is recreated at each application startup and IDs depend on the i
122
154
 
123
155
  == References
124
156
 
157
+ === ActiveRecord
158
+
125
159
  A migration helper allows your ActiveRecord models to reference a StaticRecord.
126
160
 
127
161
  In a migration, use:
@@ -153,6 +187,32 @@ If you don't want to name your column with the same name than your StaticRecord
153
187
  # In the model
154
188
  has_static_record :any_column_name, class_name: 'Article'
155
189
 
190
+ === StaticRecord
191
+
192
+ You can also use 'reference' instead of 'attribute' to have your StaticRecords reference other ones.
193
+
194
+ You must use type :static_record when declaring your column!
195
+
196
+ class Article < StaticRecord::Base
197
+ table :articles
198
+ path Rails.root.join('app', 'models', 'articles', '**', '*.rb')
199
+ primary_key :name
200
+
201
+ columns name: :string,
202
+ category :static_record
203
+ end
204
+
205
+ class ArticleOne < Article
206
+ attribute :name, 'Article one'
207
+ reference :category, Category.find('Category One')
208
+ end
209
+
210
+ You can query them like this
211
+
212
+ Article.where(category: Category.find('Category One'))
213
+ Article.joins(Category).where("categories.name = 'Category One'")
214
+ Article.joins("categories").where("categories.name = 'Category One'")
215
+
156
216
  == Questions?
157
217
 
158
218
  If you have any question or doubt regarding StaticRecord which you cannot find the solution to in the documentation, you can send me an email. I'll try to answer in less than 24 hours.
@@ -165,4 +225,4 @@ If you find a bug please add an issue on GitHub or fork the project and send a p
165
225
 
166
226
  - Better documentation
167
227
  - Generators
168
- - Allow StaticRecord to reference other static records and add possibility to query over relations (joins)
228
+ - Improve jointures
data/lib/static_record.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  require 'static_record/engine'
2
2
  require 'static_record/exceptions'
3
3
 
4
+ require 'static_record/concerns/getters_setters_concern'
4
5
  require 'static_record/concerns/query_building_concern'
5
6
  require 'static_record/concerns/sqlite_storing_concern'
7
+ require 'static_record/concerns/request_execution_concern'
6
8
 
7
9
  require 'static_record/models/query_interface/conditioners'
8
10
  require 'static_record/models/query_interface/retrievers'
@@ -0,0 +1,43 @@
1
+ module StaticRecord
2
+ # Contains StaticRecord::Base getters and setters
3
+ module GettersSettersConcern
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods # :nodoc:
7
+ def primary_key(name)
8
+ err = StaticRecord::Base::RESERVED_ATTRIBUTES.include?("@@#{name}".to_sym)
9
+ raise StaticRecord::ReservedAttributeName, "#{name} is a reserved name" if err
10
+ class_variable_set('@@_primary_key', name)
11
+ end
12
+
13
+ def pkey
14
+ class_variable_defined?(:@@_primary_key) ? class_variable_get('@@_primary_key') : nil
15
+ end
16
+
17
+ def bound_static_tables
18
+ return {} unless class_variable_defined?(:@@_bound_static_tables)
19
+ class_variable_get('@@_bound_static_tables')
20
+ end
21
+
22
+ def bound_static_tables=(value)
23
+ class_variable_set('@@_bound_static_tables', value)
24
+ end
25
+
26
+ def table(store)
27
+ class_variable_set('@@_store', store.to_s)
28
+ end
29
+
30
+ def store
31
+ class_variable_get('@@_store')
32
+ end
33
+
34
+ def path(path)
35
+ class_variable_set('@@_path_pattern', path)
36
+ end
37
+
38
+ def path_pattern
39
+ class_variable_get('@@_path_pattern')
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,6 +5,7 @@ module StaticRecord
5
5
 
6
6
  def build_query
7
7
  sql = sql_select_from
8
+ sql += sql_joins unless @joins_clauses.empty?
8
9
  sql += sql_where unless @where_clauses.empty?
9
10
  sql += sql_order unless @order_by.empty?
10
11
  sql += sql_limit_offset if @sql_limit
@@ -17,6 +18,13 @@ module StaticRecord
17
18
  "SELECT #{@columns} FROM #{@table}"
18
19
  end
19
20
 
21
+ def sql_joins
22
+ @joins_clauses.map do |joint|
23
+ " INNER JOIN #{joint} ON "\
24
+ "#{@store}.#{get_column_for_static_table(joint)} = #{joint}.klass"
25
+ end.join
26
+ end
27
+
20
28
  def sql_where
21
29
  " WHERE #{where_clause_builder}"
22
30
  end
@@ -70,14 +78,14 @@ module StaticRecord
70
78
  if value.is_a?(Array)
71
79
  # ex: where(name: ['John', 'Jack'])
72
80
  # use IN operator
73
- value.map! { |v| v =~ /^\d+$/ ? v : "\"#{v}\"" }
81
+ value.map! { |v| cast(v) }
74
82
  inverse = 'NOT ' if clause[:operator] == :not_eq
75
83
  "#{key} #{inverse}IN (#{value.join(',')})"
76
84
  else
77
85
  # ex: where(name: 'John')
78
86
  # use = operator
79
87
  inverse = '!' if clause[:operator] == :not_eq
80
- "#{key} #{inverse}= '#{value}'"
88
+ "#{key} #{inverse}= #{cast(value)}"
81
89
  end
82
90
  end
83
91
  parts.join(' AND ')
@@ -89,16 +97,32 @@ module StaticRecord
89
97
  # Anon parameters
90
98
  # ex: where("name = ? OR name = ?", 'John', 'Jack')
91
99
  clause[:parameters].each do |param|
92
- final_string.sub!(/\?/, "\"#{param}\"")
100
+ final_string.sub!(/\?/, cast(param))
93
101
  end
94
102
  elsif clause[:parameters].is_a?(Hash)
95
103
  # Named parameters (placeholder condition)
96
104
  # ex: where("name = :one OR name = :two", one: 'John', two: 'Smith')
97
105
  clause[:parameters].each do |key, value|
98
- final_string.sub!(":#{key}", "\"#{value}\"")
106
+ final_string.sub!(":#{key}", cast(value))
99
107
  end
100
108
  end
101
109
  final_string
102
110
  end
111
+
112
+ def cast(value)
113
+ return escape(value.class.name) if value.class < StaticRecord::Base
114
+ return value.to_s if value =~ /^[+|-]?\d+\.?\d*$/
115
+ escape(value)
116
+ end
117
+
118
+ def escape(value)
119
+ return "'#{value}'" unless value.include?('\'')
120
+ "\"#{value}\""
121
+ end
122
+
123
+ def get_column_for_static_table(table)
124
+ klass = @klass.constantize
125
+ klass.bound_static_tables[table.to_sym]
126
+ end
103
127
  end
104
128
  end
@@ -0,0 +1,57 @@
1
+ module StaticRecord
2
+ module RequestExecutionConcern # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+
7
+ def exec_request(expectancy = :result_set)
8
+ return build_query if @only_sql
9
+
10
+ dbname = Rails.root.join('db', 'static_records.sqlite3').to_s
11
+ result = get_expected_result_from_database(dbname, expectancy)
12
+ cast_result(expectancy, result)
13
+ end
14
+
15
+ def get_expected_result_from_database(dbname, expectancy)
16
+ result = nil
17
+
18
+ begin
19
+ db = SQLite3::Database.open(dbname)
20
+ query = build_query
21
+ if expectancy == :integer
22
+ result = db.get_first_value(query)
23
+ else
24
+ statement = db.prepare(query)
25
+ result_set = statement.execute
26
+ result = result_set.map { |row| row[1].constantize.new }
27
+ end
28
+ rescue SQLite3::Exception => e
29
+ error, msg = handle_error(e, query)
30
+ ensure
31
+ statement.close if statement
32
+ db.close if db
33
+ end
34
+ raise error, msg if error
35
+
36
+ result
37
+ end
38
+
39
+ def handle_error(error, query)
40
+ msg = query ? "#{error} in #{query}" : error
41
+ [error, msg]
42
+ end
43
+
44
+ def cast_result(expectancy, result)
45
+ if expectancy == :result_set
46
+ case @result_type
47
+ when :array
48
+ result = [] if result.nil?
49
+ when :record
50
+ result = result.empty? ? nil : result.first
51
+ end
52
+ end
53
+
54
+ result
55
+ end
56
+ end
57
+ end
@@ -8,7 +8,7 @@ module StaticRecord
8
8
  def create_store
9
9
  cols = class_variable_get(:@@_columns)
10
10
  begin
11
- dbname = Rails.root.join('db', "static_#{store}.sqlite3").to_s
11
+ dbname = Rails.root.join('db', 'static_records.sqlite3').to_s
12
12
  SQLite3::Database.new(dbname)
13
13
  db = SQLite3::Database.open(dbname)
14
14
  db.execute("DROP TABLE IF EXISTS #{store}")
@@ -37,7 +37,7 @@ module StaticRecord
37
37
 
38
38
  def column_type_to_sql(ctype)
39
39
  case ctype.to_s
40
- when 'string'
40
+ when 'string', 'static_record'
41
41
  ' TEXT'
42
42
  else
43
43
  " #{ctype.to_s.upcase}"
@@ -54,7 +54,14 @@ module StaticRecord
54
54
  sqlized = [index.to_s, "'#{record}'"]
55
55
  # model's attributes
56
56
  sqlized += cols.map do |name, ctype|
57
- ctype == :integer ? attrs[name].to_s : "'#{attrs[name]}'"
57
+ case ctype
58
+ when :integer
59
+ attrs[name].to_s
60
+ when :static_record
61
+ "'#{attrs[name].class.name}'"
62
+ else
63
+ "'#{attrs[name]}'"
64
+ end
58
65
  end
59
66
 
60
67
  db.execute("INSERT INTO #{store} VALUES(#{sqlized.join(', ')})")
@@ -1,6 +1,7 @@
1
1
  module StaticRecord
2
2
  # Class that immutable model instances can inherit from
3
3
  class Base
4
+ include StaticRecord::GettersSettersConcern
4
5
  include StaticRecord::Querying
5
6
  include StaticRecord::SqliteStoringConcern
6
7
 
@@ -8,14 +9,16 @@ module StaticRecord
8
9
  :@@_columns,
9
10
  :@@_primary_key,
10
11
  :@@_path_pattern,
11
- :@@_store
12
+ :@@_store,
13
+ :@@_bound_static_tables
12
14
  ].freeze
13
15
 
14
16
  KNOWN_TYPES = [
15
17
  :string,
16
18
  :boolean,
17
19
  :integer,
18
- :float
20
+ :float,
21
+ :static_record
19
22
  ].freeze
20
23
 
21
24
  def initialize
@@ -34,30 +37,15 @@ module StaticRecord
34
37
  class_variable_set("@@#{name}", value)
35
38
  end
36
39
 
37
- def self.primary_key(name)
38
- err = RESERVED_ATTRIBUTES.include?("@@#{name}".to_sym)
39
- raise StaticRecord::ReservedAttributeName, "#{name} is a reserved name" if err
40
- class_variable_set('@@_primary_key', name)
41
- end
42
-
43
- def self.pkey
44
- class_variable_defined?(:@@_primary_key) ? class_variable_get('@@_primary_key') : nil
45
- end
46
-
47
- def self.table(store)
48
- class_variable_set('@@_store', store.to_s)
49
- end
50
-
51
- def self.store
52
- class_variable_get('@@_store')
53
- end
54
-
55
- def self.path(path)
56
- class_variable_set('@@_path_pattern', path)
57
- end
58
-
59
- def self.path_pattern
60
- class_variable_get('@@_path_pattern')
40
+ def self.reference(name, value)
41
+ attribute(name, value)
42
+ unless value.class < StaticRecord::Base
43
+ err = 'Reference only accepts StaticRecords'
44
+ raise StaticRecord::ClassError, err
45
+ end
46
+ tables = superclass.bound_static_tables
47
+ tables[value.class.store.to_sym] = name
48
+ superclass.bound_static_tables = tables
61
49
  end
62
50
 
63
51
  def attributes
@@ -5,6 +5,12 @@ module StaticRecord
5
5
  module SearchModifiers
6
6
  private
7
7
 
8
+ def joins(joint)
9
+ joint = joint.store unless [String, Symbol].include?(joint.class)
10
+ @joins_clauses << joint
11
+ self
12
+ end
13
+
8
14
  def limit(amount)
9
15
  @sql_limit = amount
10
16
  self
@@ -6,15 +6,16 @@ module StaticRecord
6
6
 
7
7
  module ClassMethods # :nodoc:
8
8
  def method_missing(method_sym, *arguments, &block)
9
- if Relation.new(nil, store: store).respond_to?(method_sym, true)
10
- Relation.new(nil, store: store, primary_key: pkey).send(method_sym, *arguments, &block)
9
+ params = relation_params
10
+ if Relation.new(nil, params).respond_to?(method_sym, true)
11
+ Relation.new(nil, params).send(method_sym, *arguments, &block)
11
12
  else
12
13
  super
13
14
  end
14
15
  end
15
16
 
16
17
  def respond_to?(method_sym, include_private = false)
17
- if Relation.new(nil, store: store).respond_to?(method_sym, true)
18
+ if Relation.new(nil, relation_params).respond_to?(method_sym, true)
18
19
  true
19
20
  else
20
21
  super
@@ -22,12 +23,22 @@ module StaticRecord
22
23
  end
23
24
 
24
25
  def respond_to_missing?(method_sym, include_private = false)
25
- if Relation.new(nil, store: store).respond_to_missing?(method_sym, true)
26
+ if Relation.new(nil, relation_params).respond_to_missing?(method_sym, true)
26
27
  true
27
28
  else
28
29
  super
29
30
  end
30
31
  end
32
+
33
+ private
34
+
35
+ def relation_params
36
+ {
37
+ klass: name,
38
+ store: store,
39
+ primary_key: pkey
40
+ }
41
+ end
31
42
  end
32
43
  end
33
44
  end
@@ -4,6 +4,7 @@ module StaticRecord
4
4
  :sql_limit,
5
5
  :sql_offset,
6
6
  :where_clauses,
7
+ :joins_clauses,
7
8
  :chain,
8
9
  :result_type,
9
10
  :order_by,
@@ -11,16 +12,19 @@ module StaticRecord
11
12
 
12
13
  include StaticRecord::QueryInterface::Interface
13
14
  include StaticRecord::QueryBuildingConcern
15
+ include StaticRecord::RequestExecutionConcern
14
16
 
15
17
  def initialize(previous_node, params)
16
18
  @store = params[:store]
17
19
  @table = params[:store]
20
+ @klass = params[:klass]
18
21
  @primary_key = params[:primary_key]
19
22
 
20
23
  @columns = '*'
21
24
  @sql_limit = nil
22
25
  @sql_offset = nil
23
26
  @where_clauses = []
27
+ @joins_clauses = []
24
28
  @chain = :and
25
29
  @result_type = :array
26
30
  @order_by = []
@@ -31,7 +35,12 @@ module StaticRecord
31
35
 
32
36
  def method_missing(method_sym, *arguments, &block)
33
37
  if respond_to?(method_sym, true)
34
- Relation.new(self, store: @store, primary_key: @primary_key).send(method_sym, *arguments, &block)
38
+ params = {
39
+ klass: @klass,
40
+ store: @store,
41
+ primary_key: @primary_key
42
+ }
43
+ Relation.new(self, params).send(method_sym, *arguments, &block)
35
44
  elsif [].respond_to?(method_sym)
36
45
  to_a.send(method_sym)
37
46
  else
@@ -58,6 +67,7 @@ module StaticRecord
58
67
  @sql_limit = relation.sql_limit
59
68
  @sql_offset = relation.sql_offset
60
69
  @where_clauses = relation.where_clauses.deep_dup
70
+ @joins_clauses = relation.joins_clauses.deep_dup
61
71
  @chain = relation.chain
62
72
  @result_type = relation.result_type
63
73
  @order_by = relation.order_by.deep_dup
@@ -74,50 +84,5 @@ module StaticRecord
74
84
  @where_clauses << clause
75
85
  @chain = :and
76
86
  end
77
-
78
- def exec_request(expectancy = :result_set)
79
- return build_query if @only_sql
80
-
81
- dbname = Rails.root.join('db', "static_#{@store}.sqlite3").to_s
82
- result = get_expected_result_from_database(dbname, expectancy)
83
- cast_result(expectancy, result)
84
- end
85
-
86
- def get_expected_result_from_database(dbname, expectancy)
87
- result = nil
88
-
89
- begin
90
- db = SQLite3::Database.open(dbname)
91
- if expectancy == :integer
92
- result = db.get_first_value(build_query)
93
- else
94
- statement = db.prepare(build_query)
95
- result_set = statement.execute
96
- result = result_set.map { |row| row[1].constantize.new }
97
- end
98
- rescue SQLite3::Exception => e
99
- error = e
100
- ensure
101
- statement.close if statement
102
- db.close if db
103
- end
104
-
105
- raise error if error
106
-
107
- result
108
- end
109
-
110
- def cast_result(expectancy, result)
111
- if expectancy == :result_set
112
- case @result_type
113
- when :array
114
- result = [] if result.nil?
115
- when :record
116
- result = result.empty? ? nil : result.first
117
- end
118
- end
119
-
120
- result
121
- end
122
87
  end
123
88
  end
@@ -1,3 +1,3 @@
1
1
  module StaticRecord
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '1.2.0'.freeze
3
3
  end
@@ -141,7 +141,7 @@ RSpec.describe StaticRecord::Relation, :type => :model do
141
141
 
142
142
  it 'accepts array of values' do
143
143
  expect(Article.where(name: ['Article One', 'Article Two']).to_a.size).to eql(2)
144
- expect(Article.where(name: ['Article One', 'Article Two']).to_sql).to eql("SELECT * FROM articles WHERE name IN (\"Article One\",\"Article Two\")")
144
+ expect(Article.where(name: ['Article One', 'Article Two']).to_sql).to eql("SELECT * FROM articles WHERE name IN ('Article One','Article Two')")
145
145
  end
146
146
 
147
147
  it 'accepts strings' do
@@ -151,17 +151,17 @@ RSpec.describe StaticRecord::Relation, :type => :model do
151
151
 
152
152
  it 'accepts strings followed by an anonymous parameters' do
153
153
  expect(Article.where("name = ?", 'Article Two').first.class).to eql(ArticleTwo)
154
- expect(Article.where("name = ?", 'Article Two').to_sql).to eql("SELECT * FROM articles WHERE name = \"Article Two\"")
154
+ expect(Article.where("name = ?", 'Article Two').to_sql).to eql("SELECT * FROM articles WHERE name = 'Article Two'")
155
155
  end
156
156
 
157
157
  it 'accepts strings followed by several anonymous parameters' do
158
158
  expect(Article.where("name = ? AND author = ?", 'Article Two', 'The author').first.class).to eql(ArticleTwo)
159
- expect(Article.where("name = ? AND author = ?", 'Article Two', 'The author').to_sql).to eql("SELECT * FROM articles WHERE name = \"Article Two\" AND author = \"The author\"")
159
+ expect(Article.where("name = ? AND author = ?", 'Article Two', 'The author').to_sql).to eql("SELECT * FROM articles WHERE name = 'Article Two' AND author = 'The author'")
160
160
  end
161
161
 
162
162
  it 'accepts strings followed by a hash of named parameters' do
163
163
  expect(Article.where("name = :name AND author = :author", {name: 'Article Two', author: 'The author'}).first.class).to eql(ArticleTwo)
164
- expect(Article.where("name = :name AND author = :author", {name: 'Article Two', author: 'The author'}).to_sql).to eql("SELECT * FROM articles WHERE name = \"Article Two\" AND author = \"The author\"")
164
+ expect(Article.where("name = :name AND author = :author", {name: 'Article Two', author: 'The author'}).to_sql).to eql("SELECT * FROM articles WHERE name = 'Article Two' AND author = 'The author'")
165
165
  end
166
166
  end
167
167
 
@@ -178,7 +178,7 @@ RSpec.describe StaticRecord::Relation, :type => :model do
178
178
  it 'is possible to chain where.not and where.not clauses' do
179
179
  request = Article.where.not(author: ['The author', 'Me']).where.not(name: 'Article Two')
180
180
  expect(request.last.class.name).to eql(ArticleThree.name)
181
- expect(request.to_sql).to eql("SELECT * FROM articles WHERE author NOT IN (\"The author\",\"Me\") AND name != 'Article Two'")
181
+ expect(request.to_sql).to eql("SELECT * FROM articles WHERE author NOT IN ('The author','Me') AND name != 'Article Two'")
182
182
  end
183
183
  end
184
184
 
@@ -207,7 +207,7 @@ RSpec.describe StaticRecord::Relation, :type => :model do
207
207
 
208
208
  it 'accepts array of values' do
209
209
  expect(Article.find(['Article One', 'Article Two']).to_a.size).to eql(2)
210
- expect(Article.see_sql_of.find(['Article One', 'Article Two'])).to eql("SELECT * FROM articles WHERE name IN (\"Article One\",\"Article Two\")")
210
+ expect(Article.see_sql_of.find(['Article One', 'Article Two'])).to eql("SELECT * FROM articles WHERE name IN ('Article One','Article Two')")
211
211
  end
212
212
 
213
213
  context 'one value' do
@@ -249,4 +249,13 @@ RSpec.describe StaticRecord::Relation, :type => :model do
249
249
  expect(Article.where(author: 'Inexisting author').or.where(author: 'The author').to_sql).to eql("SELECT * FROM articles WHERE author = 'Inexisting author' OR author = 'The author'")
250
250
  end
251
251
  end
252
+
253
+ context '.joins' do
254
+ it 'allows to use the SQL JOIN clause' do
255
+ expect(Article.joins(:categories).where("categories.name = 'Category One'").first.class).to eql(ArticleOne)
256
+ expected_sql = "SELECT * FROM articles INNER JOIN categories ON articles.category = categories.klass"
257
+ expect(Article.joins(:categories).to_sql).to eql(expected_sql)
258
+ expect(Article.joins(Category).to_sql).to eql(expected_sql)
259
+ end
260
+ end
252
261
  end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'rails_helper'
3
+
4
+ RSpec.describe StaticRecord::Base, :type => :module do
5
+ it 'can query over a static record reference' do
6
+ category = Category.find('Category One')
7
+ article = Article.where(category: category)
8
+ expect(article.first.category.class).to eql(category.class)
9
+ end
10
+ end
@@ -11,6 +11,10 @@ class Article < StaticRecord::Base
11
11
  3
12
12
  end
13
13
 
14
+ def self.default_category
15
+ Category.find('Category Two')
16
+ end
17
+
14
18
  def self.override_cover(cover)
15
19
  unless cover.to_s.include?('/')
16
20
  folder = Rails.root.join('public', 'articles', 'cover').itself
@@ -23,5 +27,6 @@ class Article < StaticRecord::Base
23
27
  author: :string,
24
28
  rank: :integer,
25
29
  important: :boolean,
26
- cover: :string
30
+ cover: :string,
31
+ category: :static_record
27
32
  end
@@ -4,4 +4,6 @@ class ArticleOne < Article
4
4
  attribute :rank, 2
5
5
  attribute :important, false
6
6
  attribute :cover, Rails.root.join('public', 'articles', 'cover', 'article_one.jpg')
7
+
8
+ reference :category, Category.find('Category One')
7
9
  end
@@ -0,0 +1,4 @@
1
+ class CategoryOne < Category
2
+ attribute :name, 'Category One'
3
+ attribute :description, 'This is a category'
4
+ end
@@ -0,0 +1,4 @@
1
+ class CategoryTwo < Category
2
+ attribute :name, 'Category Two'
3
+ attribute :description, 'This is a category'
4
+ end
@@ -0,0 +1,7 @@
1
+ class Category < StaticRecord::Base
2
+ table :categories
3
+ path Rails.root.join('app', 'models', 'categories', '**', '*.rb')
4
+ primary_key :name
5
+ columns name: :string,
6
+ description: :string
7
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: static-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hugo Chevalier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-07 00:00:00.000000000 Z
11
+ date: 2016-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -144,7 +144,9 @@ files:
144
144
  - app/views/layouts/static_record/application.html.erb
145
145
  - config/routes.rb
146
146
  - lib/static_record.rb
147
+ - lib/static_record/concerns/getters_setters_concern.rb
147
148
  - lib/static_record/concerns/query_building_concern.rb
149
+ - lib/static_record/concerns/request_execution_concern.rb
148
150
  - lib/static_record/concerns/sqlite_storing_concern.rb
149
151
  - lib/static_record/engine.rb
150
152
  - lib/static_record/exceptions.rb
@@ -167,6 +169,7 @@ files:
167
169
  - spec/models/default_value_spec.rb
168
170
  - spec/models/querying_spec.rb
169
171
  - spec/models/relation_spec.rb
172
+ - spec/models/static_record_reference_spec.rb
170
173
  - spec/rails_helper.rb
171
174
  - spec/spec_helper.rb
172
175
  - spec/test_app/app/controllers/application_controller.rb
@@ -179,6 +182,9 @@ files:
179
182
  - spec/test_app/app/models/articles/article_two.rb
180
183
  - spec/test_app/app/models/badge.rb
181
184
  - spec/test_app/app/models/badges/badge_one.rb
185
+ - spec/test_app/app/models/categories/category_one.rb
186
+ - spec/test_app/app/models/categories/category_two.rb
187
+ - spec/test_app/app/models/category.rb
182
188
  - spec/test_app/app/models/role.rb
183
189
  - spec/test_app/app/models/roles/role_one.rb
184
190
  - spec/test_app/app/models/roles/role_two.rb
@@ -234,6 +240,7 @@ test_files:
234
240
  - spec/models/default_value_spec.rb
235
241
  - spec/models/querying_spec.rb
236
242
  - spec/models/relation_spec.rb
243
+ - spec/models/static_record_reference_spec.rb
237
244
  - spec/rails_helper.rb
238
245
  - spec/spec_helper.rb
239
246
  - spec/test_app/app/controllers/application_controller.rb
@@ -246,6 +253,9 @@ test_files:
246
253
  - spec/test_app/app/models/articles/article_two.rb
247
254
  - spec/test_app/app/models/badge.rb
248
255
  - spec/test_app/app/models/badges/badge_one.rb
256
+ - spec/test_app/app/models/categories/category_one.rb
257
+ - spec/test_app/app/models/categories/category_two.rb
258
+ - spec/test_app/app/models/category.rb
249
259
  - spec/test_app/app/models/role.rb
250
260
  - spec/test_app/app/models/roles/role_one.rb
251
261
  - spec/test_app/app/models/roles/role_two.rb