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
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.
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.
I'm having problems with an insert to the database. First an explanation of my little Blog app.
The models: Users och Posts. http://pastie.org/2694864
A post have columns: title, body, user id
3 controllers:
Session, Application (with current_user) and PostController: http://pastie.org/2695386
My loggin session seems to work but when a logged in user shoult write a post the database doesn't recognize any user_id. It's just set to nil. rails console:
=> #<Post id: 17, title: "hello", body: "hello world", created_at: "2011-10-14 14:54:25", updated_at: "2011-10-14 14:54:25", user_id: nil>
I guess it's in the post controller line 88 this should be fixed but I can't figure it out.
I have also tried:
#post = Post.new(params[:post], :user_id => session[:user_id])
But the user_id stills sets to nil!
This is my first app so I would be really greatful for detaild answears.
Tanx!
The problem is that you're passing Post.new two arguments (two hashes in this case), but it only takes one argument. Try this:
#post = Post.new(params[:post].merge!(:user_id => session[:user_id]))
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.
Don't mind me, I fricked up my attribute names :(
This is entirely possible, using the exact syntax I used - you just need to be able to spell!
I can't seem to get this to work, and it seems like a common enough scenario that there must be a solution, but I'm not having any luck with the correct terminology to get a helpful Google result.
I want to do this:
u = User.first
u.clients.find_or_create_by_email('example#example.com')
With the effect that a new Client is created with user_id = u.id.
Can I get the nice dynamic finders through a has_many relationship? If not, why?
Thanks :)
This
u = User.first
u.clients.find_or_create_by_email('example#example.com')
works if you have has_many relationship set. However, it won't raise validation error if you have any validations set on your Client object and it will silently fail if the validation fails.
You can check the output in your console when you do
u.clients.find_or_create_by_email('example#example.com') # => #<Client id: nil, email: 'example#example.com', name: nil, user_id: 1, another_attribute: nil, active: true, created_at: nil, updated_at: nil>
and the user_id will be set but not the id of client because the validation has failed and the client is not created
So this should create the client only if you pass all the required attributes of client object and the validation for client object has passed successfully.
So lets say your client model has validation on name as well apart from email then you should do
u.clients.find_or_create_by_email_and_name('example#example.com', 'my_name') #=> #<Client id: 1, email: 'example#example.com', name: 'my_name', user_id: 1, another_attribute: nil, active: true, created_at: "2009-12-14 11:08:23", updated_at: "2009-12-14 11:08:23">
This is entirely possible, using the exact syntax I used - you just need to be able to spell!