find_by_or_initialize_by*_and_* and increment! - ruby-on-rails-3

I'm a little slow when it comes to programming, but I enjoy it and try to learn as much as I can. So apologies in advance if i'm not clear enough with my question.
I have an app with a sole purpose for DJs to review songs and the song reviews to be grouped by state. 5 Fields… songid, state, stdjlike, stthinkclublike, stplait (Horrible names I know, but they help me to remember what they are :)).
Once a user decides to review a song, they will be asked 3 questions which are answered on a scale of 1 to 5, the form collects the 3 answers as well as the users state and the id of the song. Most of the time the database will just find the state and song id, then add the new data to the 3 question fields (djlike, thinkclublike, plait). But the first review for a song from a user in a new state will have to create a new record.
So again, just to be crystal clear, the first time a user from "New York" reviews (song) "123" it will create a new record with the song id, state name, and add the review values to the 3 columns stdjlike, stthinkclublike and stplait. Next user, a different user, from NY for song "123" creates a review, it will just update the record, adding the values for stdjlike, stthinkclublike and stplait to the previous review.
I have been looking around all week and finding lots of good solutions to similar but not the same problems (A well documented solution here). I decided to go with increment because I'm updating with values that aren't fixed numbers, I've been trying but I just can't get it to work.
I started with what works. The create in the reviewstate controller saves the record when it is just a simple .new and .save. So i changed it from there to this..
def create
#reviewstate = Reviewstate.find_or_initialize_by_songid_and_state(song.id, current_user.state)
#reviewstate.stdjlike.increment!(:stdjlike, reviewstate.stdjlike)
#reviewstate.stthinkclublike.increment!(:stthinkclublike, reviewstate.stthinkclublike)
#reviewstate.stplait.increment!(:stplait, reviewstate.stplait)
#reviewstate.save!
end
Stops working. I have tried different variations and methods, this is when I feel the closest. Please help me, its been 4 days :).
Thank you in advance.

If find does not return a record and a new one is initialized, stdjlike will be nil (unless you set another default value in your migration). Thus, increment will try to increment by Nil. Nil is not a fixnum though and it will not work. I suppose that this is the error you get.
Moreover, you store your object in #reviewstate and then you refer to it by reviewstate.stdjlike. You have an instance variable #reviewstate and you then refer to the local variable reviewstate. They are two different objects.
You should handle things differently. Check if your record exists. If yes, just get it. If not, create it. Use Reviewstate.exists?.
Also, bear in mind that Ruby uses underscores as a convention for names. That would make it something like std_jlike or something like that. But you can always use what you feel more comfortable with.

This is going to sound pretty drastic, but I think will make things simpler for you in the long run. I'm going to recommend a pretty big refactor. It sounds like you have a few things which should be their own models:
User (or DJ)
State
Song
Review
I would structure the application so that your reviews belong to one state, one user, and one song:
#user.rb
class User << ActiveRecord::Base
has_many :reviews
end
#state.rb
class State << ActiveRecord::Base
has_many :reviews
end
#song.rb
class Song << ActiveRecord::Base
has_many :reviews
end
#review.rb
class Review << ActiveRecord::Base
belongs_to :user
belongs_to :state
belongs_to :song
end
Then when you want to gather some form of statistics for display you derive it from the review records. This will allow you to get stats across states, DJ's, or songs.
This is also a more RESTful implementation where more objects in your system are represented as resources that can be (C)reated, (R)ead, (U)pdated, and (D)estroyed. In general doing things in a RESTful manner will make your life much easier when working in rails.

Related

How do I retrieve a random GET request in Ruby on Rails 5?

I've created my REST API based on Michael Scott from the Office, so far if you go onto the main part of the API it displays all the quotes /api/v1/quotes/ and you can get individual ones if you add an id number such as /api/v1/quotes/5,but I want users to be able to get a random quote when they put in a GET request.
This would preferably be done with an extra part of the URL such as /api/v1/quotes/random.
I've looked at a lot online but I can't figure this out. Do I put my code into the quotes controller and create another function or do I have to put this somewhere else such as routes.db? Also do I put in the SQL function of RANDOM or is there a better and more efficient way to randomise it. My database only has 50 records in it and it's done in mysql2.
Thanks in advance.
It's my first time posting here as usually I hate asking for help as I think I can always figure it out myself but I'm extremely new to Ruby so I'm fairly clueless on how to solve this. If you need me to clarify my question then just let me know.
You can do that in Model as well as controller.
Model:
class Model < ApplicationRecord
def self.random
Model.limit(1).order("RANDOM()").first
end
end
Controller: In method show
def show
render json: Model.find_by(id: params[:id]) || Model.random
end
I hope that helpful.
I would configure a quotes/random collection route:
# in config/routes.rb
resources :quote do
get 'random', on: :collection
end
and use a controller method like this:
# in controllers/quotes_controller.rb
def random
#quote = Quote.order('RAND()').first
render :show
end
Please note: Returning a single random row from a table is database specific – for MySQL
ORDER BY RAND() LIMIT 1
seems to be the way to go.

Need help in Active Record Associations in Rails with self referential and many to one mapping

Requirement:
I'm creating a twitter like web application for the local campus where users can share their feedback over a service within 140 characters just like the way twitter does.
For every feedback , there can be multiple responses to the feedback. These responses are similar to twitter replies , where replies are also 140 character response like a tweet.
Essentially the screen for entering a feedback or a response is the same with the same functional elements. Example would be :-
This is a demo feedback - user 1
This is a response to the feedback - user 2
I'm using rails for the development and this is what my feedback model looks like
class Feedback < ActiveRecord::Base
attr_accessible :title
end
My table structure in the database looks like this:-
Feedback Table
id , integer , primary key title, varchar (255)
This is how I want to manage responses to a feedback
A separate table named "Feedback_Responses" with the following structure :
feedback_id (int) [This is a parent feedback id] response_id
(int) [This is a response to the parent feedback id]
My Question
I understand that self referential mapping is needed over here with many to one relationship. However, I'm not clear as to how to proceed with creation of a response from the same view that is used to create a feedback. And secondly, a response is another feedback to the system so unless a response is created and its id is generated , the Feedback_Responses table wont be populated with the required mapping. So, in this case I'm really clueless and perhaps confused as to how to create responses and manage their mappings on a separate table. I'm starting to learn rails with this application.
I'm not understanding your design very well, but it looks like your main entity (equivalent to a tweet) is the "feedback" and each "feedback" can have many "responses" (like comments). What is not clear to me is whether the "response" can be a "feedback" in its own right, but I'm going to assume it's not, for now. So, your design says, according to this description, that a feedback has many responses. I find that "feedback_responses" table irrelevant for the matter, because it sounds like a response is connected to one and only one feedback. So, drop the table.
So, I would write it like this:
# table feedbacks
# id: Integer
# title: Varchar 140
class Feedback < ActiveRecord::Base
has_many :responses
end
# table responses
# id: Integer
# feedback_id: Integer
class Response < ActiveRecord::Base
belongs_to :feedback
end
Still, if you want to handle the response has a feedback which is not a response to another feedback, you can just use inheritance as a solution:
# additionally feedback_id: Integer to the feebacks table
class Feedback < ActiveRecord::Base
has_many :responses
end
class Response < Feedback
belongs_to :feedback
end
View generation will be automatically easy as long as you have two distinguishable models.
class Feedback < ActiveRecord::Base
attr_accessible :title
validates :title, :length => { :maximum => 140 }
has_many_and_belong_to :responses
end
class Response < ActiveRecord::Base
has_many_and_belong_to :feedbacks
end
To fetch responses from a feedback object use #feedback.responses
same for the response object #response.feedbacks
This relation is easy to maintain. You can add middle table if you need.

Why is a SystemStackError caused with a has_many association in Ruby on Rails?

I stumbled over a SystemStackError and found where it is caused in the source code. Though, I did not quite understand why it happens. Maybe you can help me.
Here is the scenario:
There are two models Facility and Location given by their model definitions in the following.
class Location < ActiveRecord::Base
belongs_to :facility
accepts_nested_attributes_for :facility
end
class Facility < ActiveRecord::Base
has_many :locations
accepts_nested_attributes_for :locations
end
Now I create an object of each class in the Rails console: rails c.
location = Location.create(...)
facility = Facility.create(...)
Then I want to associate both with each other.
location.facility = facility
facility.locations << location
I cannot execute the last command when I executed the first before - it raises a SystemStackError: stack level too deep. Though, I can run the association commands separate from each other or sequential but in reverse order. The problem is that I cannot add the location again. Why?
Why do both?
This line:
facility.locations << location
Will already set the location's facility to be the specified facility. Both lines in this case are doing the same thing. What I would recommend doing is to use the association builder, like this:
facility.locations.create!(...)
This way, Rails takes care of setting the facility_id field, rather than you doing a manual assignment after it.
The first thing that I would suspect here is that the has_many association is really has_too_many. In other words, you may have too many locations in the relationship.
In fact, given the code you posted, you seem to have created an infinite loop of associations. You wrote:
accepts_nested_attributes_for :facility
I am assuming that this causes ActiveRecord to open the facility attribute where it finds another location with yet another facility attribute ad infinitem. before you dig too deeply, try this to see if it works:
facility.locations << location
location.facility = facility
However, be wary because this might just push the stack error to some other place in the app. If you Google for that error message you can find several people who have run into infinite recursion problems, generally related to saving a record.

Do Rails 3 Active Record dynamic find_or_create finder methods have some undocumented inconsistencies?

Apologies for the long title, but this is bothering me. I'm new to Rails, so this is my first project. Rails 3.0.3.
In my model, a User may or may not have read many Entries; this is tracked in a model called ReadEntries. This many-to-one relationship is properly defined in the code, I think.
User.rb:
has_many :read_entries
Entry.rb:
has_many :read_entries
ReadEntry.rb:
belongs_to :entry
belongs_to :user
This table has to be populated at some point. If I try to do this:
user.read_entries.find_or_create_by_entry_id(entry.id, :read => false)
I get the error Unknown key(s): read. Leave out trying to set :read, and it works.
However, if I create the same row with this, it works:
ReadEntry.find_or_create_by_entry_id_and_user_id(entry.id, user.id, :read => false)
Logically, these methods should be identical, right? Thanks.
I've also had weird experiences with find_or_create. I would love it if it worked, but it seems inconsistent.
I'm currently having the same issue as you, and I think it may be due to calling find_or_create on an association as opposed to the model directly. Here's my example:
permission_assignments.find_or_create_by_role_id(:role_id => role_id, :is_allowed => false)
This works to create the assignment, except the "is_allowed" field gets set to it's default of "true". This code works for me (in the Permission model, hence the self reference)
PermissionAssignment.find_or_create_by_permission_id_and_role_id(:permission_id => self.id, :role_id => role_id, :is_allowed => false)
It's more verbose, unfortunately, but it works. The only problem that I still notice is that the object that is returned has no id assigned (the record does get created in the database, however, but if I wanted to update any more attributes I wouldn't be able to without the id). Don't know if that's a separate issue or not.
Rails 3.0.4 here with Postgres 8.4
You cannot pass in other fields like that as Rails will assume they are options for the find. Instead, you will need to make your method call longer:
user.read_entries.find_or_create_by_entry_id_and_read(entry.id, false)
Or alternatively use a shorter, custom syntax for that.
For your final example, my thoughts are that Rails will take the second argument and use that as options. Other than that, I am not sure.

Help with Rails find_by queries

Say if #news_writers is an array of records. I then want to use #news_writers to find all news items that are written by all the news writers contained in #news_writers.
So I want something like this (but this is syntactically incorrect):
#news = News.find_all_by_role_id(#news_writers.id)
Note that
class Role < ActiveRecord::Base
has_many :news
end
and
class News < ActiveRecord::Base
belongs_to :role
end
Like ennen, I'm unsure what relationships your models are supposed to have. But in general, you can find all models with a column value from a given set like this:
News.all(:conditions => {:role_id => #news_writers.map(&:id)})
This will create a SQL query with a where condition like:
WHERE role_id IN (1, 10, 13, ...)
where the integers are the ids of the #news_writers.
I'm not sure if I understand you - #news_writers is a collection of Role models? If that assumption is correct, your association appears to be backwards - if these represent authors of news items, shouldn't News belong_to Role (being the author)?
At any rate, I would assume the most direct approach would be to use an iterator over #news_writers, calling on the association for each news_writer (like news_writer.news) in turn and pushing it into a separate variable.
Edit: Daniel Lucraft's suggestion is a much more elegant solution than the above.