Programmatically get all database column types - ruby-on-rails

I am building a Rails gem for which I might need to know the currently available column types. So say for Postgres, I am looking for something like: ActiveRecord::Base.available_column_types. I looked through the source with no success so far.

I can't find an ActiveRecord method to get what you want. But I can show you two ways you can achieve this:
With any path you need to create an initializer and Monkey Patch ActiveRecord. For example: /config/initializers/active_record_extensions.rb. Then, the options:
OPTION 1: get data types based on your models
class ActiveRecord::Base
def self.available_column_types
types = []
ActiveRecord::Base.subclasses.collect{ |type| type.name }.each do |model_name|
types += eval("#{model_name}.columns.map(&:type)")
end
types.uniq
end
end
Then you can do rails console on your terminal and write:
irb(main):001:0> User.available_column_types
=> [:integer, :string, :text, :datetime, :boolean, :date, :hstore]
irb(main):002:0> ActiveRecord::Base.available_column_types
=> [:integer, :string, :text, :datetime, :boolean, :date, :hstore]
irb(main):003:0>
OPTION 2: get all posible data types based on you db adapter
class ActiveRecord::Base
if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) and
ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
types = ActiveRecord::Base.connection.execute("select * from pg_type;")
return types.inject([]) { |result, record| result << record["typname"] }
# Too much info on pg_type table, you can get whatever you need.
end
if defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter) and
ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
# I don't know, it's just an example. Yo can add all adapters you want
return
end
# maybe raise an Exception with NO ADAPTER! message
end
end
Once again, on your console, you can do ActiveRecord::Base.available_column_types to see the result.
Note: you need to adapt this in order to make it work with your gem.

Related

Ransack + FlagShihTzu + Active Admin don't play well together

I'm using the brilliant gem flag_shih_tzu to create bitwise boolean flags on a single integer column without requiring a separate DB column for each flag. I have loved this gem for many years now, and it's quite excellent at interplaying with ActiveRecord attributes in all the ways you would normally expect.
However, it does not play well with Ransack and Active Admin out of the box. Active Admin requires me to add permitted params for each flag:
permit_params do
:identity_verified
end
for the :identity_verified "flag" attribute to even show up in filters or index columns, which is fine; I don't mind that. But the real problem I'm having is when I try to use the :identity_verified flag as a filter (it's boolean, of course), Active Admin shows the normal select option for it with Any/Yes/No, but when I first submitted the filter query, I got an exception: undefined method identity_verified_eq for Ransack::Search.
Ok, so I did some research and figured out that I need to add a ransacker for :identity_verified. Did that, and I don't get the exception anymore, but the ransacker doesn't appear to do anything at all. In fact, I intentionally put a syntax error in the block to cause an exception, but Active Admin just returns all the Users, regardless of whether they're :identity_verified or not. That code inside the ransacker block doesn't seem to even get executed. Can anyone help me figure out how to create a proper Ransack definition for :identity_verified?
Here's the code:
User Model :
# ===============
# = FlagShihTzu =
# ===============
has_flags 1 => :guest,
2 => :prospect,
3 => :phone_verified,
4 => :identity_verified,
# ==============
# = Ransackers =
# ==============
# this avoids the identity_verified_eq missing method exception, but
# doesn't appear to do anything else
ransacker :identity_verified, args: [:ransacker_args] do |args|
asdf # <-- should cause an exception
Arel.sql(identity_verified_condition)
end
Active Admin:
ActiveAdmin.register User do
permit_params do
:identity_verified
end
# ...
filter :identity_verified, as: :boolean
# ...
end
The Identity Verified filter shows up as a boolean select in Active Admin, like I expect, but when I submit the filter, (as I metioned above), I get all the Users back, and the ransacker block doesn't even seem to get executed. I've read the Ransack examples. I've dug into the code of all four gems (including Formtastic), and I still can't sort it out.
Here's the POST URL from Active Admin on query submit:
http://example.com/admin/users?q%5Bidentity_verified%5D=true&commit=Filter&order=id_desc
Here's the Rails log to confirm the :identity_verified param is getting passed:
Processing by Admin::UsersController#index as HTML
Parameters: {"utf8"=>"✓", "q"=>{"identity_verified"=>"true"}, "commit"=>"Filter", "order"=>"id_desc"}
Again, the inside of the ransacker block doesn't seem to get executed. I need to understand why, and if it ever does, how to write the proper Arel statement so I can filter on this flag.
Help?
Resurrecting this question as it is the first result on G* here searching for "flag shih tzu activeadmin". Plus it seems OP's solution is not ideal in that it loads & instantiates AR objects for all records fulfilling the flag condition in this part:
results = object_class.send("#{flag}")
results = results.map(&:id)
So here's my current solution for others:
# config/initializers/ransack.rb
Ransack.configure do |config|
config.add_predicate 'flag_equals',
arel_predicate: 'eq',
formatter: proc { |v| (v.downcase == 'true') ? 1 : 0 },
validator: proc { |v| v.present? },
type: :string
end
# app/concerns/has_flags.rb
module HasFlags
extend ActiveSupport::Concern
included { include FlagShihTzu }
class_methods do
def flag_ransacker(flag_name, flag_col: 'flags')
ransacker(flag_name) do |parent|
Arel::Nodes::InfixOperation.new('DIV',
Arel::Nodes::InfixOperation.new('&', parent.table[flag_col], flag_mapping[flag_col][flag_name]),
flag_mapping[flag_col][flag_name])
end
end
end
end
# app/models/foo.rb
class Foo < ActiveRecord::Base
include HasFlags
has_flags 1 => :bar, 2 => :baz
flag_ransacker :bar
end
# app/admin/foos.rb
ActiveAdmin.register Foo do
filter :bar_flag_equals, as: :boolean, label: 'Bar'
end
So, I finally figured out the answer after luckily stumbling on to this post ActiveAdmin Filters with Ransack. The gist of it is properly defining the Active Admin filter using the DSL and more importantly, the appropriate ransacker in the model for the FlagShihTzu flag you want to filter on.
Here's a working example:
models/user.rb:
class User
include FlagShihTzu
# define the flag
has_flags 1 => :identity_verified
# convenience method to define the necessary ransacker for a flag
def self.flag_ransacker(flag)
ransacker flag.to_sym,
formatter: proc { |true_false|
if true_false == "true"
results = object_class.send("#{flag}")
else
results = object_class.send("not_#{flag}")
end
results = results.map(&:id)
results = results.present? ? results : nil
}, splat_params: true do |parent|
parent.table[:id]
end
end
admin/user.rb:
ActiveAdmin.register User do
# A method used like a standard ActiveAdmin::Resource `filter` DSL call, but for FlagShizTzu flags
# A corresponding `flag_ransacker` call must be made on the model, which must include
# the FlagShizTzuRansack module defined in app/concerns/models/flag_shih_tzu_ransack.rb
def flag_filter(flag)
#resource.flag_ransacker flag.to_sym # call the ransacker builder on the model
flag = flag.to_s
filter_name = "#{flag}_in" # we use the 'in' predicate to allow multiple results
filter filter_name.to_sym,
:as => :select,
:label => flag.gsub(/[\s_]+/, ' ').titleize,
:collection => %w[true false]
end
flag_filter :identity_verified
end
And voila, a working sidebar filter for flag-shih-tzu flags. The key was adding the in predicate at the end of the flag name in the filter declarion, instead of excluding it, which defaults to the eq Ransack predicate. Defining the ransacker itself took trial and error using pry and the debugger, but was based largely on the aforementioned post.
Ultimately, I ended up pulling out the inline methods in the two files into modules that I include in the necessary models and AA resource definitions that need them.
app/concerns/models/flag_shih_tzu_ransack.rb:
# Used to define Ransackers for ActiveAdmin FlagShizTzu filters
# See app/admin/support/flag_shih_tzu.rb
module FlagShihTzuRansack
extend ActiveSupport::Concern
module ClassMethods
# +flags are one or more FlagShihTzu flags that need to have ransackers defined for
# ActiveAdmin filtering
def flag_ransacker(*flags)
object_class = self
flags.each do |flag|
flag = flag.to_s
ransacker flag.to_sym,
formatter: proc { |true_false|
if true_false == "true"
results = object_class.send("#{flag}")
else
results = object_class.send("not_#{flag}")
end
results = results.map(&:id)
results = results.present? ? results : nil
}, splat_params: true do |parent|
parent.table[:id]
end
end
end
end
end
app/admin/support/flag_shih_tzu.rb:
# Convenience extension to filter on FlagShizTzu flags in the AA side_bar
module Kandidly
module ActiveAdmin
module DSL
# used like a standard ActiveAdmin::Resource `filter` DSL call, but for FlagShizTzu flags
# A corresponding `flag_ransacker` call must be made on the model, which must include
# the FlagShizTzuRansack module defined in app/concerns/models/flag_shih_tzu_ransack.rb
def flag_filter(flag)
#resource.flag_ransacker flag.to_sym # call the ransacker builder on the model
flag = flag.to_s
filter_name = "#{flag}_in" # we use the 'in' predicate to allow multiple results
filter filter_name.to_sym,
:as => :select,
:label => flag.gsub(/[\s_]+/, ' ').titleize,
:collection => %w[true false]
end
end
end
end
ActiveAdmin::ResourceDSL.send :include, Kandidly::ActiveAdmin::DSL
Then more cleanly, in the model:
class User
include FlagShihTzu
include FlagShihTzuRansack
# define the flag
has_flags 1 => :identity_verified
end
and in the resource definition:
ActiveAdmin.register User do
flag_filter :identity_verified
end
There are probably more elegant implementations of the methods, but having a working solution, I'm moving on. Hope this helps whoever up-voted this question. Ransack documentation leaves a bit to be desired. Thanks to Russ for his post on Jaguar Design Studio, and to the commenters on https://github.com/activerecord-hackery/ransack/issues/36, who helped me better understand how Ransack works. In the end I had to dig into the gems to get my final solution, but I would not have known where to start without their contributions.

ActiveAdmin Filter on postgres Array field

I added the following filter in ActiveAdmin.
filter :roles, as: :select, collection Model::ROLES, multiple: true
but when i choose the filter value to search the roles. it gives me following error
PG::InvalidTextRepresentation: ERROR: malformed array literal: "teacher"LINE 1: ...ted" = $1 AND roles" IN('teacher
DETAIL: Array value must start with "{" or dimension information. ^
Any idea ? How we can search/Filter ARRAY field using AA filters? I'm using Rails 4.2.4,
ruby 2.2.2p95
I came up to a solution slightly different (and inspired by) this one over here: https://stackoverflow.com/a/45728004/1170086
Mine involves some changes (and prevent breaking contains operator in other cases). So, you're going to basically create two initializer files:
This one is for Arel, in order to support #> operator (array's contain operator in PG) for a given table column.
# config/initializers/arel.rb
module Arel
class Nodes::ContainsArray < Arel::Nodes::Binary
def operator
:"#>"
end
end
class Visitors::PostgreSQL
private
def visit_Arel_Nodes_ContainsArray(o, collector)
infix_value o, collector, ' #> '
end
end
module Predications
def contains(other)
Nodes::ContainsArray.new self, Nodes.build_quoted(other, self)
end
end
end
The other file aims to create a new Ransack predicate but I also decided to support the :array type (that's not natively supported in Ransack in terms of predicates).
# config/initializers/ransack.rb
module Ransack
module Nodes
class Value < Node
alias_method :original_cast, :cast
def cast(type)
return Array(value) if type == :array
original_cast(type)
end
end
end
end
Ransack.configure do |config|
config.add_predicate 'contains_array',
arel_predicate: 'contains',
formatter: proc { |v| "{#{v.join(',')}}" },
validator: proc { |v| v.present? },
type: :array
end
And in other to use it. All you need to do is:
User.ransack(roles_contains_array: %i[admin manager])
Or as a filter in ActiveAdmin (which is my case):
ActiveAdmin.register User do
# ...
filter :roles_contains_array, as: :select, collection: User.roles_for_select
# ...
end
I hope it works for you as it worked for me. ;)
You can set up a custom ransacker method to first collect the ids you want returned using a regular postgres search, and then return the results based on those ids:
class User < ApplicationRecord
ransacker :roles,
formatter: proc { |str|
data = where("? = ANY (roles)", str).map(&:id)
data.present? ? data : nil
} do |parent|
parent.table[:id]
end
end
If your filter is a select drop-down, then this should work fine. If you have a free-form text box, then make sure to use the "in" predicate:
filter :roles_in, as: :string
leandroico solutions works well.
But if you add the predicate with this formatter
formatter: proc { |v| "{#{v.join(', ')}}" }, (note the space after the comma)
Then you could use the multiple: true keyword in the filter input and filter by more than one value:
filter :roles_contains_array, as: :select, multiple: true, collection: User.roles_for_select
I used the answer from #leandroico to come up with the below wiki-type approach to doing this.
How to Create Custom SQL Searches for ActiveAdmin (using Arel and Ransack)
In ActiveAdmin, filters are declared in app/admin/model.rb like:
ActiveAdmin.register Model do
filter 'column_name', label: 'column_name', as: :string
end
That will make a searchbox available on the front-end with options to choose between
contains
equals
starts with
ends with
You can even do something like...
filter 'column_name_contains', label: 'column_name', as: :string
...to only have a contains type search available on the front-end.
You can also (after defining some custom methods elsewhere) specify other, non-built-in search methods, like:
filter 'column_name_custom_contains', label: 'column_name', as: :string
The rest of this doc will be about how to define this custom search method, custom_contains
Within config/initializers/arel.rb, define the following:
module Arel
# this example of custom_contains will cast the SQL column as ::text and then do a wildcard-wrapped ILIKE
class Nodes::CustomContains < Arel::Nodes::Binary
def operator
'::text ILIKE'.to_sym
end
end
class Visitors::PostgreSQL
private
def visit_Arel_Nodes_CustomContains(o, collector)
infix_value o, collector, '::text ILIKE '
end
end
module Predications
def custom_contains(column_value)
column_value = self.relation.engine.column_types[self.name.to_s].type_cast_for_database(column_value)
column_value = "%#{self.relation.engine.send(:sanitize_sql_like, column_value)}%" # wrap escaped value with % wildcard
column_value = Nodes.build_quoted(column_value, self)
Nodes::CustomContains.new(self, column_value)
end
end
end
module ActiveRecord::QueryMethods
def custom_contains(predicates)
return none if predicates.length == 0
predicates.map{ |column_name, column_value|
column_value = table.engine.column_types[column_name.to_s].type_cast_for_database(column_value)
column_value = "%#{table.engine.send(:sanitize_sql_like, column_value)}%" # wrap escaped value with % wildcard
column_value = Arel::Nodes.build_quoted(column_value)
where Arel::Nodes::CustomContains.new(table[column_name], column_value)
}.inject(:merge)
end
end
module ActiveRecord::Querying
delegate :custom_contains, :to => :all
end
Within config/initializers/ransack.rb, define the following:
Ransack.configure do |config|
config.add_predicate(
'custom_contains',
arel_predicate: 'custom_contains',
formatter: proc { |v| v.to_s },
validator: proc { |v| v.present? },
type: :string
)
end
The above has accomplished a couple of things:
1) You can use the custom_contains method that was delegate'd to all ActiveRecord models:
puts Model.custom_contains(column_name: 'search for me').to_sql
2) You can use Ransack to search against the Arel predicates that were defined:
puts Model.ransack(column_name_custom_contains: 'search for me').result.to_sql
However, in order to do the below in ActiveAdmin...
filter 'column_name_custom_contains', label: 'column_name', as: :string
...we must add a scope to Model so that there is a method, column_name_custom_contains, on Model
scope_name = "#{column_name}_custom_contains".to_sym
unless Model.methods.include?(scope_name)
Model.scope(
scope_name,
->(value) {
Model.custom_contains({column_name.to_sym => value})
}
)
end
Voila!

Getting rails3-autocomplete-jquery gem to work nicely with Simple_Form with multiple inputs

So I am trying to implement multiple autocomplete using this gem and simple_form and am getting an error.
I tried this:
<%= f.input_field :neighborhood_id, collection: Neighborhood.order(:name), :url => autocomplete_neighborhood_name_searches_path, :as => :autocomplete, 'data-delimiter' => ',', :multiple => true, :class => "span8" %>
This is the error I get:
undefined method `to_i' for ["Alley Park, Madison"]:Array
In my params, it is sending this in neighborhood_id:
"search"=>{"neighborhood_id"=>["Alley Park, Madison"],
So it isn't even using the IDs for those values.
Does anyone have any ideas?
Edit 1:
In response to #jvnill's question, I am not explicitly doing anything with params[:search] in the controller. A search creates a new record, and is searching listings.
In my Searches Controller, create action, I am simply doing this:
#search = Search.create!(params[:search])
Then my search.rb (i.e. search model) has this:
def listings
#listings ||= find_listings
end
private
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline)
listings = listings.includes(:neighborhood).where("listings.headline like ? or neighborhoods.name like ?", key, key) if keywords.present?
listings = listings.where(neighborhood_id: neighborhood_id) if neighborhood_id.present?
#truncated for brevity
listings
end
First of all, this would be easier if the form is returning the ids instead of the name of the neighborhood. I haven't used the gem yet so I'm not familiar how it works. Reading on the readme says that it will return ids but i don't know why you're only getting names. I'm sure once you figure out how to return the ids, you'll be able to change the code below to suit that.
You need to create a join table between a neighborhood and a search. Let's call that search_neighborhoods.
rails g model search_neighborhood neighborhood_id:integer search_id:integer
# dont forget to add indexes in the migration
After that, you'd want to setup your models.
# search.rb
has_many :search_neighborhoods
has_many :neighborhoods, through: :search_neighborhoods
# search_neighborhood.rb
belongs_to :search
belongs_to :neighborhood
# neighborhood.rb
has_many :search_neighborhoods
has_many :searches, through: :search_neighborhoods
Now that we've setup the associations, we need to setup the setters and the attributes
# search.rb
attr_accessible :neighborhood_names
# this will return a list of neighborhood names which is usefull with prepopulating
def neighborhood_names
neighborhoods.map(&:name).join(',')
end
# we will use this to find the ids of the neighborhoods given their names
# this will be called when you call create!
def neighborhood_names=(names)
names.split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
# view
# you need to change your autocomplete to use the getter method
<%= f.input :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, input_html: { data: { delimiter: ',', multiple: true, class: "span8" } %>
last but not the least is to update find_listings
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline).includes(:neighborhood)
if keywords.present?
listings = listings.where("listings.headline LIKE :key OR neighborhoods.name LIKE :key", { key: "#{keywords}")
end
if neighborhoods.exists?
listings = listings.where(neighborhood_id: neighborhood_ids)
end
listings
end
And that's it :)
UPDATE: using f.input_field
# view
<%= f.input_field :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, data: { delimiter: ',' }, multiple: true, class: "span8" %>
# model
# we need to put [0] because it returns an array with a single element containing
# the string of comma separated neighborhoods
def neighborhood_names=(names)
names[0].split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
Your problem is how you're collecting values from the neighborhood Model
Neighborhood.order(:name)
will return an array of names, you need to also collect the id, but just display the names
use collect and pass a block, I beleive this might owrk for you
Neighborhood.collect {|n| [n.name, n.id]}
Declare a scope on the Neighborhood class to order it by name if you like to get theat functionality back, as that behavior also belongs in the model anyhow.
edit>
To add a scope/class method to neighborhood model, you'd typically do soemthing like this
scope :desc, where("name DESC")
Than you can write something like:
Neighborhood.desc.all
which will return an array, thus allowing the .collect but there are other way to get those name and id attributes recognized by the select option.

Globalize2 and migrations

I have used globalize2 to add i18n to an old site. There is already a lot of content in spanish, however it isn't stored in globalize2 tables. Is there a way to convert this content to globalize2 with a migration in rails?
The problem is I can't access the stored content:
>> Panel.first
=> #<Panel id: 1, name: "RT", description: "asd", proje....
>> Panel.first.name
=> nil
>> I18n.locale = nil
=> nil
>> Panel.first.name
=> nil
Any ideas?
I'm sure you solved this one way or another but here goes. You should be able to use the read_attribute method to dig out what you're looking for.
I just used the following to migrate content from the main table into a globalize2 translations table.
Add the appropriate translates line to your model.
Place the following in config/initializers/globalize2_data_migration.rb:
require 'globalize'
module Globalize
module ActiveRecord
module Migration
def move_data_to_translation_table
klass = self.class_name.constantize
return unless klass.count > 0
translated_attribute_columns = klass.first.translated_attributes.keys
klass.all.each do |p|
attribs = {}
translated_attribute_columns.each { |c| attribs[c] = p.read_attribute(c) }
p.update_attributes(attribs)
end
end
def move_data_to_model_table
# Find all of the translated attributes for all records in the model.
klass = self.class_name.constantize
return unless klass.count > 0
all_translated_attributes = klass.all.collect{|m| m.attributes}
all_translated_attributes.each do |translated_record|
# Create a hash containing the translated column names and their values.
translated_attribute_names.inject(fields_to_update={}) do |f, name|
f.update({name.to_sym => translated_record[name.to_s]})
end
# Now, update the actual model's record with the hash.
klass.update_all(fields_to_update, {:id => translated_record['id']})
end
end
end
end
end
Created a migration with the following:
class TranslateAndMigratePages < ActiveRecord::Migration
def self.up
Page.create_translation_table!({
:title => :string,
:custom_title => :string,
:meta_keywords => :string,
:meta_description => :text,
:browser_title => :string
})
say_with_time('Migrating Page data to translation tables') do
Page.move_data_to_translation_table
end
end
def self.down
say_with_time('Moving Page translated values into main table') do
Page.move_data_to_model_table
end
Page.drop_translation_table!
end
end
Borrows heavily from Globalize 3 and refinerycms.

Overriding id on create in ActiveRecord

Is there any way of overriding a model's id value on create? Something like:
Post.create(:id => 10, :title => 'Test')
would be ideal, but obviously won't work.
id is just attr_protected, which is why you can't use mass-assignment to set it. However, when setting it manually, it just works:
o = SomeObject.new
o.id = 8888
o.save!
o.reload.id # => 8888
I'm not sure what the original motivation was, but I do this when converting ActiveHash models to ActiveRecord. ActiveHash allows you to use the same belongs_to semantics in ActiveRecord, but instead of having a migration and creating a table, and incurring the overhead of the database on every call, you just store your data in yml files. The foreign keys in the database reference the in-memory ids in the yml.
ActiveHash is great for picklists and small tables that change infrequently and only change by developers. So when going from ActiveHash to ActiveRecord, it's easiest to just keep all of the foreign key references the same.
You could also use something like this:
Post.create({:id => 10, :title => 'Test'}, :without_protection => true)
Although as stated in the docs, this will bypass mass-assignment security.
Try
a_post = Post.new do |p|
p.id = 10
p.title = 'Test'
p.save
end
that should give you what you're looking for.
For Rails 4:
Post.create(:title => 'Test').update_column(:id, 10)
Other Rails 4 answers did not work for me. Many of them appeared to change when checking using the Rails Console, but when I checked the values in MySQL database, they remained unchanged. Other answers only worked sometimes.
For MySQL at least, assigning an id below the auto increment id number does not work unless you use update_column. For example,
p = Post.create(:title => 'Test')
p.id
=> 20 # 20 was the id the auto increment gave it
p2 = Post.create(:id => 40, :title => 'Test')
p2.id
=> 40 # 40 > the next auto increment id (21) so allow it
p3 = Post.create(:id => 10, :title => 'Test')
p3.id
=> 10 # Go check your database, it may say 41.
# Assigning an id to a number below the next auto generated id will not update the db
If you change create to use new + save you will still have this problem. Manually changing the id like p.id = 10 also produces this problem.
In general, I would use update_column to change the id even though it costs an extra database query because it will work all the time. This is an error that might not show up in your development environment, but can quietly corrupt your production database all the while saying it is working.
we can override attributes_protected_by_default
class Example < ActiveRecord::Base
def self.attributes_protected_by_default
# default is ["id", "type"]
["type"]
end
end
e = Example.new(:id => 10000)
Actually, it turns out that doing the following works:
p = Post.new(:id => 10, :title => 'Test')
p.save(false)
As Jeff points out, id behaves as if is attr_protected. To prevent that, you need to override the list of default protected attributes. Be careful doing this anywhere that attribute information can come from the outside. The id field is default protected for a reason.
class Post < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
(Tested with ActiveRecord 2.3.5)
Post.create!(:title => "Test") { |t| t.id = 10 }
This doesn't strike me as the sort of thing that you would normally want to do, but it works quite well if you need to populate a table with a fixed set of ids (for example when creating defaults using a rake task) and you want to override auto-incrementing (so that each time you run the task the table is populate with the same ids):
post_types.each_with_index do |post_type|
PostType.create!(:name => post_type) { |t| t.id = i + 1 }
end
Put this create_with_id function at the top of your seeds.rb and then use it to do your object creation where explicit ids are desired.
def create_with_id(clazz, params)
obj = clazz.send(:new, params)
obj.id = params[:id]
obj.save!
obj
end
and use it like this
create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})
instead of using
Foo.create({id:1,name:"My Foo",prop:"My other property"})
This case is a similar issue that was necessary overwrite the id with a kind of custom date :
# in app/models/calendar_block_group.rb
class CalendarBlockGroup < ActiveRecord::Base
...
before_validation :parse_id
def parse_id
self.id = self.date.strftime('%d%m%Y')
end
...
end
And then :
CalendarBlockGroup.create!(:date => Date.today)
# => #<CalendarBlockGroup id: 27072014, date: "2014-07-27", created_at: "2014-07-27 20:41:49", updated_at: "2014-07-27 20:41:49">
Callbacks works fine.
Good Luck!.
For Rails 3, the simplest way to do this is to use new with the without_protection refinement, and then save:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save
For seed data, it may make sense to bypass validation which you can do like this:
Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)
We've actually added a helper method to ActiveRecord::Base that is declared immediately prior to executing seed files:
class ActiveRecord::Base
def self.seed_create(attributes)
new(attributes, without_protection: true).save(validate: false)
end
end
And now:
Post.seed_create(:id => 10, :title => 'Test')
For Rails 4, you should be using StrongParams instead of protected attributes. If this is the case, you'll simply be able to assign and save without passing any flags to new:
Post.new(id: 10, title: 'Test').save # optionally pass `{validate: false}`
In Rails 4.2.1 with Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test') works as long as there isn't a row with id = 10 already.
you can insert id by sql:
arr = record_line.strip.split(",")
sql = "insert into records(id, created_at, updated_at, count, type_id, cycle, date) values(#{arr[0]},#{arr[1]},#{arr[2]},#{arr[3]},#{arr[4]},#{arr[5]},#{arr[6]})"
ActiveRecord::Base.connection.execute sql

Resources