In Rails, what is the best way to update a record or create a new one if it doesn't exist? - ruby-on-rails

I have a create statement for some models, but it’s creating a record within a join table regardless of whether the record already exists.
Here is what my code looks like:
#user = User.find(current_user)
#event = Event.find(params[:id])
for interest in #event.interests
#user.choices.create(:interest => interest, :score => 4)
end
The problem is that it creates records no matter what. I would like it to create a record only if no record already exists; if a record does exist, I would like it to take the attribute of the found record and add or subtract 1.
I’ve been looking around have seen something called find_or_create_by. What does this do when it finds a record? I would like it to take the current :score attribute and add 1.
Is it possible to find or create by id? I’m not sure what attribute I would find by, since the model I’m looking at is a join model which only has id foreign keys and the score attribute.
I tried
#user.choices.find_or_create_by_user(:user => #user.id, :interest => interest, :score => 4)
but got
undefined method find_by_user
What should I do?

my_class = ClassName.find_or_initialize_by_name(name)
my_class.update_attributes({
:street_address => self.street_address,
:city_name => self.city_name,
:zip_code => self.zip_code
})

Assuming that the Choice model has a user_id (to associate with a user) and an interest_id (to associate with an interest), something like this should do the trick:
#user = User.find(current_user)
#event = Event.find(params[:id])
#event.interests.each do |interest|
choice = #user.choices.find_or_initialize_by_interest_id(interest.id) do |c|
c.score = 0 # Or whatever you want the initial value to be - 1
end
choice.score += 1
choice.save!
end
Some notes:
You don't need to include the user_id column in the find_or_*_by_*, as you've already instructed Rails to only fetch choices belonging to #user.
I'm using find_or_initialize_by_*, which is essentially the same as find_or_create_by_*, with the one key difference being that initialize doesn't actually create the record. This would be similar to Model.new as opposed to Model.create.
The block that sets c.score = 0 is only executed if the record does not exist.
choice.score += 1 will update the score value for the record, regardless if it exists or not. Hence, the default score c.score = 0 should be the initial value minus one.
Finally, choice.save! will either update the record (if it already existed) or create the initiated record (if it didn't).

find_or_create_by_user_id sounds better

Also, in Rails 3 you can do:
#user.choices.where(:user => #user.id, :interest => interest, :score => 4).first_or_create

If you're using rails 4 I don't think it creates the finder methods like it used to, so find_or_create_by_user isn't created for you. Instead you'd do it like this:
#user = User.find(current_user)
#event = Event.find(params[:id])
for interest in #event.interests
#user.choices.find_or_create_by(:interest => interest) do |c|
c.score ||= 0
c.score += 1
end
end

In Rails 4
You can use find_or_create_by to get an object(if not exist,it will create), then use update to save or update the record, the update method will persist record if it is not exist, otherwise update record.
For example
#edu = current_user.member_edu_basics.find_or_create_by(params.require(:member).permit(:school))
if #edu.update(params.require(:member).permit(:school, :majoy, :started, :ended))

Related

How do I enter a record for every user where condition is true in Rails

So I have app that has political candidates.
When a new political candidate is entered, I want to enter a notification into the notifications table for every user that's state is equal to the state of the new candidate being entered.
Ultimately, I want to enter in records to the notification table for every single user where that condition is met.
I know I'm way off, but here's where I'm at now. I'm trying to loop through each user and then enter this record when that condition is true.
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
User.each do |u|
if Candidate.state == User.state
#notification = Notification.new(:message => 'Message', :user_id => U.id)
#notification.save
else
end
end
else
render('new')
end
end
The candidate is created with this code, but the notifications aren't working. Basically I have two users where their state equals "Arizona" and I would expect if I create a new candidate where the state is "Arizona" that I should get two record into notifications, one with each user ID.
I think you got a bit mixed up between classes and instances. Here's the relevant bit:
#candidate = Candidate.new(candidate_params)
...
User.each do |u|
if Candidate.state == User.state
...
end
end
In your code Candidate is a class, and #candidate holds the recently created instance of a Candidate. Likewise, User is a class and u holds a User instance (on each loop iteration). Your comparison should actually use the instances rather than the classes:
if #candidate.state == u.state
Having sorted that, it's worth noting that your code has a couple of other errors -- User.each won't work. You need to specify a selector to get a list of User objects to loop through. One way would be to call User.all.each (which looking at your code is probably what you were trying). That pulls all User objects. But, since users can be from anywhere, if you do that you will cycle through a lot of users you don't need to.
Since all you need is users whose state matches the new candidate, you can use the where() method to pre-filter the list you are looping through. That way you don't need the if at all.
#candidate = Candidate.new(candidate_params)
...
User.where(state: #candidate.state).each do |u|
#notification = Notification.new(message: 'Message', user: u)
#notification.save
end
The other problem in your code is in the line to create a notification. You use U.id but the loop variable is lower case u. As an added tip, you don't need to set the object ID specifically. If you just pass the User object (as in the code above), Rails is smart enough to figure out the rest.
For performance don't iterate all users, you can search users that match the candidate's state then create notification for each user.
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
users = User.where(state: #condidate.state)
users.each { |user| #notification = Notification.create(:message => 'Message', :user_id =user.id } if users
else
render 'new'
end
end

Rails: update existing has_many through record via controller?

So two thirds of this works. Every time a User reads an Article, a History record is created (has_many through), which just says "User read Article at Read_Date_X".
The database is ok, the models are ok, the read_date param is permitted in the History controller, and the following operation works both 1) to check if a User has read an article before and 2) to create a new History record if it is the first time on this article.
But I cannot work out why the middle bit (to just update the read_date on an existing record) is not working. It doesn't matter if I try it with h.save! or h.update().
h = History.where(article_id: #article, user_id: current_user)
if h.exists?
h = History.where(article_id: #article, user_id: current_user)
h.read_date = Time.now
h.save!
else
h = History.new
h.article_id = #article.id
h.user_id = current_user.id
h.read_date = Time.now
h.save!
end
The error it throws if it finds an existing record is:
undefined method `read_date=' for #<History::ActiveRecord_Relation:0x007fe7f30a5e50>
UPDATE: working answer
So Derek was right, and this version works. The middle bit needed a single instance, not an array, which is what the top conditional (without .first) was checking for. Using that to return a single record, though, means you need to swap "exists?" to "present?" in the second part.
h = History.where(article_id: #article, user_id: current_user).first
if h.present?
h.read_date = Time.now
h.save!
else
h = History.new
h.article_id = #article.id
h.user_id = current_user.id
h.read_date = Time.now
h.save!
end
History.where(article_id: #article, user_id: current_user) is returning a History::ActiveRecord_Relation. If you want to set the read_date, you'll want to get a single record.
Here's one way you could do this with what you have currently:
h = History.where(article_id: #article, user_id: current_user).first
Another way you could handle this is by using find_by instead of where. This would return a single record. Like this:
h = History.find_by(article_id: #article, user_id: current_user)
However, if it's possible for a user to have many history records for an article, I would stick to the way you're doing things and make one change. If for some reason you have a lot of history records, this may not be very efficient though.
histories = History.where(article_id: #article, user_id: current_user)
histories.each { |history| history.update(read_date: Time.now) }
I realize this question is already answered. Here are a couple of additional thoughts and suggestions.
I would not have a separate read_date attribute. Just use updated_at instead. It's already there for you. And, the way your code works, read_date and updated_at will always be (essentially) the same.
When looking up whether the history exists, you can do current_user.histories.where(article: #article). IMO, that seems cleaner than: History.where(article_id: #article, user_id: current_user).first.
You can avoid all that exists? and present? business by just checking if the h assignment was successful. Thus, if h = current_user.histories.where(article: #article).
If you go the route of using updated_at instead of read_date, then you can set updated_at to Time.now by simply doing h.touch.
I would use the << method provided by has_many :through (instead of building the history record by hand). Again, if you use updated_at instead of read_date, then you can use this approach.
So, you could boil your code down to:
if h = current_user.histories.where(article: #article)
h.touch
else
current_user.articles << #article
end
You could use a ternary operator instead of that if then else, in which case it might look something like:
current_user.histories.where(article: #article).tap do |h|
h ? h.touch : current_user.articles << #article
end

How to delete a single instance from a collection

I'm trying to delete a single instance from a database query. "l.remove" represents what i want to do but i know its wrong. I have tried delete and destroy. destroy didn't work and delete actually removed the data from the database. I just want the data removed from the variable. Can anyone help me?
<%
#owner = User.find(params[:id])
#job_list = ShoppingList.where(:user_id=>#user.user_id)
#job_list.each do |l|
#temp = FlaggedCandidate.where(:flagged_user_id=>#owner.user_id, :list_id=>l.list_id)
if !#temp.nil?
l.remove
end
end
#candidate = FlaggedCandidate.new
%>
based on the code i assume that User has many ShoppingList.
You can do something like:
#job_list = #owner.shopping_lists.where( list_id: FlaggedCandidate.where( flagged_user_id: #owner.user_id ).pluck(:list_id) )
That could save the trouble of looping around.
You are trying to remove record from db. In order to just modify collection #job_list you need reject some unsatisfied elements. You can do it with select method (to select job_lists that flagged), or reject in opposite. This is how you code should looks like:
#owner = User.find(params[:id])
#job_list = ShoppingList.where(:user_id=>#user.user_id)
#job_list.select! do |job_list|
FlaggedCandidate.where(
:flagged_user_id => #owner.user_id,
:list_id => job_list.list_id
).any?
end
#candidate = FlaggedCandidate.new
select! simply change the original collection, instead of doing #job_list = #job_list.select { ... }

Ruby - how to save values from checkboxes to database

I have an array of values, like:
- #colors.each do |color|
= check_box_tag 'colors[]', color.id
Every time, when I update the values from those checkbox in the DB table, I am doing that by this way:
UserColor.delete_all(['user_id = ?'], current_user.id) #delete all user's rows
unless params[:colors].nil?
params[:colors].each do |color|
UserColor.create(:user_id => current_user.id, :color_id => color)
end
end
It's the working solution, but I don't like it very much. That's why I would like to ask you, how do you solve this problem and if doesn't exist any some better way to do it.
Thanks
class User
has_many :colors, through: :user_colors
end
in controller
if params[:colors]
user = current_user
user.color_ids = params[:colors]
user.save
end
or u can try
current_user.update_attribute(:color_ids, params[:colors]) if params[:colors]
I would define a method in the User model
def update_colors!(new_color_ids)
# get an array of the current color ids
old_color_ids = user_colors.map(&:color_id)
# destroy colors that appear in the old list but not in the new
user_colors.where(color_id: old_color_ids - new_color_ids).destroy_all
# add colors that appear in the new list but not in the old
(new_color_ids - old_color_ids).each do |add_color_id|
user_colors.create!(color_id: add_color_id)
end
end
From the controller, just call
current_user.update_colors!(params[:colors])
.

How do I define a static value for a parameter of a new class?

I created a model for a thing called a Lesson that has :content and a :user_id. For early builds of this app, I want the content to be changing, based on my entry, and for the user_id to always = 1 so that it's clean in the DB and there's not a nil value.
How do I go about this?
In my lessons_controller.rb I have this:
def create
#lesson = Lesson.new(params[:lesson])
if #lesson.save
... do something
else
... do something else
I'm guessing this would be the best place to define that the user_id = 1 but how should I go about that?
You can just set #lesson.user_id = 1 in the line after you create it with new, and before you save it.
Another way to do it would be to set a hook in the lesson model -
before_validation :on => :create do |lesson|
lesson.user_id = 1
end

Resources