Rails - How to use Find Or Create - ruby-on-rails

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?

Related

Check if a user has voted with scope

I am using acts as voteable to implement a web poll. With two choices it is simple to determine whether a user has voted or not.
#user.likes #comment1
#user.up_votes #comment2
# user has not voted on #comment3
#user.voted_for? #comment1 # => true
#user.voted_for? #comment2 # => true
#user.voted_for? #comment3 # => false
#user.voted_as_when_voted_for #comment1 # => true, user liked it
#user.voted_as_when_voted_for #comment2 # => false, user didnt like it
#user.voted_as_when_voted_for #comment3 # => nil, user has yet to vote
https://github.com/ryanto/acts_as_votable
I need to has custom multiple choices and have implemented it based upon this:
How do I setup a multi-option voting system using acts-as-votable?
The item above states you can check if a user has voted with voted_for? however this does include scoped items:
Poll.first.vote_by voter: User.first, vote_scope: 'blue'
User.first.voted_for? Poll.first #false
User.first.voted_for? Poll.first, :vote_scope => 'blue' #true
My question is what is the best way to determine if a user has voted when using scopes? Do I need to loop through and check each scope individually for each record?
Edit 1
Currently I have the following Poll instance method:
def has_voted?(user)
['red', 'green', 'blue', 'white'].each do |option|
if user.voted_for? self, :vote_scope => option
return true
end
end
return false
end
Poll.first.has_voted?(User.first)
It looks like you should be able to call Poll.first.votes_for and get a list of the votes that have been cast:
p.votes_for
=> #<ActiveRecord::Associations::CollectionProxy [#<ActsAsVotable::Vote id: 1,
votable_type: "Poll", votable_id: 1, voter_type: "User", voter_id: 1, vote_flag: true,
vote_scope: "blue", vote_weight: 1,
created_at: "2017-11-05 22:12:52", updated_at: "2017-11-05 22:12:52">]>
With that list you should be able to check if any of the voter_ids matches the User you are looking for:
p.votes_for.any? { |v| v.voter_id == u.id }
=> true

Rails after_create created_at not set on created objects

I have on my Message model, an after_create which creates a new instance of a Notification like such.
after_create :send_notification
def send_notification
n = Notification.new :name => "#{self.sender.smart_name} sent you a message:", :user_id => self.receiver_id, :notification_type => 'message', :subject => self.subject
n.save
end
However, the objects that are created all have their created_at and updated_at set to nil.
#<Notification:0x0000000c486208
id: 123123,
user_id: 3423,
name: "I sent you a message:\n" + "10:27",
notification_type: "message",
created_at: nil,
updated_at: nil>
I've checked to see that the model.record_timestamps is set to true based on this answer.
I don't have anything set on active_record as suggested here.
I'm using Mysql on Rails 4.
You should call n.reload after n.save just to get the timestamps read after save

How do I clone a Rails model attribute?

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.

Ruby on rails: Session/current_user, db-insert

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]))

How can I pass multiple attributes to find_or_create_by in Rails 3?

I want to use find_or_create_by, but this statement does NOT work. It does not "find" or "create" with the other attributes.
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property.id, :value => d[descname])
There seems to be very little, or no, information on the use of dynamic finders in Rails 3. "and"-ing these together gives me a an unknown method error.
UPDATE:
Originally I couldn't get the following to work. Please assume I'm not an idiot and "product" is an instance of Product AR model.
product.product_properties.find_or_create_by_property_id_and_value(:property_id => 1, :value => "X")
The error methods was:
no such keys: property_id, value
I couldn't figure that out. Only this morning did I find the reference to passing the values like this instead:
product.product_properties.find_or_create_by_property_id_and_value(1, "X")
And voilá, it works fine. I would have expected a hash to work in the same situation but I guess not.
So I guess you get a down vote if you miss something on the internet?
If you want to search by multiple attributes, you can use "and" to append them. For example:
productproperty = ProductProperty.find_or_create_by_product_id_and_property_id_and_value(:product_id => product.id, :property_id => property.id, :value => d[descname])
There is one minor catch to be aware of. It will always return the object you've specified, even if that object can't be saved due to validation errors. So make sure you check to see if the returned object has an id (or is_valid?). Don't assume its in the database.
Alternatively, you can use the 'bang' version of the method to raise an error if the object cannot be saved:
http://guides.rubyonrails.org/active_record_querying.html#find-or-create-by-bang
This applies to Rails 3.
See http://api.rubyonrails.org/classes/ActiveRecord/Base.html:
With single query parameter:
productproperty = ProductProperty.find_or_create_by_product_id(product.id) { |u| u.property_id => property_id, u.value => d[descname] } )
or extended with multiple parameters:
productproperty = ProductProperty.find_or_create_by_product_id(:product_id => product.id, :property_id => property_id, :value => d[descname]) { |u| u.property_id => property_id, u.value => d[descname] } )
Would work with:
conditions = { :product_id => product.id,
:property_id => property.id,
:value => d[descname] }
pp = ProductProperty.find(:first, :conditions => conditions) || ProductProperty.create(conditions)
In Rails 4, you can use find_or_create_by(attr1: 1, attr2: 2) to find or create by multiple attributes.
You can also do something like:
User.create_with(
password: 'secret',
password_confirmation: 'secret',
confirmation_date: DateTime.now
).find_or_create_by(
email: 'admin#domain.com',
admin: true
)
If you need to create the user with some attributes, but cannot search by those attributes.
You could also use where(...).first_or_create - ActiveRecord::Relation#first_or_create.
product_property_attrs = { product_id: product.id,
property_id: property.id,
value: d[descname] }
product_property = ProductProperty.where(product_property_attrs).first_or_create
I've found in Rails 3.1 you do not need to pass the attributes in as a hash. You just pass the values themselves.
ProductProperty.find_or_create_by_product_id_and_property_id_and_value(
product.id, property.id, d[descname])

Resources