Edit author of an article - ruby-on-rails

I have post which belongs_to users, and would like an option when editing to be able to change the user that post is associated with.
I've made a start, but I am very new to Rails and a bit stuck.
I started in my post_controller.rb.
def update
#post.user = associated_user
if #post.update(post_params)
flash[:notice] = "Post was successfully updated"
redirect_to edit_post_path(#post)
else
render 'edit'
end
end
I've defined the method associated_user in my application_controller.rb (I want to do this for a more than just articles.)
def associated_user
#associated_user ||= User.find(session[:user_id]) if session[:user_id]
end
I understand that this code is wrong - I don't want it to get the logged in user, I want to get it from the field I've set in my form view.
<%= f.text_field :associated_user, class: "form-control", placeholder: "Edit Author" %>
Ideally this form field would be a drop down listing all users with a certain status (a boolean I have already set in the users table.)
I'm not sure how far away I am, but if any one is able to offer some guidance, that would be greatly appreciated!

How are the relationships defined in the models. If your post belongs to users, then use a dropdown control in your form:
f.collection_select :user_id, User.whateverscope, :id, :text_method
and when the form is submitted rails will do the rest.
If you have something different set up, then post your models, your strong_params methods, etc.

Related

How to update existing model when another is created?

I have two models, User, and Product. Product belongs to User, User has many Products.
When a Product is created I also want to update multiple fields in the User model. I've been developing with Ruby for like 2 years now and still don't understand forms fully when it comes to this stuff. I'm still getting permitted: false. Now I know that for instance if I was creating a user while also creating a product I would just do #product.user.build but in this case I just want to update an already existing record.
I also realize that I probably can't call f.fields_for :user as #product doesn't know about user yet. In my head I believe I should be able to just pass additional params to the form, grab the current_user in the product#create action and then update the attributes manually by calling update_attributes on user.
product.rb
accepts_nested_attributes_for :user
product controller
def new
#product = Product.new
end
params.require(:product).permit(:product_name, user_attributes: [:phone_number, :email_address])
product view
form_for #product do |f|
f.fields_for :user do |c|
c.text_field :phone_number
c.text_field :email_address
f.text_field :product_name
end
I also realize that I probably can't call f.fields_for :user as #product doesn't know about user yet.
You can assign attributes to #product without saving it.
def new
#product = Product.new(
user: current_user
)
end
Now #product.user works.

How to execute this HABTM console association in the view

I have a User model, and two models that inherit from that: Teacher and Student. They also have their own controllers that inherit from the User controller.
I also have a Group model.
group belongs to teacher, teacher has many groups. group has and belongs to many students. student has and belongs to many groups.
There is a join table for the HABTM relationships, called 'groups_students'.
I have managed to create a form element that allows me to set the 'type' of User to 'Student' or 'Teacher'.
The difficulty I have now is assigning students to groups in the view. I can do this in the console no problem, as outlined here: Rails 4 HABTM how to set multiple ids in console?
I'm stumped as to how to do this in a view. I want to do it in the Student's show view.
Can someone at least give me some guidance? I have a pretty good book on Rails, but I need to know roughly what I have to do.
In the end I actually paid someone to solve this for me, because I couldn't find any free help for this on the internet. Thankfully, it wasn't too easy for him to solve, but solve it he did:
views/users/show.html.erb:
<%= form_tag assign_to_group_path do %>
<%= hidden_field_tag :user_id, #user.id %>
<%= select_tag :group_id, options_from_collection_for_select(Group.all, "id", "title") %>
<%= submit_tag "Assign to Class" %>
<% end %>
users_controller.rb:
private
def user_params
params[:user].permit(:type) if params[:user]
end
students_controller.rb (inherits from users_controller.rb):
def assign_to_group
#user = User.find(params[:user_id])
#group = Group.find(params[:group_id])
#user.groups << #group unless #user.groups.include? #group
#user.save!
redirect_to user_path #user
end
routes.rb:
post 'assign_to_group' => 'students#assign_to_group'

Rails single table inheritance. Shared controller, howto Update (CRUD)

I've got three classes Admin, Client, Agent which all inherit from User < ActiveRecord::Base. The system is designed using STI, hence all classes share the same users table.
I am interested in keeping the CRUD functionality pretty much the same, hence for now I am using a single UsersController.
When implementing the Update functionality I'm faced with a doubt.
Here's my edit form:
#Edit Form
<%= form_for(#user,{url:user_path(#user),method: :put}) do |f| %>
<%= render 'edit_fields',f:f %>
<%= f.submit "Save", class: "btn btn-large btn-primary"%>
<% end %>
#UsersController
def edit
#user=User.find(params[:id])
end
def update
binding.pry
#if #user.update_attributes(params[:user]) #<---BEFORE
#WORKAROUND BELOW
if #user.update_attributes(params[#user.type.downcase.to_sym])
flash[:success]="User was updated successfully."
redirect_to user_path(#user)
else
flash[:danger]="User could not be updated."
render 'new'
end
end
My "problem" is that params is dependent on the #user.type of the #user instance. Therefore sometimes there's a params[:client], other times a params[:admin] or params[:agent].
Hence the line
if #user.update_attributes(params[:user]) does not always work.
The workaround I implemented works fine, but I was wondering whether there's a more DRY or elegant way to approach these issues, i.e. sharing CRUD between different STI-ed classes.
There is indeed a much more elegant solution. Just change your form_for declaration and add the as option, like this:
<%= form_for(#user, as: :user, url: user_path(#user), method: :put) do |f| %>
That way in your controller your parameters will be scoped under the user key instead of the model's class.
In your controller, check for the User type in a before_filter as below. I've used this for similar STI Controllers and works great for me.
before_filter :get_user_type
private
def get_user_type
#klass = params[:type].blank? ? User : params[:type].constantize
end
And then for example you could call a show method as :
def show
#user = #klass.find params[:id]
#render
end
Using #klass across your CRUD actions should simplify your Controller.

Rails has_one build_association deletes record before save

So this has been asked previously, but with no satisfying answers.
Consider two models, User, and Subscription associated as such:
class User < ActiveRecord::Base
has_one :subscription, dependent: :destroy
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
Inside of SubscriptionsController, I have a new action that looks like this
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
Given that a subscription already exists for a user record, I'm faced with the following problem:
user.build_subscription is destructive, meaning that simply visiting the new action actually destroys the association, thereby losing the current subscription record.
Now, I could simply check for the subscription's existence and redirect like this:
def new
user = User.find(params[:user_id])
if user.subscription.present?
redirect_to root_path
else
#subscription = user.build_subscription
end
end
But that doesn't seem all that elegant.
Here's my question
Shouldn't just building a tentative record for an association not be destructive?
Doesn't that violate RESTful routing, since new is accessed with a GET request, which should not modify the record?
Or perhaps I'm doing something wrong. Should I be building the record differently? Maybe via Subscription.new(user_id: user.id)? Doesn't seem to make much sense.
Would much appreciate an explanation as to why this is implemented this way and how you'd go about dealing with this.
Thanks!
It depends on what you want to do
Thoughts
From what you've posted, it seems the RESTful structure is still valid for you. You're calling the new action on the subscriptions controller, which, by definition, means you're making a new subscription (not loading a current subscription)?
You have to remember that Rails is basically just a group of Ruby classes, with instance methods. This means that you don't need to keep entirely to the RESTful structure if it doesn't suit
I think your issue is how you're handling the request / action:
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
#subscription is building a new ActiveRecord object, but doesn't need to be that way. You presumably want to change the subscription (if they have one), or create an association if they don't
Logic
Perhaps you could include some logic in an instance method:
#app/models/user.rb
Class User < ActiveRecord::Base
def build
if subscription
subscription
else
build_subscription
end
end
end
#app/controllers/subscriptions_controller.rb
def new
user = User.find(params[:user_id])
#subscription = user.build
end
This will give you a populated ActiveRecord, either with data from the subscription, or the new ActiveRecord object.
View
In the view, you can then use a select box like this:
#app/views/subscriptions/new.html.erb
<%= form_for #subscription do |f| %>
<%= "User #{params[:user_id]}'s subscription: %>
<%= f.collection_select :subscription_id, Subscription.all,:id , :name %>
<% end %>
They are my thoughts, but I think you want to do something else with your code. If you give me some comments on this answer, we can fix it accordingly!
I also always thought, that a user.build_foobar would only be written to the db, if afterwards a user.save is called. One question: After calling user.build_subscription, is the old subscription still in the database?
What is the output user.persisted? and user.subscription.persisted?, after calling user.build_subscription?
Your method to check if a subscription is present, is IMHO absolutely ok and valid.
I came across this today and agree that deleting something from the db when you call build is a very unexpected outcome (caused us to have bad data). As you suggested, you can work around if very easily by simply doing Subscription.new(user: user). I personally don't think that is much less readable then user.build_subscription.
As of 2018 Richard Peck's solution worked for me:
#app/models/user.rb
Class User < ActiveRecord::Base
def build_a_subscription
if subscription
subscription
else
build_subscription
end
end
end
My issue was that a user controller didn't have a new method, because users came from an api or from a seed file.
So mine looked like:
#app/controllers/subscriptions_controller.rb
def update
#user = User.find(params[:id])
#user.build_a_subscription
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user), notice: 'User was successfully updated.'
else
render :edit
end
end
And I was finally able to have the correct singular version of subscriptions in my fields_for, so :subscription verses :subscriptions
#app/views
<%= f.fields_for :subscription do |sub| %>
<%= render 'subscription', f: sub %>
<% end %>
Before I could only get the fields_for to show in the view if I made subscriptions plural. And then it wouldn't save.
But now, everything works.

Rails 3: Single Model backing multiple Controllers

Ok, I have search Google, API's as well as StackOverflow and have found no real decisive help for my issue. So here goes!
I have a Polymorphic model setup named Favorite and it ties to the User. Being that Favorite is Polymorphic I of course can use the relationship to allow my user to add pretty much any entity in my application as their Favorite.
Each of these Favorite relationships between the user and a specific model I want to be able to call different things such as 'Favorite' or 'Like' or 'Friends'. This allows me to have a different Controller with Views to manage each of these different relationships so they are more understandable to the user and myself. Hence I am covering the global generic idea of Favorites with a more precise idea of a 'Friend'.
So I went ahead and created a Friend controller with its associated views to handle the Favorite relationship between a user and other user's in the system.
But what I have found is that Rails expects me to pass a 'Friend' model in all of my interactions between views and controller even though I want to use the Favorite model and I get 'uninitialized constant Friend' as an error in my view. How do I get past this 'convention', how do I make the controller and views if necessary understand that I am using the Favorite model as my underlying model not the Friend?
I considered creating a new model named 'Friend' and inheriting it from 'Favorite' just to fool the controller, but man that just seems like a waste of energy to me. Any ideas out there?
CODE EXAMPLE this is using the Favorite polymorphic model to ButtSlap another User. Each form partial is pass the User as a local variable called local_entity.
ButtSlapController
class ButtSlapsController < AuthorizedResourceController
def create
#favorite = current_user.favorites.build(params[:favorite])
respond_to do |format|
if #favorite.save
flash[:success] = 'butt slap successful!'
format.html { redirect_to('/lounge') }
# format.js { render :action => "create_success"}
else
flash[:success] = 'ah poop!'
format.html { redirect_to('/lounge') }
# format.js { render :action => "create_failure"}
end
end
end
def destroy
#favorite = current_user.favorites.find(params[:id])
respond_to do |format|
if #favorite.destroy
flash[:success] = 'butt slap has been successfully removed.'
format.html { redirect_to('/lounge') }
# format.js { render :action => "create_success"}
else
flash[:success] = 'ah poop!'
format.html { redirect_to('/lounge') }
# format.js { render :action => "create_failure"}
end
end
end
end
Creates The ButtSlap
<%= form_for current_user.favorites.build, :as => :favorite, :url => butt_slaps_path do |f| %>
<div><%= f.hidden_field :favorable_id, :value => local_entity.id %></div>
<div><%= f.hidden_field :favorable_type, :value => local_entity.class.to_s %></div>
<div class="actions"><%= f.submit "butt slap!" %></div>
<% end %>
Removes the ButtSlap
<%= form_for current_user.get_favorites(
{:id => local_entity.id,
:type => local_entity.class.to_s}),
:html => { :method => :delete }, :url => butt_slaps_path do |f| %>
<div class="actions"><%= f.submit "take back" %></div>
<% end %>
Well it all turned out to be a little gem called CanCan v1.6.4
I have been using CanCan for Authorization within my application and when declaring your authorization rules in your Ability class you can either do it by Model or by Controller or a mixture.
In order to handle this I setup 2 root Controllers which inherited from ApplicationController. The first 'AuthorizedController' is used for all controllers which do not use a Model and the second 'AuthorizedResourceController' is used for all controllers which are backed by a Model.
Turns out that for my ButtSlap controller I had it setup as an AuthorizedResourceController and by doing so CanCan was automatically looking to pull and authorize either a collection or a single model based off of the controller's name 'ButtSlap'. But due to the fact that I was using the Favorite model on the backend every time I tried to post to the controller CanCan tried to load its imaginary model based off of its convention. And I thus received the errors messages 'Uninitialized Constant 'ModelName''.
Once I switched the ButtSlapController from an AuthorizedResourceController over to a AuthorizedController CanCan no longer looked to instantiate and authorize a model based off the controller name and it moved to controller based authorization instead and just like everyone was saying 'Poof' my confusion as to why Rails was looking for a Model tied to a controller name was gone.
You really have to love bugs like this, they really stretch your limits as well as your keyboard stockpile (I tend to throw keyboards when I get frustrated ;)

Resources