im a new developer and i need some help to refactor this code
So, I have a Rails App with a controller called subscription_signups. There i have a New method, that helps me create a Subscription, and it executes a private method called add_plan_to_cookies.
This is my controller
def new
#subscription_signup = SubscriptionSignup.new(account_id: current_account.id)
add_plan_to_cookies
end
private
def add_plan_to_cookies
plan = current_account.base_plans.find_by(id: params[:base_plan])&.current_plan
remember_plan(plan.id) if plan.present?
#plan = current_account.base_plans.find_by(id: cookies.signed[:plan])&.current_plan
end
def remember_plan(plan)
cookies.signed[:plan] = plan
end
In the add_plan_to_cookies method, the plan is obtained through the base_plan_id and then another method called remember_plan is executed and saves the plan in a cookie. What i have to do is using the plan that was saved in the cookie. & I can obtain that with the second query, but there has to be a better way.
So first I get the ID of the params, look for the plan and add its id to the cookies. Then i have to use that id to search for the plan that you already got before.
MY problem is that the im doing too queries for something that is kind of the same, and i dont now have to refactor it. anyone has a suggestion?
In the view i have something like this.
<% if #plan.present? %>
<%= #plan.name %>
<%= image_tag #plan.base_plan.cover(:medium), class: "img-responsive "%>
<% end %>
It seems that you store the id of current_plan in the cookie and then you load a base_plan with the id of your current_plan and then proceeds to check if it has yet another current_plan. Written like this:
#plan = current_account.base_plans.find_by(id: current_account.base_plans.find_by(id: params[:base_plan])&.current_plan || cookies.signed[:plan])&.current_plan
I don't know your datastucture, but did you perhaps mean to write something like?
remember_plan(params[:base_plan]) if plan.present?
If that's the case I would write it like so:
def new
#subscription_signup = SubscriptionSignup.new(account_id: current_account.id)
#plan = load_plan
remember_plan(#plan)
end
private
def load_plan
plan = current_account.base_plans.find_by(id: params[:base_plan_id])&.current_plan
plan ||= current_account.base_plans.find_by(id: cookies.signed[:plan_id])&.current_plan
end
def remember_plan(plan)
cookies.signed[:plan_id] = plan.id if plan.present?
end
I don't entirely understand the intentions of your code, but as I see it you want to load the plan in to an instance variable so it is exposed to the view and also want to save the plan for later use. It's two responsibilities, and they are both in one method add_plan_to_cookies. I would split it up in a load and a store method.
I recommend making your variables names that contain id end with _id, so it's more specific what kind of object you work with. Like params[base_plan_id] or cookies.signed[:plan_id]. But it's personal preference I guess.
I might have compleetly misunderstood your code. Please let me know.
Related
The current controller code I'm using to create a comment on a Post object is this:
commentable = #post
comment = commentable.comments.create
# comment.title = params[:title] #Title not needed
comment.comment = params[:comment][:comment]
comment.user = current_user
gon.post_id = #post.id #for javascript
if comment.save
....
This is also how it's recommended on the docs.
Is there a way to do this by passing the params into the .create function, like
(user = current_user ....)
This avoid making 2 calls to the db on a create, thus upping performance.
Additionally, if you're using the public_activity gem it makes things easier too. because it adds a Created and Updated activity on my database every time I write a comment.
Thnaks
more simpler approach to include commentable model in your rails app..Railscasts for adding commentable
Oh, it was actually really simple.
comment = commentable.comments.create(:comment => params[:comment], :user => current_user)
Add :title if you're using that feature.
I don't understand why this isn't the solution provided in the docs? I should probably modify it to use
create(comment_params)
questions_controller.rb
def index
#questions = Question.all(app_params)
end
private
def app_params
params.require(:questions).permit(:question, :answer)
end
end
question.rb
class Question < ActiveRecord::Base
end
I am completely new to ruby-on-rails. I was following a guide and it said I should take care of some "loopholes" or "security issues" and it used attr_accessible, but on Rails 4, they suggest strong parameters, so now I'm trying to use them. I'm confused on how to define the :questions params, because I'm currently getting an error saying that :questions param is not found.
:questions is pretty much something that I will define myself as the web developer.
So for example, I will define questions = "How are you?", "What is your name?". I'm basically starting very simply. I want questions that I have created to be displayed on my webpage. Ultimately, I plan to make a website what is basically a list of questions and, with answer options. After the user clicks "submit" I want to store the information into my database.
Am I supposed to even be requiring this as a param? I'm completely lost..
Do you have a dump of the params we could look at? They are shown when your app encounters an error, and typically shows you the params array which rails will pass through
Strong Params In Rails 4
Strong Params allow you to allow certain parameters for use in the controller, protecting against any malicious assignment client-side. They replaced attr_accessible in Rails 4.0
Strong Params is only for user-submitted content, as it's designed to protect the params hash. To that end, it's mostly used with the create and find functions:
class PeopleController < ActionController::Base
# Using "Person.create(params[:person])" would raise an
# ActiveModel::ForbiddenAttributes exception because it'd
# be using mass assignment without an explicit permit step.
# This is the recommended form:
def create
Person.create(person_params)
end
# This will pass with flying colors as long as there's a person key in the
# parameters, otherwise it'll raise an ActionController::MissingParameter
# exception, which will get caught by ActionController::Base and turned
# into a 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap { |person|
person.update!(person_params)
}
end
private
# Using a private method to encapsulate the permissible parameters is
# just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end
params.require
The params.require function works by taking this params hash:
params{:question => {:question => "1", :answer => "5"}}
That's why people asked what your params hash looks like, because the require function can only work if the :question hash is present.
Possible Solutions For You
Question.all(app_params)
Regardless of what you're trying to achieve, don't use all. The where function is better for receiving an array of data based on certain values. I believe all is depreciated anyway.
def index
#questions = Question.where("value = ?", variable)
end
What data is being passed?
I will define questions = "How are you?", "What is your name?"
This is okay, but typically in rails, you'd call data by using an ID in the database. If you're defining these questions in a form, you'd use the strong params system; but you'd need a form to submit the data to
Further Additions
The rails way is to keep all your data in a database, and use the application to manipulate that data, either by showing it, or allowing people to input more.
The "params" variables are basically there to help the rails controllers & models accept & process data from end users, and consequently allow you to keep the system growing. Instead of having to write custom code to accommodate all sorts of different data, the params give you a rigid structure to work with. Here is a good explaination of how MVC (and params) works for you: How does an MVC system work?
I think you're getting confused with how your app should work
Your "questions" should be stored in a questions table / model, and can be accessed by calling their ID's with the find function. This code would be like this:
#app/controllers/questions_controller.rb
def show
#question = Question.find(params[:id])
end
If you want to add new questions, you'll be best to add them to the questions table, like this:
#app/controllers/questions_controller.rb
def new
#question = Question.new
end
def create
#question = Question.new(question_params)
#question.save
end
private
def question_params
params.require(:question).permit(:question)
end
#app/views/questions/new.html.erb
<%= form_for #question do |f| %>
<%= f.text_field :question %>
<% end %>
This will give you a central store of your questions, which you'll then be able to access when you need them, either with a helper or with your ".all" call :)
Give it a shot with question (singular):
params.require(:question).permit(:text, :answer)
Assuming question is your model and text (which I made up) is the wording of the question.
I have a mass assignment error I can only fix with UGLY code. I must be doing something wrong.
Let me explain:
I my rails application clients have many contacts. The Client page shows a list of contacts belonging to that client.
I want to be able to add a new contact to the list. So I pass the client to the contact controller using...
<%= link_to('new contact', new_contact_path(client_id: #client)) %>
In the ContactsController....
def new
client=Client.find(params[:client_id])
#contact=client.contacts.new
end
def create
#client = Client.find(params[:contact][:client_id])
#contact= #client.contacts.build(params[:contact])
if #contact.save
...
The 'save' results in an unsurprising error:
Can't mass-assign protected attributes: client_id
because the contact params include the client_id which is not (and should not be) attr_accessible in the contact model
The only way I know how to fix the problem is to set every parameter individually (excluding the client_id) as follows:
#contact= #client.contacts.build(first_name: params[:contact][:first_name], last_name: params[:contact][:first_name], email: params[:contact][:email])
This approach works but it just all seems wrong. Surely is some more elegant alternative.
Any help is appreciated.
(yes i am new to rails)
To skip the part to tell you how you should fix your error, I would like to tell you how you should do the coding in the first place :) Correct if I am wrong.
In Routes.rb you should have put(if not so already):
resources :client do
resources : contacts
end
Then, second in you view file you should put something like this:
<%= link_to('new contact', new_client_contact_path(#client)) %>
In that way, you don't have to do anything in your create action, rails will manage all other things.
That is the way it is supposed to be done
Edit:
just to make it more clear.
In new action in your contacts controller you should put:
user= user.find(params[:user_id])
#2nd you build a new one
#contact= user.contacts.build
And in your create action in contacts controller , you should put:
user = User.find(params[:user_id])
#2nd you create the contact with arguments in params[:contact ]
#contact = user.contact.create(params[:contact ])
response .....
I have a resource in my project that collects some information from a user. Basically it's a form that they fill out before they can access another area of the site. It then sets a cookie for a week, but if they come back it will look up their previous entry and keep their preferences tied to them (and will update any details as long as the email address matches).
Currently I have a Applicants controller that looks like this:
class ApplicantsController < ApplicationController
...
def create
#applicant = Applicant.find_or_initialize_by_email(params[:applicant])
if #applicant.new_record? ? #applicant.save : #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
def update
if #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
end
The set_cookie_and_redirect is a private method that just sets some cookies and redirects the user to a page. The code works, but it just feels dirty. It's essentially updating a record within the create method under the condition that it's not a new record. I'm also forced to have an update method in case an existing record comes back with a validation error--the form helper will then switch the form over to sending to the update method.
So to my point... is there a more appropriate way to push the update_attributes call in the create method to the update method? Or better put, is there a better way to respect the RESTful methods in isolating the create and update functionality?
UPDATE: I wanted to be a little more specific too. If the user has filled this form out before it will set a cookie so they don't have to fill it out again for seven days. However after seven days the cookie is expired and they see the form again. The controller doesn't know if the user is new or existing until they add user input into the form which is then compared based on the email address.
Thanks in advance! I definitely look forward to anyone's thoughts on this.
The create method should only create, and the update method should only update. Let Rails decide which is going to happen based on what is inside of #applicant when the form is rendered - It essentially does what you're doing: Checks if the record is new or not, and sends it to update/create accordingly. Example:
def applicant
#applicant = Applicant.find_or_initialize_by_email(cookies[:email])
# renders applicant.html.erb form
end
<%= form_for #applicant do |f| %>
# ... fields ...
<% end %>
def create
#applicant = Applicant.new(params[:applicant])
#applicant.save
# .. etc.
end
def update
#applicant = Applicant.find_by_email(cookies[:email])
#applicant.update_attributes(params[:applicant])
# ... etc.
end
Rails will send the request to the correct action based on the new_record? status of the Applicant object.
I have been trying to get my head around render_to but I haven't had much success.
Essentially I have controller methods:
def first
#I want to get the value of VAR1 here
end
def second
VAR1 = ["Hello", "Goodbye"]
render_to ??
end
What I can't figure out is how to accomplish that. Originally I just wanted to render the first.html.erb file but that didn't seem to work either.
Thanks
Edit: I appreciate the answers I have received, however all of them tend to avoid using the render method or redirect_to. Is it basically the case then that a you cannot pass variables from controller to controller? I have to think that there is some way but I can't seem to find it.
It is not a good idea to assign the object to a constant. True this is in a global space, but it is global for everyone so any other user going to this request will get this object. There are a few solutions to this.
I am assuming you have a multi-step form you are going through. In that case you can pass the set attributes as hidden fields.
<%= f.hidden_field :name %>
If there are a lot of fields this can be tedious so you may want to loop through the params[...] hash or column_names method to determine which attributes to pass.
Alternatively you can store attributes in the session.
def first
#item = Item.new(params[:item])
session[:item_attributes] = #item.attributes
end
def second
#item = Item.new(session[:item_attributes])
#item.attributes = params[:item]
end
Thirdly, as Paul Keeble mentioned you can save the model to the database but mark it as incomplete. You may want to use a state machine for this.
Finally, you may want to take a look at the Acts As Wizard plugin.
I usually don't have my controllers calling each other's actions. If you have an identifier that starts with a capital letter, in Ruby that is a constant. If you want to an instance level variable, have it start with #.
#var1 = ["Hello", "Goodbye"]
Can you explain what your goal is?
Have you considered using the flash hash? A lot of people use it solely for error messages and the like, it's explicitly for the sort of transient data passing you might be interested in.
Basically, the flash method returns a hash. Any value you assign to a key in the hash will be available to the next action, but then it's gone. So:
def first
flash[:var] = ["hello", "goodbye"]
redirect_to :action => :second
end
def second
#hello = flash[:var].first
end
way 1
Global variable
(fail during concurrent requests)
way 2
class variable
(fail during concurrent requests)
way 3
Stash the object on the server between requests. The typical way is to save it in the session, since it automatically serializes/deserializes the object for you.
Serialize the object and include it in the form somewhere, and
deserialize it from the parameters in the next request. so you can store attributes in the session.
def first
#item = Item.new(params[:item])
session[:item_attributes] = #item.attributes
end
def second
#item = Item.new(session[:item_attributes])
#item.attributes = params[:item]
end
way 4
The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed to the very next action and then cleared out.
def new
#test_suite_run = TestSuiteRun.new
#tests = Test.find(:all, :conditions => { :test_suite_id => params[:number] })
flash[:someval] = params[:number]
end
def create
#test_suite_run = TestSuiteRun.new(params[:test_suite_run])
#tests = Test.find(:all, :conditions => { :test_suite_id => flash[:someval] })
end
way 5
you can use rails cache.
Rails.cache.write("list",[1,2,3])
Rails.cache.read("list")
But what happens when different sessions have different values?
Unless you ensure the uniqueness of the list name across the session this solution will fail during concurrent requests
way 6
In one action store the value in db table based on the session id and other action can retrieve it from db based on session id.
way 7
class BarsController < UsersController
before_filter :init_foo_list
def method1
render :method2
end
def method2
#foo_list.each do | item|
# do something
end
end
def init_foo_list
#foo_list ||= ['Money', 'Animals', 'Ummagumma']
end
end
way 8
From action sent to view and again from view sent to other actions in controller.