activerecord-tablefree 3.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.
@@ -0,0 +1,37 @@
1
+ Given /^I delete all migrations$/ do
2
+ steps %{
3
+ When I successfully run `bash -c 'rm db/migrate/*.rb'`
4
+ }
5
+ end
6
+
7
+ Given /^I update my new user model to be tablefree$/ do
8
+ in_current_dir do
9
+ file_name = 'app/models/user.rb'
10
+ content = File.read(file_name)
11
+ if framework_version < "3.0"
12
+ content = "require 'activerecord-tablefree'\n" + content
13
+ end
14
+
15
+ content.gsub!(/^(class .* < ActiveRecord::Base)$/, "\\1\n" + <<-TABLELESS)
16
+ has_no_table
17
+ column :id, :integer
18
+ column :name, :string
19
+
20
+ TABLELESS
21
+ File.open(file_name, 'w') { |f| f << content }
22
+ end
23
+ end
24
+
25
+ Given /^I update my users controller to render instead of redirect$/ do
26
+ in_current_dir do
27
+ transform_file('app/controllers/users_controller.rb') do |content|
28
+ ##Changes in #create method
29
+ content.gsub!(/@user = User.new\((.*?)\)/,
30
+ '@user = User.new(\1); @user.id = 1')
31
+ content.gsub!("if @user.save",
32
+ "if @user.valid?")
33
+ content.gsub!(/redirect_to([\( ])@user, .*?([\)]| \}|$)/,
34
+ "render\\1:action => 'show'\\2")
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,140 @@
1
+ # TL;DR: YOU SHOULD DELETE THIS FILE
2
+ #
3
+ # This file was generated by Cucumber-Rails and is only here to get you a head start
4
+ # These step definitions are thin wrappers around the Capybara/Webrat API that lets you
5
+ # visit pages, interact with widgets and make assertions about page content.
6
+ #
7
+ # If you use these step definitions as basis for your features you will quickly end up
8
+ # with features that are:
9
+ #
10
+ # * Hard to maintain
11
+ # * Verbose to read
12
+ #
13
+ # A much better approach is to write your own higher level step definitions, following
14
+ # the advice in the following blog posts:
15
+ #
16
+ # * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html
17
+ # * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/
18
+ # * http://elabs.se/blog/15-you-re-cuking-it-wrong
19
+ #
20
+
21
+
22
+ require 'uri'
23
+ require 'cgi'
24
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
25
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors"))
26
+
27
+ module WithinHelpers
28
+ def with_scope(locator)
29
+ locator ? within(*selector_for(locator)) { yield } : yield
30
+ end
31
+ end
32
+ World(WithinHelpers)
33
+
34
+ # Single-line step scoper
35
+ When /^(.*) within (.*[^:])$/ do |step, parent|
36
+ with_scope(parent) { When step }
37
+ end
38
+
39
+ # Multi-line step scoper
40
+ When /^(.*) within (.*[^:]):$/ do |step, parent, table_or_string|
41
+ with_scope(parent) { When "#{step}:", table_or_string }
42
+ end
43
+
44
+ Given /^(?:|I )am on (.+)$/ do |page_name|
45
+ visit path_to(page_name)
46
+ end
47
+
48
+ When /^(?:|I )go to (.+)$/ do |page_name|
49
+ visit path_to(page_name)
50
+ end
51
+
52
+ When /^(?:|I )press "([^"]*)"$/ do |button|
53
+ click_button(button)
54
+ end
55
+
56
+ When /^(?:|I )follow "([^"]*)"$/ do |link|
57
+ click_link(link)
58
+ end
59
+
60
+ When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value|
61
+ fill_in(field, :with => value)
62
+ end
63
+
64
+ When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field|
65
+ fill_in(field, :with => value)
66
+ end
67
+
68
+ # Use this to fill in an entire form with data from a table. Example:
69
+ #
70
+ # When I fill in the following:
71
+ # | Account Number | 5002 |
72
+ # | Expiry date | 2009-11-01 |
73
+ # | Note | Nice guy |
74
+ # | Wants Email? | |
75
+ #
76
+ # TODO: Add support for checkbox, select og option
77
+ # based on naming conventions.
78
+ #
79
+ When /^(?:|I )fill in the following:$/ do |fields|
80
+ fields.rows_hash.each do |name, value|
81
+ When %{I fill in "#{name}" with "#{value}"}
82
+ end
83
+ end
84
+
85
+ When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
86
+ select(value, :from => field)
87
+ end
88
+
89
+ When /^(?:|I )check "([^"]*)"$/ do |field|
90
+ check(field)
91
+ end
92
+
93
+ When /^(?:|I )uncheck "([^"]*)"$/ do |field|
94
+ uncheck(field)
95
+ end
96
+
97
+ When /^(?:|I )choose "([^"]*)"$/ do |field|
98
+ choose(field)
99
+ end
100
+
101
+ When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
102
+ attach_file(field, File.expand_path(path))
103
+ end
104
+
105
+ Then /^(?:|I )should (not )?see "([^"]*)"$/ do |negate, text|
106
+ should_name = negate ? :should_not : :should
107
+ if page.respond_to? should_name
108
+ page.send should_name, have_content(text)
109
+ else
110
+ assert(negate ? page.has_no_content?(text) : page.has_content?(text))
111
+ end
112
+ end
113
+
114
+ Then /^(?:|I )should (not )?see \/([^\/]*)\/$/ do |negate, regexp|
115
+ regexp = Regexp.new(regexp)
116
+ should_name = negate ? :should_not : :should
117
+ if page.respond_to? should_name
118
+ page.send should_name, have_xpath('//*', :text => regexp)
119
+ else
120
+ assert(negate ? page.has_no_xpath?('//*', :text => regexp) : page.has_xpath?('//*', :text => regexp))
121
+ end
122
+ end
123
+
124
+ Then /^(?:|I )should be on (.+)$/ do |page_name|
125
+ current_path = URI.parse(current_url).path
126
+ expect(current_path).to eq path_to(page_name)
127
+ end
128
+
129
+ Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
130
+ query = URI.parse(current_url).query
131
+ actual_params = query ? CGI.parse(query) : {}
132
+ expected_params = {}
133
+ expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
134
+
135
+ expect(actual_params).to eq expected_params
136
+ end
137
+
138
+ Then /^show me the page$/ do
139
+ save_and_open_page
140
+ end
@@ -0,0 +1,15 @@
1
+ #require 'bundler'
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts "Run `bundle install` to install missing gems"
7
+ exit e.status_code
8
+ end
9
+
10
+ require 'aruba/cucumber'
11
+ require 'capybara/cucumber'
12
+
13
+ Before do
14
+ @aruba_timeout_seconds = 120
15
+ end
@@ -0,0 +1,28 @@
1
+ module NavigationHelpers
2
+ # Maps a name to a path. Used by the
3
+ #
4
+ # When /^I go to (.+)$/ do |page_name|
5
+ #
6
+ # step definition in web_steps.rb
7
+ #
8
+ def path_to(page_name)
9
+ case page_name
10
+
11
+ when /the home\s?page/
12
+ '/'
13
+ when /the new user page/
14
+ '/users/new'
15
+ else
16
+ begin
17
+ page_name =~ /the (.*) page/
18
+ path_components = $1.split(/\s+/)
19
+ self.send(path_components.push('path').join('_').to_sym)
20
+ rescue Object => e
21
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
22
+ "Now, go and add a mapping in #{__FILE__}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ World(NavigationHelpers)
@@ -0,0 +1,52 @@
1
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
2
+ APP_NAME = 'testapp'.freeze
3
+ BUNDLE_ENV_VARS = %w(RUBYOPT BUNDLE_PATH BUNDLE_BIN_PATH BUNDLE_GEMFILE)
4
+ ORIGINAL_BUNDLE_VARS = Hash[ENV.select{ |key,value| BUNDLE_ENV_VARS.include?(key) }]
5
+
6
+ ENV['RAILS_ENV'] = 'test'
7
+
8
+ Before do
9
+ ENV['BUNDLE_GEMFILE'] = File.join(Dir.pwd, ENV['BUNDLE_GEMFILE']) unless ENV['BUNDLE_GEMFILE'].start_with?(Dir.pwd)
10
+ @framework_version = nil
11
+ end
12
+
13
+ After do
14
+ ORIGINAL_BUNDLE_VARS.each_pair do |key, value|
15
+ ENV[key] = value
16
+ end
17
+ end
18
+
19
+ When /^I reset Bundler environment variable$/ do
20
+ BUNDLE_ENV_VARS.each do |key|
21
+ ENV[key] = nil
22
+ end
23
+ end
24
+
25
+ module RailsCommandHelpers
26
+ def framework_version?(version_string)
27
+ framework_version =~ /^#{version_string}/
28
+ end
29
+
30
+ def framework_version
31
+ @framework_version ||= `rails -v`[/^Rails (.+)$/, 1]
32
+ end
33
+
34
+ def framework_major_version
35
+ framework_version.split(".").first.to_i
36
+ end
37
+
38
+ def new_application_command(app_name)
39
+ framework_major_version >= 3 ? "rails new #{app_name} --skip-sprockets --skip-javascript --skip-bundle" : "rails #{app_name}"
40
+ end
41
+
42
+ def generator_command
43
+ framework_major_version >= 3 ? "rails generate" : "script/generate"
44
+ end
45
+
46
+ def runner_command
47
+ framework_major_version >= 3 ? "rails runner" : "script/runner"
48
+ end
49
+
50
+ end
51
+
52
+ World(RailsCommandHelpers)
@@ -0,0 +1,19 @@
1
+ module HtmlSelectorsHelpers
2
+ # Maps a name to a selector. Used primarily by the
3
+ #
4
+ # When /^(.+) within (.+)$/ do |step, scope|
5
+ #
6
+ # step definitions in web_steps.rb
7
+ #
8
+ def selector_for(locator)
9
+ case locator
10
+ when "the page"
11
+ "html > body"
12
+ else
13
+ raise "Can't find mapping from \"#{locator}\" to a selector.\n" +
14
+ "Now, go and add a mapping in #{__FILE__}"
15
+ end
16
+ end
17
+ end
18
+
19
+ World(HtmlSelectorsHelpers)
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "mime-types", "< 2.0", :platform => :ruby_18
6
+ gem "rubyzip", "< 1.0", :platform => :ruby_18
7
+ gem "byebug"
8
+ gem "rails", "~> 5.0.0"
9
+ gem "jquery-rails"
10
+
11
+ gemspec :path => "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "mime-types", "< 2.0", :platform => :ruby_18
6
+ gem "rubyzip", "< 1.0", :platform => :ruby_18
7
+ gem "byebug"
8
+ gem "rails", "~> 5.1.0"
9
+ gem "jquery-rails"
10
+
11
+ gemspec :path => "../"
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ # For use by Railed and de-Railed applications alike
2
+
3
+ require 'active_record'
4
+ require File.join(File.dirname(__FILE__), 'lib', 'activerecord-tablefree')
@@ -0,0 +1,318 @@
1
+ # See #ActiveRecord::Tablefree
2
+ require 'activerecord-tablefree/version'
3
+
4
+ module ActiveRecord
5
+
6
+ # = ActiveRecord::Tablefree
7
+ #
8
+ # Allow classes to behave like ActiveRecord models, but without an associated
9
+ # database table. A great way to capitalize on validations. Based on the
10
+ # original post at http://www.railsweenie.com/forums/2/topics/724 (which seems
11
+ # to have disappeared from the face of the earth).
12
+ #
13
+ # = Example usage
14
+ #
15
+ # class ContactMessage < ActiveRecord::Base
16
+ #
17
+ # has_no_table
18
+ #
19
+ # column :name, :string
20
+ # column :email, :string
21
+ # column :message, :string
22
+ #
23
+ # end
24
+ #
25
+ # msg = ContactMessage.new( params[:msg] )
26
+ # if msg.valid?
27
+ # ContactMessageSender.deliver_message( msg )
28
+ # redirect_to :action => :sent
29
+ # end
30
+ #
31
+ module Tablefree
32
+ require 'active_record'
33
+
34
+ class NoDatabase < StandardError; end
35
+ class Unsupported < StandardError; end
36
+
37
+ def self.included( base ) #:nodoc:
38
+ base.send :extend, ActsMethods
39
+ end
40
+
41
+ module ActsMethods #:nodoc:
42
+
43
+ # A model that needs to be tablefree will call this method to indicate
44
+ # it.
45
+ def has_no_table(options = {:database => :fail_fast})
46
+ raise ArgumentError.new("Invalid database option '#{options[:database]}'") unless [:fail_fast, :pretend_success].member? options[:database]
47
+ # keep our options handy
48
+ class_attribute :tablefree_options
49
+ self.tablefree_options = {
50
+ :database => options[:database],
51
+ :columns_hash => {}
52
+ }
53
+
54
+ # extend
55
+ extend ActiveRecord::Tablefree::SingletonMethods
56
+ extend ActiveRecord::Tablefree::ClassMethods
57
+
58
+ # include
59
+ include ActiveRecord::Tablefree::InstanceMethods
60
+
61
+ # setup columns
62
+ include ActiveModel::AttributeAssignment
63
+ include ActiveRecord::ModelSchema
64
+ end
65
+
66
+ def tablefree?
67
+ false
68
+ end
69
+
70
+ end
71
+
72
+ module SingletonMethods
73
+
74
+ # Used internally by ActiveRecord 5. This is the special hook that makes everything else work.
75
+ def load_schema!
76
+ @columns_hash = tablefree_options[:columns_hash].except(*ignored_columns)
77
+ @columns_hash.each do |name, column|
78
+ define_attribute(
79
+ name,
80
+ connection.lookup_cast_type_from_column(column),
81
+ default: column.default,
82
+ user_provided_default: false
83
+ )
84
+ end
85
+ end
86
+
87
+ # Register a new column.
88
+ def column(name, sql_type = nil, default = nil, null = true)
89
+ cast_type = "ActiveRecord::Type::#{sql_type.to_s.camelize}".constantize.new
90
+ tablefree_options[:columns_hash][name.to_s] = ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, cast_type, sql_type.to_s, null)
91
+ end
92
+
93
+ # Register a set of columns with the same SQL type
94
+ def add_columns(sql_type, *args)
95
+ args.each do |col|
96
+ column col, sql_type
97
+ end
98
+ end
99
+
100
+ def destroy(*args)
101
+ case tablefree_options[:database]
102
+ when :pretend_success
103
+ self.new()
104
+ when :fail_fast
105
+ raise NoDatabase.new("Can't #destroy on Tablefree class")
106
+ end
107
+ end
108
+
109
+ def destroy_all(*_args)
110
+ case tablefree_options[:database]
111
+ when :pretend_success
112
+ []
113
+ when :fail_fast
114
+ raise NoDatabase.new("Can't #destroy_all on Tablefree class")
115
+ end
116
+ end
117
+
118
+ case ActiveRecord::VERSION::MAJOR
119
+ when 5
120
+ def find_by_sql(*args)
121
+ case tablefree_options[:database]
122
+ when :pretend_success
123
+ []
124
+ when :fail_fast
125
+ raise NoDatabase.new("Can't #find_by_sql on Tablefree class")
126
+ end
127
+
128
+ end
129
+ else
130
+ raise Unsupported.new("Unsupported ActiveRecord version")
131
+ end
132
+
133
+ def transaction(&block)
134
+ # case tablefree_options[:database]
135
+ # when :pretend_success
136
+ @_current_transaction_records ||= []
137
+ yield
138
+ # when :fail_fast
139
+ # raise NoDatabase.new("Can't #transaction on Tablefree class")
140
+ # end
141
+ end
142
+
143
+ def tablefree?
144
+ true
145
+ end
146
+
147
+ def table_exists?
148
+ false
149
+ end
150
+ end
151
+
152
+ module ClassMethods
153
+
154
+ def from_query_string(query_string)
155
+ unless query_string.blank?
156
+ params = query_string.split('&').collect do |chunk|
157
+ next if chunk.empty?
158
+ key, value = chunk.split('=', 2)
159
+ next if key.empty?
160
+ value = value.nil? ? nil : CGI.unescape(value)
161
+ [ CGI.unescape(key), value ]
162
+ end.compact.to_h
163
+
164
+ new(params)
165
+ else
166
+ new
167
+ end
168
+ end
169
+
170
+ def connection
171
+ conn = Object.new()
172
+ def conn.quote_table_name(*_args)
173
+ ""
174
+ end
175
+ def conn.quote_column_name(*_args)
176
+ ""
177
+ end
178
+ def conn.substitute_at(*_args)
179
+ nil
180
+ end
181
+ def conn.schema_cache(*_args)
182
+ schema_cache = Object.new()
183
+ def schema_cache.columns_hash(*_args)
184
+ Hash.new()
185
+ end
186
+ schema_cache
187
+ end
188
+ # Fixes Issue #17. https://github.com/softace/activerecord-tablefree/issues/17
189
+ # The following method is from the ActiveRecord gem: /lib/active_record/connection_adapters/abstract/database_statements.rb .
190
+ # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
191
+ #
192
+ # The +limit+ may be anything that can evaluate to a string via #to_s. It
193
+ # should look like an integer, or a comma-delimited list of integers, or
194
+ # an Arel SQL literal.
195
+ #
196
+ # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
197
+ # Returns the sanitized limit parameter, either as an integer, or as a
198
+ # string which contains a comma-delimited list of integers.
199
+ def conn.sanitize_limit(limit)
200
+ if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
201
+ limit
202
+ elsif limit.to_s.include?(',')
203
+ Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
204
+ else
205
+ Integer(limit)
206
+ end
207
+ end
208
+
209
+ # Called by bound_attributes in /lib/active_record/relation/query_methods.rb
210
+ # Returns a SQL string with the from, join, where, and having clauses, in addition to the limit and offset.
211
+ def conn.combine_bind_parameters(**_args)
212
+ ""
213
+ end
214
+
215
+ def conn.lookup_cast_type_from_column(*_args)
216
+ lct = Object.new
217
+ def lct.assert_valid_value(*_args)
218
+ true
219
+ end
220
+ # Needed for Rails 5.0
221
+ def lct.serialize(args)
222
+ args
223
+ end
224
+ def lct.deserialize(args)
225
+ args
226
+ end
227
+ def lct.cast(args)
228
+ args
229
+ end
230
+ def lct.changed?(*_args)
231
+ false
232
+ end
233
+ def lct.changed_in_place?(*_args)
234
+ false
235
+ end
236
+ lct
237
+ end
238
+
239
+ # This is used in the StatementCache object. It returns an object that
240
+ # can be used to query the database repeatedly.
241
+ def conn.cacheable_query(arel) # :nodoc:
242
+ if prepared_statements
243
+ ActiveRecord::StatementCache.query visitor, arel.ast
244
+ else
245
+ ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
246
+ end
247
+ end
248
+ conn
249
+ end
250
+
251
+ end
252
+
253
+ module InstanceMethods
254
+
255
+ def to_query_string(prefix = nil)
256
+ attributes.to_a.collect{|(name,value)| escaped_var_name(name, prefix) + "=" + escape_for_url(value) if value }.compact.join("&")
257
+ end
258
+
259
+ def quote_value(_value, _column = nil)
260
+ ""
261
+ end
262
+
263
+ %w(create create_record _create_record update update_record _update_record).each do |method_name|
264
+ define_method(method_name) do |*args|
265
+ case self.class.tablefree_options[:database]
266
+ when :pretend_success
267
+ true
268
+ when :fail_fast
269
+ raise NoDatabase.new("Can't ##{method_name} a Tablefree object")
270
+ end
271
+ end
272
+ end
273
+
274
+ def destroy
275
+ case self.class.tablefree_options[:database]
276
+ when :pretend_success
277
+ @destroyed = true
278
+ freeze
279
+ when :fail_fast
280
+ raise NoDatabase.new("Can't #destroy a Tablefree object")
281
+ end
282
+ end
283
+
284
+ def reload(*args)
285
+ case self.class.tablefree_options[:database]
286
+ when :pretend_success
287
+ self
288
+ when :fail_fast
289
+ raise NoDatabase.new("Can't #reload a Tablefree object")
290
+ end
291
+ end
292
+
293
+ def add_to_transaction
294
+ end
295
+
296
+ private
297
+
298
+ def escaped_var_name(name, prefix = nil)
299
+ prefix ? "#{URI.escape(prefix)}[#{URI.escape(name)}]" : URI.escape(name)
300
+ end
301
+
302
+ def escape_for_url(value)
303
+ case value
304
+ when true then "1"
305
+ when false then "0"
306
+ when nil then ""
307
+ else URI.escape(value.to_s)
308
+ end
309
+ rescue
310
+ ""
311
+ end
312
+
313
+ end
314
+
315
+ end
316
+ end
317
+
318
+ ActiveRecord::Base.send( :include, ActiveRecord::Tablefree )