Rails: Confirming two objects were saved at the same time? - ruby-on-rails

When a user is created on my site I want a User.new instance to execute but I also need to make a Alias.new object too. Users have many Aliases. However, I also need to validate that there are no other Aliases with that name before saving.
From the console my code might look like this:
u = User.new(:name => "Bob")
a = Alias.new(:name => "SirBob", :user_id => u)
But that's doesn't work since u doesn't have a id until I save. So how do I validate both items for uniqueness of name before saving them?

Try this one:
u = User.new
u.aliases.build
Hope this helps...

Use
ActiveRecord::Base.transaction do
u = User.new(:name => "Bob")
a = Alias.new(:name => "SirBob", :user_id => u)
end
and add validates_uniqueness_of :name on Alias model
This will solve your problem.

Related

update several records using update_all

So I'm trying to update few things in my user for it's role and its plan_id .. I've came up with something dirty in my console but everytime I'm trying to use update_all similar way I'm getting no where. I think I'm missing something.. here's my original console way;
expired_subscription_user_ids = Subscription.where("expiry_date < ?", Time.now.beginning_of_day).pluck(:user_id)
User.where(:id => cancelled_subscription).each do |user|
user.role = 'cancelled'
user.plan_id = 'cancelled'
user.save
end
Here's the same thing but using update all that's not working out for me.
User.where(:id => cancelled_subscription).each do |user|
user.update_all(:role => 'subscriber', :plan_id => 'subscriber')
end
So pretty much all users with cancelled_subscription will have their role and plan_id chanced.
You have to use the .update_all method on an ActiveRecord::Relation object, like this:
scope = User.where(:id => cancelled_subscription)
scope.update_all(:role => 'subscriber', :plan_id => 'subscriber')
Documentation: http://apidock.com/rails/ActiveRecord/Relation/update_all
An interesting comment in the documentation, from "openface":
Note that ActiveRecord will not update the timestamp fields (updated_at/updated_on) when using update_all().

In Rails, how do I limit which attributes can be updated, without preventing them from being created?

I have a situation where an attribute can be created through a JSON API. But once it is created, I want to prevent it from ever being updated.
This constraint causes my first solution, which is using attr_accessible, to be insufficient. Is there a nice way to handle this type of situation in rails, or do I have to perform a manual check in the update method?
You can use attr_readonly, this will allow the value to be set on creation, but ignored on update.
Example:
class User < ActiveRecord::Base
attr_accessible :name
attr_readonly :name
end
> User.create(name: "lorem")
> u = User.first
=> #<User id: 1, name: "lorem">
> u.name = "ipsum"
=> "ipsum"
> u.save
=> true
> User.first.name
=> "lorem"
There is not a nice way to do that as far as I know, you have to write a custom filter
before_update :prevent_attributes_update
def prevent_attribute_updates
%w(attr1, attr2).each do |a|
send("#{attr1}=", send("#{attr1}_was")) unless self.send("#{attr1}_was").blank?
end
end

Rails: .create nullifies a custom value for :id

When I execute a Model.create method, if I specify a value for :id, it later gets nullified. Example:
Model.create (
:id => 50,
:name => Joe,
:enabled => yes
)
Instead what I have to do is use a .new and store it in a class variable, store my id value via the class variable, and then finally call a save:
m = Model.new (
:name => Joe,
:enabled => yes
)
m.id = 50
m.save
I am trying to execute this code in a seeds.rb, and this is NOT very DRY code. How can I do this better and achieve the same results?
id is just attr_protected. To prevent that, you can 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 Model < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
or go with #Leo answer
This might be an answer for you. Model.create is basically a Model.new followed by a Model.save and since you are changing the id and saving again you might as well do
m = Model.new {
:name => Joe,
:enabled => yes
}
m.id = 50
m.save!
That will rid you of doing two saves.

FactoryGirl: attributes_for not giving me associated attributes

I have a Code model factory like this:
Factory.define :code do |f|
f.value "code"
f.association :code_type
f.association(:codeable, :factory => :portfolio)
end
But when I test my controller with a simple test_should_create_code like this:
test "should create code" do
assert_difference('Code.count') do
post :create, :code => Factory.attributes_for(:code)
end
assert_redirected_to code_path(assigns(:code))
end
... the test fails. The new record is not created.
In the console, it seems that attributes_for does not return all required attributes like the create does.
rob#compy:~/dev/my_rails_app$ rails console test
Loading test environment (Rails 3.0.3)
irb(main):001:0> Factory.create(:code)
=> #<Code id: 1, code_type_id: 1, value: "code", codeable_id: 1, codeable_type: "Portfolio", created_at: "2011-02-24 10:42:20", updated_at: "2011-02-24 10:42:20">
irb(main):002:0> Factory.attributes_for(:code)
=> {:value=>"code"}
Any ideas?
Thanks,
You can try something like this:
(Factory.build :code).attributes.symbolize_keys
Check this: http://groups.google.com/group/factory_girl/browse_thread/thread/a95071d66d97987e)
This one doesn't return timestamps etc., only attributes that are accessible for mass assignment:
(FactoryGirl.build :position).attributes.symbolize_keys.reject { |key, value| !Position.attr_accessible[:default].collect { |attribute| attribute.to_sym }.include?(key) }
Still, it's quite ugly. I think FactoryGirl should provide something like this out of the box.
I opened a request for this here.
I'd suggest yet an other approach, which I think is clearer:
attr = attributes_for(:code).merge(code_type: create(:code_type))
heres what I end up doing...
conf = FactoryGirl.build(:conference)
post :create, {:conference => conf.attributes.slice(*conf.class.accessible_attributes) }
I've synthesized what others have said, in case it helps anyone else. To be consistent with the version of FactoryGirl in question, I've used Factory.build() instead of FactoryGirl.build(). Update as necessary.
def build_attributes_for(*args)
build_object = Factory.build(*args)
build_object.attributes.slice(*build_object.class.accessible_attributes).symbolize_keys
end
Simply call this method in place of Factory.attributes_for:
post :create, :code => build_attributes_for(:code)
The full gist (within a helper module) is here: https://gist.github.com/jlberglund/5207078
In my APP/spec/controllers/pages_controllers_spec.rb I set:
let(:valid_attributes) { FactoryGirl.attributes_for(:page).merge(subject: FactoryGirl.create(:theme), user: FactoryGirl.create(:user)) }
Because I have two models associated. This works too:
FactoryGirl.define do
factory :page do
title { Faker::Lorem.characters 12 }
body { Faker::Lorem.characters 38 }
discution false
published true
tags "linux, education, elearning"
section { FactoryGirl.create(:section) }
user { FactoryGirl.create(:user) }
end
end
Here's another way. You probably want to omit the id, created_at and updated_at attributes.
FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys
Limitations:
It does not generate attributes for HMT and HABTM associations (as these associations are stored in a join table, not an actual attribute).
Association strategy in the factory must be create, as in association :user, strategy: :create. This strategy can make your factory very slow if you don't use it wisely.

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