Suppose I have this association:
class User < ActiveRecord :: Base
has_one :car
end
class Car < ActiveRecord :: Base
belongs_to :user
end
routes:
resources :users do
resources :cars
end
Then what would be the code, in the 'new' action method in CarsController
class CarsController < ApplicationController
def new
#??? what's necessary to be put here,
# if I have request 'localhost:3000/users/1/cars/new'
end
...
end
Will Rails figure out everything automatically so I don't have to write any code in the 'new' method? Also, since the 'new' action will generate a 'form_for(#car)' form helper, how can I create this car object
Is this right?
class CarsController < ApplicationController
def new
#user = User.find(params[:user_id])
#car = #user.build_car({})
end
end
That looks just fine. Rails will not do any of this automatically. There are gems out there that can automate some of that if you like, but the jury is out on whether they're actually worth your time.
If you have no params to pass to the car, you can just run #user.build_car with no arguments, by the way. You'll also need to specifically say in the form_for helper that you're nesting the car under the user: form_for [#user, #car] Otherwise the form's destination will be /cars instead of /user/1/cars.
You're pretty close, Baboon. I would actually do:
class UsersController < ApplicationController
def new
#user = User.new
#car = #user.cars.build
end
end
But if you don't want to create the Car at the same time as the user, try:
class CarsController < ApplicationController
def new
#user = User.find(params[:user_id])
#car = #user.cars.build
end
end
I found the nested_form railscast extremely helpful when doing this sort of thing. In fact, I think you'll probably get a lot out of it (and using the nested_form gem).
railscasts_196-nested-model-form-part-1
Related
Trying to figure our how to set up associations in form.
I have 3 models:
class Request < ActiveRecord::Base
has many :answers
has many :users, through: :answers
end
class Answer < ActiveRecord::Base
belongs to :user
belongs to :request
end
class User < ActiveRecord::Base
has many :answers
has many :requests, through: :answers
end
I am trying to figure out: how to have a User link to Answer#new from Request#Show, and then create an Answer record passing in the Request#Show request_id from the previous page - creating an association between the User's Answer and the Request he was viewing.
My method of doing this now is: I flash the request_id value on Request#Show, and then when a User links to Answer#new, it passes the flashed value into a hidden form tag on Answer#new. This does not seem like the best way to do this.
Any thoughts?
Kudos for the creative approach using flash, however your right there is an easy way. You can pass parameters much between controllers just like passing parameters between methods using the route names.
I didn't quite follow what it was you were trying to achieve in this case but it looks like this blog entry here should get you started..
https://agilewarrior.wordpress.com/2013/08/31/how-to-pass-parameters-as-part-of-the-url-in-rails/
Good luck!
User link to Answer#new from Request#Show
This can be achieved with either sessions or nested resources (or both!). Let me explain:
I would definitely add a nested resource to your requests routes:
#config/routes.rb
resources :requests do
resources :answers, only: [:new, :create] #-> url.com/requests/:request_id/answers [POST]
end
This gives you the ability to call a "nested" route (IE one which sends data to a child controller, and requires "parent" data to be appended to the request).
In your case, you want to create an answer for a request. The most efficient way is to use a routing structure as above; this will allow you to use the following controller method:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
#request = Request.find params[:request_id]
#answer = #request.answers.new
end
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.save
end
private
def answer_params
params.require(:answer).permit(:title, :body)
end
end
The above gives you the ability to create an answer by passing the request_id to the nested route. You must note the corresponding route will require a POST method in order to work.
You don't need the new method. If you wanted it, it can easily be handled with the above structure.
Passing the user is a little more tricky.
You can either use the routes, or set a session.
I would personally set a session (it's cleaner):
#app/controllers/requests_controller.rb
class RequestsController < ApplicationController
def show
session[:user_id] = #user.id #-> I don't know how you populate #user
end
end
This will give you the ability to access this session here:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
user = User.find session[:user_id]
end
end
#app/views/requests/show.html.erb
<%= link_to "New Answer", request_new_answer_path(request) %>
--
If you're using Devise, the user object should be available in the current_user object (which means you don't have to set session[:user_id]):
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
## current_user available here if using devise
end
end
To assign a #user to the new answer record, just do this in answers#create:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
...
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.user = current_user
#answer.save
end
end
Something like this worked for me:
I have two models (Formula and FormulaMaterial)
Formula has_many FormulaMaterials, which belongs to Formula
My Formula controller sets #formula like so:
#formula = Formula.find(params[:id])
I list my Formula Materials in my Formula show.html.erb by declaring it in my Formula controller like so:
#formula_materials = FormulaMaterial.where(:formula_id => #formula)
When I want to add a new FormulaMaterial to my Formula, the "New Formula Material" button in my show.html.erb file looks like this:
<%= link_to 'Add Material To Formula', new_formula_material_path(:formula_id => #formula), class: "btn btn-success" %>
In the "new_..._path" I set the associated id to the #formula variable. When it passes through to the new.html.erb for my FormulaMaterial, my URL looks like so:
http://localhost:3000/formula_materials/new?formula_id=2
In my FormulaMaterial new.html.erb file, I created a hidden_field that sets the value of the association by using "params" to access the formula_id in the URL like so:
params[:formula_id] %>
I am not sure if this is the best way to do this, but this way has allowed me to pass through the view id from the previous page as a hidden, associated and set field in the form every time.
Hope this helps!
I'm using Pundit for authorization and I want to make use of its scoping mechanisms for multi-tenancy (driven by hostname).
I've been doing this manually to date by virtue of:
class ApplicationController < ActionController::Base
# Returns a single Client record
def current_client
#current_client ||= Client.by_host(request.host)
end
end
And then in my controllers doing things like:
class PostsController < ApplicationController
def index
#posts = current_client.posts
end
end
Pretty standard fare, really.
I like the simplicity of Pundit's verify_policy_scoped filter for ensuring absolutely every action has been scoped to the correct Client. To me, it really is worthy of a 500 error if scoping has not been officially performed.
Given a Pundit policy scope:
class PostPolicy < ApplicationPolicy
class Scope < Scope
def resolve
# have access to #scope => Post class
# have access to #user => User object or nil
end
end
end
Now, Pundit seems to want me to filter Posts by user, e.g.:
def resolve
scope.where(user_id: user.id)
end
However, in this scenario I actually want to filter by current_client.posts as the default case. I'm not sure how to use Pundit scopes in this situation but my feeling is it needs to look something like:
def resolve
current_client.posts
end
But current_client is naturally not going to be available in the Pundit scope.
One solution could be to pass current_client.posts to policy_scope:
def index
#posts = policy_scope(current_client.posts)
end
But I feel this decentralizes my tenancy scoping destroys the purpose of using Pundit for this task.
Any ideas? Or am I driving Pundit beyond what it was designed for?
The most "Pundit-complient" way to deal with this problem would be to create a scope in your Post model:
Class Post < ActiveRecord::Base
scope :from_user, -> (user) do
user.posts
end
end
Then, you will be able to use it in your policy, where user is filled with the current_user from your controller:
class PostPolicy < ApplicationPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope.from_user(user)
end
end
end
If you are returning an ActiveRecord::Relation from the scope, you can stop reading from here.
If your scope returns an array
The default ApplicationPolicy implement the method show using a where:
source.
So if your scope does not return an AR::Relation but an array, one work-around could be to override this show method:
class PostPolicy < ApplicationPolicy
class Scope
# same content than above
end
def show?
post = scope.find do |post_in_scope|
post_in_scope.id == post.id
end
post.present?
end
end
Whatever your implementation is, you just need to use the PostPolicy from your controller the "Pundit-way":
class PostsController < ApplicationController
def index
#posts = policy_scope(Post)
end
def show
#post = Post.find(params[:id])
authorize #post
end
end
I have some methods now under "profile" like user blocking, banning, moderation.
It feels these should belong under "user" and inside the user controller.
Is there a way to have a user_controller.rb when using devise with a user model?
Reason for this is to scope all user related methods under the user_controller instead of the profile_controller as it is now.
Yes. There is no problem with that. You can simply create users_controller.rb and interact with User model like:
class UsersController < ApplicationController
# do any stuff you need here
def block
#user = User.find(params[:id])
#user.block
end
def ban
#user = User.find(params[:id])
#user.ban
end
end
For sure, you have to create routes for this controller:
resources :users, only: [] do
member do
get :ban
get :block
end
end
Like that.
When my user creates an object that should have his id on it, the id comes out nil. Ideas why?
# controllers/units_controller.rb
class UnitsController < ApplicationController
before_filter :authenticate_user!
def new
#unit = Unit.new
#unit.user = current_user # also tried .user_id = current_user.id
[...]
# models/unit.rb
class Unit < ActiveRecord::Base
belongs_to :user
# :user has_many :units too
[...]
The object is saved successfully, but with an empty user_id field (showing nil).
No exception is thrown. There is no callback on save.
If you're doing this in the standard Rails two step (#new (POST)-> #create), then you need to associate Unit with current_user in the #create action.
def create
#unit = Unit.new
#unit.user = current_user
# ...
end
In my Ruby on Rails app, I've got:
class AdminController < ApplicationController
def create
if request.post? and params[:role_data]
parse_role_data(params[:role_data])
end
end
end
and also
module AdminHelper
def parse_role_data(roledata)
...
end
end
Yet I get an error saying parse_role_data is not defined. What am I doing wrong?
Helpers are mostly used for complex output-related tasks, like making a HTML table for calendar out of a list of dates. Anything related to the business rules like parsing a file should go in the associated model, a possible example below:
class Admin < ActiveRecord::Base
def self.parse_role_data(roledata)
...
end
end
#Call in your controller like this
Admin.parse_role_data(roledata)
Also look into using (RESTful routes or the :conditions option)[http://api.rubyonrails.org/classes/ActionController/Routing.html] when making routes, instead of checking for request.post? in your controller.
Shouldn't you be accessing the parse_role_data through the AdminHelper?
Update 1: check this
http://www.johnyerhot.com/2008/01/10/rails-using-helpers-in-you-controller/
From the looks of if you're trying to create a UI for adding roles to users. I'm going to assume you have a UsersController already, so I would suggest adding a Role model and a RolesController. In your routes.rb you'd do something like:
map.resources :users do |u|
u.resources :roles
end
This will allow you to have a route like:
/users/3/roles
In your RolesController you'd do something like:
def create
#user = User.find_by_username(params[:user_id])
#role = #user.roles.build(params[:role])
if #role.valid?
#role.save!
redirect_to #user
else
render :action => 'new'
end
end
This will take the role params data from the form displayed in the new action and create a new role model for this user. Hopefully this is a good starting point for you.