How to get ar_fixtures to load protected attributes? - ruby-on-rails

I'm using ar_fixtures to seed data in a rails project (the procedure was written before seed arrived in 2.3.4)
It is all working fine, except I find that one column in particular is not getting populated (user_id). user_id is set in the yaml file, for example:
- !ruby/object:Service
attributes:
name: name
updated_at: 2009-10-14 11:50:36
provider_id: "1"
id: "1"
description: ""
user_id: "1"
created_at: 2009-10-14 11:47:01
attributes_cache: {}
But even though the related user object exists when running the import (Service.load_from_file), user_id is nil after import. Other foreign keys (like provider_id in the example above) are loaded correctly.
I am suspecting this is because user_id is protected in the model and this getting blocked during mass assignment.
Does anyone know if this is the case, and if so, how to get around the mass assignment protection? Of course, I want to leave mass assignment protection in place for the application at runtime.

Fixed! Answering my own question..
Simply requires a tweak of the attr_protected / attr_accessible setting prior to loading. e.g.
Service.attr_protected.delete "user_id"
Service.load_from_file
Or if the restriction is based on attr_accessible:
Service.attr_accessible :user_id
Service.load_from_file

Related

Rails Duplicate key error: How to tell Rails to continue with the ID from the database

I'm currently deploying a Ruby on Rails web application with Postgres. I'm working with Docker, just to say it.
When I deploy my application, I insert some predefined data into the database. When I want to create a new record, I get a duplicate key error.
Full error message:
ERROR: duplicate key value violates unique constraint "modelname_pkey"
DETAIL: Key (id)=(1) already exists
How can I solve this? How can I tell Rails to continue with the primary key from the last record?
You can check insert_all method from Rails 6. If you are in lower versions of Rails use activerecord-import gem.
In case of insert_all
First form the json
new_records = [{id: 1, name: 'steve'},{id: 2, name: 'george'}]
Model.insert_all(new_records)
This will insert records if its not already there and ignore if records are there.
In case of activerecord-import
new_records = [{id: 1, name: 'steve'},{id: 2, name: 'george'}]
Model.import new_records, on_duplicate_key_ignore: true
references:
activerecord-import
rails-6-insert_all
If you don't specify an ID the database will choose one for you.
person = Person.create!( name: "MacReady", thing: false )
If you need to reference a specific ID, reconsider that. Relying on special IDs is too fragile, as you're discovering. Database IDs should be considered unique identifiers with no further special meaning.
For example, instead of remembering that "user ID 1 is the admin user" add an "admin" field.
admin = User.create!( name: "Yarrow Hock", admin: true )
Now you can check if user.admin for any user, have as many admins as you want, and change whether a user is an admin at any time.
I think this can also resolve your problem. Add this in your staging or preprod console (I am using Heroku for this so I added in my heroku rails console):
connection = ActiveRecord::Base.connection
connection.execute("SELECT setval(pg_get_serial_sequence('table_name', 'id'), MAX(id)) FROM table_name;")
Check out this blog for more info and how this is been generated.

How to show Rails ActiveRecord fields alphabetically in console

Can't find the configuration or setting for this (already googled several times). When inspecting some model objects in Rails console, why is it sometimes the model fields are displayed alphabetically and most times in other deployments, it does not?
Example:
### FIELDS NOT SORTED
#rails console
(main)> l = Loan.find '123'
=> #<Loan:0x000000066722e8
id: "8f196106c00e",
user_id: "f90084a53972",
borrower_id: "043bb77b3aac",
parent_id: nil,
score_id: "f00c39e74570",
number: 11231321,
scoring_tag: nil,
.....
but in other deployments, when I go in rails console
# FIELDS SORTED ALPHABETICALLY
(main)> l = Loan.find '123'
=> #<Loan:0x007fca8b45a468
active_servicer_id: nil,
amortization: nil,
amount: 150000.0 (BigDecimal),
application_fee: nil,
borrower_id: "asdasdajdjasd",
borrower_requested_closing_attorney: nil,
channel: nil,
closed_date: nil,
commitment_end_at: nil,
How can I make rails console display output of models sorted? This is sometimes necessary when comparing two records.
If you just want to print out the attributes, you can do that more explicitly:
Loan.find(123).attributes.sort_by(&:first).to_h
If you really want the console print to do that, you may want to override the inspect method. At least for irb and some other consoles, this is the method that determines what gets printed. If you have some other plugin or gem that the console is using, you may have to look at the docs to find the appropriate method.
Something like (modify as you wish, e.g. to better mimic the default inspect, display object_id, etc)
class Loan ...
def inspect
(self.class.name + ": " + attributes.sort_by(&:first).to_h).to_s
end
end
In Ruby's Hash, the order of its elements is arbitrary. When it is inspect-ed, (as in the output of rails console), the method inspect just uses each_pair or something similar, and so the order can be arbitrary. In practice, the order for Hash#each_pair usually seems to follow the order each Hash element is created; however there is no guarantee! Also, how Rails (or ActiveRecord) creates those instances is another issue (I guess the alphabetical order you see originates how Rails created them; it may have something to do with how they are stored in the DB). Anyway, the bottom line is you can not control how they are stored, as long as a Hash is used. But you can tweak how they are inspect-ed.
The following strategy is to redefine ApplicationRecord#inspect for any of its child classes so the inspect method of any Models are modified but of none of any other classes.
Put the following code in whatever file in your app/ directory (e.g., an obvious filename is /app/models/application_record.rb):
class ApplicationRecord
alias_method :inspect_orig, :inspect if ! self.method_defined?(:inspect_orig)
def inspect
klass = self.class
if klass == ApplicationRecord
return inspect_orig
end
attr_list = attribute_names.sort.map { |name| "#{name}: "+attributes[name].inspect }.join ', '
sprintf('<%s:0x%s %s>', klass.name, object_id.to_s(16), attr_list)
end
end
Or, instead you can run it every time you open rails console if you want to activate it only temporarily during the session.
Note the name of the parent class of ApplicationRecord may be added as follows (in the case of Rails 5.2; but I don't think inclusion of the parent class is mandatory, for the class ApplicationRecord should be defined before the file is read).
class ApplicationRecord < ActiveRecord::Base
I notice your rails console outputs look slightly different from my Rails 5.2.1, maybe due to the difference in the versions(?). In the code snippet above, I have tried to mimic the outputs you seem to get. Anyway adjust it to your liking; now you have a total control on it!
EDIT1:
The description above applies to Rails 5. In Rails 4, the class name should be
class ActiveRecord::Base
EDIT2:
In the code snippet above, it shows #object_id (n.b., it is not displayed in Rails 5.2). However, what #inspect shows for Object instances is different from #object_id, as explained in detail in this answer. If you prefer to display it, a way is this (the left side value is the format for sprintf):
'0x%016x' % (object_id << 1)

Seeds file - adding id column to existing data

My seeds file populated the countries table with a list of countries. But now it needs to be changed to hard-code the id (instead of rails generating the id column for me).
I added the id column and values as per below:
zmb: {id: 103,code: 'ZMB', name: Country.human_attribute_name(:zambia, default: 'Error!'), display_order: nil, create_user: user, update_user: user, eff_date: Time.now, exp_date: default_exp_date},
skn: {id: 104,code: 'SKN', name: Country.human_attribute_name(:st_kitts_and_nevis, default: 'Error!'), display_order: nil, create_user: user, update_user: user, eff_date: Time.now, exp_date: default_exp_date}
countries.each { |key, value| countries_for_later[key] = Country.find_or_initialize_by(id: value[:id]); countries_for_later[key].assign_attributes(value); countries_for_later[key].save!; }
Above it just a snippet. I have added an id: for every country.
But when I run db:seed I get the following error:
ActiveRecord::RecordInvalid: Validation failed: Code has already been taken
I am new to rails so I'm not sure what is causing this - is it because the ID column already exists in the database?
What I think is happening is you have existing data in your database ... let's say
[{id:1 , code: 'ABC'},
{id:2 , code: 'DEF'}]
Now you run your seed file which has {id: 3, 'DEF'} for example.
Because you are using find_or_initialize_by with id you are running into errors. Since you can potentially insert duplicates.
I recon you should just clear your data, but you can try doing find_or_initialize_by using code instead of id. That way you wont ever have a problem of trying to create a duplicate country code.
Country.find_or_initialize_by(code: value[:code])
I think you might run into problems with your ids, but you will have to test that. It's generally bad practice to do what you are doing. Whether they ids change or now should be irrelevant. Your seed file should reference the objects that are being created not ids.
Also make sure you aren't using any default_scopes ... this would affect how find_or_initialize_by works.
The error is about Code: Code has already been taken. You've a validation which says Code should be uniq. You can delete all Countries and load seeds again.
Run this in the rails console:
Country.delete_all
Then re-run the seed:
rake db:seed
Yes, it is due to duplicate entry. In that case run ModelName.delete_all in your rails console and then run rake db:seed again being in the current project directory. Hope this works.
ActiveRecord::RecordInvalid: Validation failed: Code has already been taken
is the default error message for the uniqueness validator for :code.
Running rake db:reset will definitely clear and reseed your database. Not sure about the hardcoded ids though.
Check this : Overriding id on create in ActiveRecord
you will have to disable protection with
save(false)
or
Country.create(attributes_for_country, without_protection: true)
I haven't tested this though, be careful with your validators.
Add the line for
countries_for_later[key].id = value[:id]
the problem is that you can't set :id => value[:id] to Country.new because id is a special attribute, and is automatically protected from mass-assignment
so it will be:
countries.each { |key, value|
countries_for_later[key] = Country.find_or_initialize_by(id: value[:id])
countries_for_later[key].assign_attributes(value)
countries_for_later[key].id = value[:id] if countries_for_later[key].new_record?
countries_for_later[key].save(false)
}
The ids data that you are using in your seeds file: does that have any meaning outside of Rails? Eg
zmb: {id: 103,code: 'ZMB',
is this some external data for Zambia, where 103 is it's ID in some internationally recognised table of country codes? (in my countries database, Zambia's "numcode" value is 894). If it is, then you should rename it to something else, and let Rails decide what the id field should be.
Generally, mucking about with the value of ID in rails is going to be a pain in the ass for you. I'd recommend not doing it. If you need to do tests on data, then use some other unique field (like 'code') to test whether associations etc have been set up, or whatever you want to do, and let Rails worry about what value to use for ID.

How can I update a data record's value with Ruby on Rails 4.0.1/PostgreSQL Hstore?

I'm encountering a strange issue that must be user error on my part but can't figure it out.
I'm using Ruby 1.9.3-p194, Rails 4.01, PostgreSQL.
I have a model, Customer, with a column called data that is a hstore type. For some reason, I am not able to update the data (hstore) column with any new key/values nor can I update an existing key's value. I can do an insert and specify any key/values w/o any issue.
Customer id: 1, first_name: "Mark", last_name: "Test", data: {"balance"=>"0"}, created_at: "2013-11-27 14:39:09", updated_at: "2013-11-27 14:39:09"
c.data["balance"] = "100"
c.save
(0.2ms) BEGIN
(0.3ms) COMMIT
=> true
If I do an update_attributes, it does save it.
c.update_attributes({:data => {"balance" => "343"}})
I don't see any errors or exceptions when I used c.save!. Anyone have any ideas?
I still needed this to work now and couldn't wait for the bug to be solved, so here's my workaround:
c.data["balance"] = "100"
c.data_will_change!
c.save
... and it will save to the DB!
The "attribute_name_will_change!" method isn't that well documented and can be found in the introduction to the Active Model Dirty module: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
Ok finally found an answer to my question. Turns out that it's a bug within Rails 4.0.
"ActiveRecord Hstore bug: can't update a key in the hash"
https://github.com/rails/rails/issues/6127
Upgraded Hstore docs:
http://edgeguides.rubyonrails.org/active_record_postgresql.html#hstore
They're working to use the same solution for Hstore and Json object updating.
This will be fixed on Rails 4.2.
Pull-request: https://github.com/rails/rails/pull/15674

Build vs new in Rails 3

In the Rails 3 docs, the build method for associations is described as being the same as the new method, but with the automatic assignment of the foreign key. Straight from the docs:
Firm#clients.build (similar to Client.new("firm_id" => id))
I've read similar elsewhere.
However, when I use new (e.g. some_firm.clients.new without any parameters), the new client's firm_id association is automatically created. I'm staring at the results right now in the console!
Am I missing something? Are the docs a bit out of date (unlikely)? What's the difference between build and new?
You're misreading the docs slightly. some_firm.client.new is creating a new Client object from the clients collection, and so it can automatically set the firm_id to some_firm.id, whereas the docs are calling Client.new which has no knowledge of any Firm's id at all, so it needs the firm_id passed to it.
The only difference between some_firm.clients.new and some_firm.clients.build seems to be that build also adds the newly-created client to the clients collection:
(some_firm = Firm.new).save # Create and save a new Firm
#=> true
some_firm.clients # No clients yet
#=> []
some_firm.clients.new # Create a new client
#=> #<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>
some_firm.clients # Still no clients
#=> []
some_firm.clients.build # Create a new client with build
#=> #<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>
some_firm.clients # New client is added to clients
#=> [#<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>]
some_firm.save
#=> true
some_firm.clients # Saving firm also saves the attached client
#=> [#<Client id: 1, firm_id: 1, created_at: "2011-02-11 00:18:47", updated_at: "2011-02-11 00:18:47">]
If you're creating an object through an association, build should be preferred over new as build keeps your in-memory object, some_firm (in this case) in a consistent state even before any objects have been saved to the database.
build is just an alias for new:
alias build new
Full code can be found: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb#L74
You are correct, the build and new functions have the same effect of setting the foreign key, when they are called through an association. I believe the reason the documentation is written like this is to clarify that a new Client object is being instantiated, as opposed to a new active record relationship. This is the same effect that calling .new on a class would have in Ruby. That is to say that the documentation is clarifying that calling build on an association is the same is creating a new object (calling .new) and passing the foreign keys to that object. These commands are all equivalent:
Firm.first.clients.build
Firm.first.clients.new
Client.new(:firm_id => Firm.first.id)
I believe the reason .build exists is that Firm.first.clients.new might be interpreted to mean that you are creating a new has_many relationship object, rather than an actual client, so calling .build is a way of clarifying this.
build vs new:
mostly new and build are same but build stores object in memory,
eg:
for new:
Client.new(:firm_id=>Firm.first.id)
For build:
Firm.first.clients.build
Here clients are stored in memory, when save firm , associated records are also saved.
Model.new
Tag.new post_id: 1 will instantiate a Tag with its post_id set.
#model.models.new
#post.tags.build does the same AND the instantiated Tag will be in #post.tags even before it's saved.
This means #post.save will save both the #post and the newly built tag (assuming :inverse_of is set). This is great because Rails will validate both objects before saving, and neither will be saved if either one of them fails validation.
models.new vs models.build
#post.tags.build and #post.tags.new are equivalent (at least since Rails 3.2).

Resources