How do I clone a single attribute in a Rails model? This didn't work:
irb(main):309:0> u.reload
=> #<User id: 1, username: "starrychloe", ...
irb(main):310:0> u2 = u.dup
=> #<User id: nil, username: "starrychloe", ...
irb(main):311:0> u2 = u.clone
=> #<User id: 1, username: "starrychloe", ...
irb(main):312:0> u2.username = u.username.clone
=> "starrychloe"
irb(main):313:0> u2.username = 'star'
=> "star"
irb(main):314:0> u.username ############ Changes original
=> "star"
Neither did this:
irb(main):320:0> u.reload
=> #<User id: 1, username: "starrychloe", ...
irb(main):321:0> u2 = u.clone
=> #<User id: 1, username: "starrychloe", ...
irb(main):322:0> u2[:username] = u[:username].clone
=> "starrychloe"
irb(main):323:0> u2.username = 'cow'
=> "cow"
irb(main):324:0> u.username ############ Changes original
=> "cow"
#dup doesn't copy the ID, and #clone on the attribute keeps the reference to the same string. This will not solve my problem.
u2 = User.new(u.attributes.merge(username: "cow"))
Also, take a look at this question. It has a lot of interesting info on similar subject:
What is the easiest way to duplicate an activerecord record?
Do you want to duplicate an instance or an attribute?
To duplicate an instance, use u2 = u.dup not u2 = u.clone.
You might wanna look into amoeba gem. https://github.com/rocksolidwebdesign/amoeba
To make a copy of the instance with its attributes and de-reference you can do this:
u2 = u.class.new(u.attributes)
I ended up making copies of each of the fields I wanted to keep track of:
#oldUsername = #user.username.clone
User.new looked promising, but it treated the copy as a new object, when it was an existing model, and output invalid forms to edit the model in the views:
> app.controller.view_context.form_for u2 do end # This is from Rails console
=> "<form accept-charset=\"UTF-8\" action=\"/users\" class=\"new_user\" id=\"new_user_1\" method=\"post\">
So it would attempt to PATCH to /users (from the view), which is invalid, when it should PATCH to /users/1/.
It's unbelievable that Rails won't clone objects correctly. In Java, you could use u2.setProperty( u.getProperty().clone() ) and be sure to have a new object that won't interfere with the old one.
Related
Suppose there are users records in the database. And we decided to add validation in model. Model:
class User < ActiveRecord::Base
validates_format_of :name, with: /\A[^\d]*\z/, allow_blank: true
before_validation :delete_digits_from_name
def delete_digits_from_name
self.name = name.gsub!(/\d/, '')
end
end
Scenario 1 in console:
User.create(name: 'Username 15')
User.last
=> #<User id: 14154, name: "Username"
And it's ok. But there are old record (created before adding validation) and.. scenario 2:
user = User.first
=> #<User id: 1, name: "Username 15"
user.save
=> true
user
=> #<User id: 1, name: "Username"
user.reload
=> #<User id: 1, name: "Username 15"
But why?? Why changes not saved?
The gsub! in delete_digits_from_name changes the name in place, so Rails thinks name is the same thing it loaded from the DB. It's the same object, even though you've changed its value. Rails does this to optimize away DB updates when no data has changed, and in-place editing confuses it.
Switching to self.name = self.name.gsub(/\d/, '') (no !) assigns a new String that Rails will recognize as dirty and needing saving.
You can also add name_will_change! after your gsub! to tell Rails the attribute needs saving.
Let's say I have this simple method in my helper that helps me to retrieve a client:
def current_client
#current_client ||= Client.where(:name => 'my_client_name').first
end
Now calling current_client returns this:
#<Client _id: 5062f7b851dbb2394a00000a, _type: nil, name: "my_client_name">
Perfect. The client has a few associated users, let's look at the last one:
> current_client.user.last
#<User _id: 5062f7f251dbb2394a00000e, _type: nil, name: "user_name">
Later in a new method I call this:
#new_user = current_client.user.build
And now, to my surprise, calling current_client.user.last returns
#<User _id: 50635e8751dbb2127c000001, _type: nil, name: nil>
but users count doesn't change. In other words - it doesn't add the new user but one user is missing... Why is this? How can I repair it?
current_client.users.count makes a round trip to the database to figure out how many user records are associated. Since the new user hasn't been saved yet (it's only been built) the database doesn't know about it.
current_client.users.length will give you the count using Ruby.
current_client.users.count # => 2
current_client.users.length # => 2
current_client.users.build
current_client.users.count # => 2
current_client.users.length # => 3
I am trying to lowercase params[:user][:email] in my application but currently I'm using #user = User.new(params[:user]) (which includes the email) in my def create. Is it possible to allow for mass assignment for everything except for a single item?
I know I could just not use mass assignment but I was wondering if this was possible.
Yes.
class User
attr_protected :email
end
Here's how you'd use it:
user = User.new(params[:user])
user.email = params[:user][:email].downcase
If you want to downcase the email attribute automatically though, you can simply override the email= method, which I strongly recommend:
class User < ActiveRecord::Base
def email=(other)
write_attribute(:email, other.try(:downcase))
end
end
Loading development environment (Rails 3.2.5)
irb(main):001:0> User.new({:email => 'Me#you.com'})
=> #<User id: nil, email: "me#you.com", username: nil, created_at: nil, updated_at: nil>
irb(main):002:0> User.new({:email => nil})
=> #<User id: nil, email: nil, username: nil, created_at: nil, updated_at: nil>
you should look at attr_protected. This lets you define only the attributes you want to prevent from being mass assigned.
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html#method-i-attr_protected
I noticed that I can do a Model.find in a number of ways (assuming #user is an instance of the User model):
User.find(2)
=> #<User id: 2, name: "Mike Swift", email: "valid#email.com", ... etc ...
OR
User.find(#user)
=> #<User id: 2, name: "Mike Swift", email: "valid#email.com", ... etc ...
OR
User.find(#user[:id])
=> #<User id: 2, name: "Mike Swift", email: "valid#email.com", ... etc ...
OR
User.find(#user.id)
=> #<User id: 2, name: "Mike Swift", email: "valid#email.com", ... etc ...
Is there any real difference between the later three of these methods? (I already know User.find(n) would be the fastest) I would imagine they all work in about the same time, but perhaps I'm wrong.
In terms of sql they all do the same thing.
User.find(2)
This will be the fastest because there is no conversion needed.
Then User.find(#user.id) and User.find(#user[:id]).
And finally User.find(#user because rails needs convert the user to an ID.
User.find(2) should be faster as Rails doesn't have to do any work to figure out the id. The others require some level of message passing to get the id.
I doubt the difference is very significant though.
You could try all of them and look at your log to see how long it takes to get your response.
I have the following:
#permission = #group.permissions.create(
:user_id => #user.id,
:role_id => 2,
:creator_id => current_user.id)
How can I update that to be find_or_create, so that if this record already exists, it's assigned to #permission, and if it doesn't exist, the record is created?
While the accepted answer is correct it's important to note that in Rails 4 this syntax will be changing (and the hash syntax). You should be writing the following:
#permission = Permission.where(
user_id: #user.id,
role_id: 2,
creator_id: current_user.id).first_or_create
Which actually looks much closer to your original method! See the sub-section Deprecated Finders for more details.
Related topic:
find_or_create_by in Rails 3 and updating for creating records
You can extend ActiveRecord with your own update_or_create method (see related topic) and then you can use this
#permission = Permission.update_or_create_by_user_id_and_role_id_and_creator_id(#user.id, 2, current_user.id) do |p|
p.group_id = #group.id
end
Or you can use find_or_create_by... method:
#permission = Permission.find_or_create_by_user_id_and_role_id_and_creator_id(#user.id, 2, current_user.id)
#permission.group = #group
#permission.save
I'm updating questions with versioned answers. Because its important.
Rails 4 (docs)
There are a few ways to "find or create" an object, one is find_or_create_by(args)
Client.find_or_create_by(email: "bambam#flinstones.com", phone: "4255551212")
But the community preferred way is using where
client = Client.where(email: "bambam#flinstones.com", phone: "4255551212").first_or_create
and then you can do something like:
client = Client.where(client_params.slice(:email, :phone)).first_or_create
client.update(client_params)
Rails 3 (docs)
Suppose you want to find a client named ‘Andy’, and if there’s none,
create one and additionally set his locked attribute to false. You can
do so by running:
client = Client.where(:first_name => 'Andy').first_or_create(:locked => false)
# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
Or you wanna try this if you have many fields to fill in:
conditions = { :user_id => #user.id,
:role_id => 2,
:creator_id => current_user.id }
#permission = group.permissions.find(:first, :conditions => conditions) || group.permissions.create(conditions)
see this post:
How can I pass multiple attributes to find_or_create_by in Rails 3?