I am building a project management app and need some help with how to pass a parameter (I think that is how you say it). Here is what I have going on.
I have a table called "proposals" and "proposals" allow you to create multiple concepts per proposal. I have a table called "concepts" and on each "concept" the user can "comment". The way that I have set this up is to generate a table called concept_comments.
Currently the comments are only associated with the concepts but I would like to display all the comment, across all the concepts, for that particular proposal. I am guessing that his has to do with two things:
including another line to collect proposal_id when creating a comment.
Assigning a has_many :concept_comments to the model/proposal.rb file.
Adding map.resources :proposals, :has_many => :concept_comments.
Not sure if that is correct but that is in my head. Only thing I have done so far is to create a column in my concept_comments table called proposal_id. Here is my concept_comments_controller.rb code for 'create':
def create
#concept = Concept.find(params[:concept_id])
#concept_comment = #concept.concept_comments.create!(params[:concept_comment])
respond_to do |format|
format.html { redirect_to #concept }
format.js
end
end
Not quite sure how to tell it to also collect the proposal_id. Somehow I need to tell it to look at the concept_id that has been passed in, then pull the proposal_id number from the concept table, and pass that to the proposal_id in the concept_comments table.
My thinking is that I can then call on the concept_comments table for all entries that have the proposal_id.
I am not even sure if that makes any sense.
well - you could pass in the proposal id, but, if you already have a concept_comment id, you don't need a proposal id
#proposal_comments = ConceptComment.all(:joins => :concept,
:conditions => ["concepts.proposal_id = ?",
#concept_comment.concept.proposal_id])
Where #concept_comment is on a member action of the comments controller - for collection actions, you will need to pass in a proposal id and substitute that in for #concept_comment.concept.proposal_id
Couldn't you just iterate over all of the given proposal's concepts and gather up their comments into one big array, or is this something that's going to be happening millions of times a second?
Related
Rails newbie so bear with me...
I created a calendar app and the owner of the calendar can add other users to the calendar. I just realized that they can add the same user many times, which obviously I don't want. I've tried a bunch of different things including methods to check inside of the model, those didn't work... I was using includes to verify...
My current method in my controller is:
def update
if #calendar.calendar_admin?(current_user)
#new_user = User.find(params[:calendar][:user_ids])
if #calendar.users.includes(#new_user)
redirect_to user_calendar_path(#calendar), notice: 'This user has already been added to this calendar.'
else
#calendar.users << #new_user
if #calendar.save
redirect_to user_calendar_path(#calendar), notice: 'Your calendar has been updated.'
else
redirect_to user_calendar_path(#calendar)
end
end
end
end
end
No matter what, it gets stuck at "this user has already been added to this calendar", even if the user hasn't. I am shoveling the new user in but I'm not even sure if that is the right manner to add the new user?
Input?
You want to use include? instead of includes.
include? returns a boolean based on whether a set contains a specific item. includes is a different method and will return a non-nil value that is truthy.
You want to test if #calendar.users already contains a specific user. Unfortunately, includes sounds right, but it is used to add a table/model to join result (if I try to run your line it explodes).
So I suggest the following alternatives:
if #calendar.users.to_a.any{|uu| uu.id == #new_user.id}
this will fetch all users in an array and check if a user exists with the same id. This is easy to understand, but not optimal since it might retrieve a lot of data.
Probably better alternative is to use the database, and count if a user with your wanted id is linked:
if #calendar.users.where("users.id" => #new_user.id).count > 0
I'm a Rails beginner, and have been reading tutorials and typing out applications for a few months now. I'm really enjoying it after a few years spent in the front end world, and beginning to get up to speed with it all. The time has come though for me to start building my own stuff without any handholding. So far, so good.
I'm creating a basic to-do list app, where goals and tasks are displayed on the same page - goals#index. My issue is that I'm not sure how to get all tasks for a particular goal (that belongs to a user). I understand that I need to pass an ID param to the Goal model in order to find out its tasks, like so:
Goal.find(1).tasks
The above works fine, as I've already set up foreign keys on the tasks table and have a has_many :tasks relationship for the Goal model and a belongs_to relationship for the Task model.
Here's my Goals controller:
def index
#user = current_user
#goals = #user.goals # list all goals for the current user and assign it to the #goals variable.
# Need to find all tasks for each goal ID and assign it to the #tasks variable. Goal ID needs to be supplied here, but it isn't as we're not in show action.
#tasks = Goal.find(1).tasks
As I said, I can find all tasks for a Goal when I manually enter the ID (1 in this example). This works fine in my app, no errors. But obviously I want to supply these IDs dynamically, and I'm just not sure how I get the params in there.
I have tried the below:
#tasks = Goal.find(params[:id]).tasks
and
#tasks = Goal.find(params[:goal_id]).tasks
And I get the "Couldn't find Goal without an ID" error when I try to iterate over #tasks in my view. Which makes sense, as I don't think the goal params are being passed to it as we're not in the Show action.
Surely there must be an easy Rails way?! I'm stumped and don't really know where to look. Thanks for your help and Happy New Year.
You are getting current user's goals, so when you will do this you have one array object. so when you will pass array object to find, it will have multiple IDS. so when need to find All the tasks from all goals you just need to pass Array of IDS instead of single value.
#tasks = Task.where(:goal_id => #goals)
This will run this SQL query.
SELECT "tasks".* FROM "tasks" WHERE "tasks"."goal_id" IN (SELECT
"goals"."id" FROM "goals")
So when you are dealing with array just pass ids. for e.g. [1,2,3]
Once you do #goals = #user.goals (assuming that's working, which it sounds like it is), you have your goals and there is no reason to go back to the DB to "find" them.
To get ALL your tasks from ALL of user's goals, you can do the following:
#tasks = []
#goals.each do |goal|
#tasks << goal.tasks
end
Ah of course, #goals is an array of the user's goals so I can just work with that. So simple when someone just tells you. Thanks for all your help!
Here's my final code that works (I left the controller unchanged). This gets the first goal in the array and then gets the tasks associated with each goal. I have a set number of goals so I can just use goals[0], goals[1] or goals[2] for each goal.
<% #goals[0].tasks.each do |task| %>
<li><div class="task-item"><%= task.task_name %></div></li>
<% end %>
I was reading an article about Rails controllers, can you help me understand please what is meant by the following phrase:
"The best controller is Dilbert-esque: It gives orders without knowing (or caring) how it gets done."
Is it true, in your opinion?
If, for example, I am accessing the index page associated with the subjects controllers, I would define the index method in the subjects_controller.rb rigorously, so I am confused as to what they mean in the article, as I would have thought the opposite.
Any pointers, please?
Thank you and sorry if this is too interpretable. This is the original article: http://betterexplained.com/articles/intermediate-rails-understanding-models-views-and-controllers/
This article is talking about MVC architecture. What's important to take away from an article like this is the fact that Rails is best written with Fat Models and Thin Controllers. This means that you want to have the bulk of your methods/functions in your Model and want to have calls to the functions from your controller. Index is a bad example since typically you're not going to have a lot going on in there.
Your controller for index will typically look something like this
def index
#subjects = Subject.all
end
If you want to scope order for displaying your subjects though, you would do that in your model with a block as follows:
default_scope { order("id DESC") }
A less contrived example might look something like this: Say for example you have an app that accepts input, takes that input and tallies several counters based on what the user entered. Your controller might be named subject_tally and look like this:
def subject_tally
#subject = Subject.find(params[:id])
#subject.winnings += 1
#subject.total_matches += 1
#subject.win_percentage = #subject.winnings.to_f/#subject.total_matches
redirect_to subjects_path
end
THIS IS WRONG. This is a very fat controller and easily moved to the Model where it should be.
If written properly it would look something like this:
subjects_controller.rb: (The Controller)
def subject_tally
#subject = Subject.find(params[:id])
#subject.subject_tally
redirect_to subjects_path
end
subject.rb: (The Model)
def subject_tally
self.winnings += 1
self.total_matches += 1
self.win_percentage =winnings.to_f/total_matches
end
So as you can see, you make only one call from the controller and it "doesn't care" what is actually going on in the backend. It's literally there to pass a value (in this case, the ID of the subject in question) and direct you to another page, in this case, the index.
Furthermore, if you'll notice, you don't need to add that pesky #subject everywhere in your model's subject_tally function... you can reference the attributes of the object just by using self.winnings where you're assigning to an attribute. Ruby is smart enough to know the current subject the method applies to (since you called that function ON a subject from the controller) and in fact you don't even need the self. if you're just retrieving the attributes instead of assigning them... which is why we didn't need self before winnings.to_f or the last line's total_matches.
Very convenient, less code, less time, yay.
The best controller is Dilbert-esque: It gives orders without knowing
(or caring) how it gets done.
means that you should put less logic as you can in the controller,
the controller should only know what to call to get what it needs, and should not know how to carry out a certain action.
In the "Sandy Metz rules" for rails developers (http://robots.thoughtbot.com/sandi-metz-rules-for-developers), she says:
Controllers can instantiate only one object. Therefore, views can only
know about one instance variable and views should only send messages
to that object
only one object could seem a bit extreme, but makes the idea about how much business logic (no logic) you should put in the controller.
How would I define in my controller to run and do the following when the Class is fetched in URL;
I need to find all "jobs" to current_user I'm guessing something like this;
#joblisting = current_user.Joblisting.find(params[:id])
Then I need to take those jobs and check if their column "job_status" has the text "completed" in them or other"
If the jobs_status is "completed" then I need to run code so I do an "if"
I would have to pass the calculation.
#joblisting = current_user.Joblisting.find(params[:id])
if #joblisting.where(:project_status => "completed")
number_to_currency(current_user.Joblisting.where(:project_status => 'completed').sum('jobprice') * 1.60 - current_user.Joblisting.where(:project_status => 'completed').sum('jobprice'))
Notifier.notify_payout(current_user).deliver
#joblisting.project_status = 'paid'
#joblisting.save
end
This is what I've got and I'm stuck with passing the calculation to the Notifier.notify_payout template.
I'm sure whomever knows rails better then me, will right away see my mistakes.
My answer will not give you the code you seek. I'm doing this because I feel like you still have a lot to learn, but I will tell you what to do, just not give you the code. If you say that this line of code works....
#joblisting = Joblisting.where(:developer_id => current_developer[:id])
if #joblisting.where(:project_status => "completed")
Notifier.notify_payout(current_developer).deliver
end
then so be it. As for updating the :project_status column from "completed" to "paid", there can be multiply ways to handle this. You could create a method inside your model (let's call it project_is_paid) that changes project status to paid and declare it inside notify_payout method on success.
But that might be confusing for someone else looking at your code and wondering why all of a sudden the record is changing from "completed" to "paid" inside the database. Plus you would have to pass the joblisting object as an argument which is even more work.
Another way to think of it is to just write a simple conditional statement inside the controller. If the mail is delivered, then call the method project_is_paid. Just be careful not to start adding all this logic to your controller, your controller should be concise and short. Let the model deal with the logic.
I want to conclude with going back to your working controller code you posted. I'm willing to bet that you can turn the two following lines into one.
#joblisting = Joblisting.where(:developer_id => current_developer[:id])
if #joblisting.where(:project_status => "completed")
Why do I say that? Well you are making two queries to the same table Job Listings. Here is a little help if you can't figure out how... link
And if I failed to answer your latest question in the comments, I'm sorry. Post an update in your question and I would be happy to update my own answer.
I am new in this world of Rails. And I cannot get my head around this problem. How to get #microposts ordered by the date the micropost was created.
This is the line
#microposts = current_user.followeds.map(&:microposts).flatten
I only managed to order by date the 'followeds', but that is not what I am looking for. All the attempts I have made gave me errors, so I guess I am not aware of some syntax.
Any help is welcome!
Normally, you would add an order clause, as in:
Micropost.where(:written_by => current_user.followeds).order(:created_at)
The way you currently have this line structured doesn't permit that, however, since order is only available on ActiveRecord::Relations, and once you do map you no longer have a Relation available to chain on.
If that's the case, you'll want something like:
current_user.followeds.map(&:microposts).flatten.sort_by { |m| m.created_at }
I think you should try to approach this from another angle.
How about something like this:
#microposts = Micropost.where(author_id: current_user.followed_ids).order(:created_at).all
You might of course have to exchange author_id for whatever foreign key you have to identify what user a Micropost was written by.
If you want to reverse the posts (newest first), you just write order("created_at desc") instead.