Complex sorting in Rails - ruby-on-rails

In my application there is a Membership model with attribute username_or_email, which is used to invite users to system. When user accepts invitation, the Membership object is updated with a relation to new User object, who has a name attribute. Now I would like to sort every Membership objects in a single view, and show it like this:
If there is already user registered, show name attribute from joined User object.
If there is only invited user, show username_or_email attribute.
How can I easily sort it alphabetically by two columns?

Here is how you might do this (let's assume using PostgreSQL)
A scope on Membership that handles the query
# class Membership
def self.ordered_by_formatted_name
joins("LEFT JOIN users ON users.id = memberships.user_id").
order("COALESCE(users.name, memberships.username_or_email)")
end
An instance method on Membership for the display logic
# class Membership
def formatted_name
user ? user.name : username_or_email
end
Example usage
#memberships = Membership.ordered_by_formatted_name.preload(:user)
<% #memberships.each do |m| %>
<%= m.formatted_name %>
<% end %>

Related

How to correctly pass through foreign keys in Rails form_for

I currently have a working form to create a resource (An event booking) which belongs_to two other models, a Consumer (the customer) and a Course. In the Booking creation form, I'm using two hidden fields which pass through consumer_id and course_id.
For this to work in form_for, I've created two virtual attributes in my Booking model
attr_accessor :course_id, :consumer_id
And in the create event of BookingsController, I've grabbed those ID's from mass assignment and then manually assigned the actual Course and Consumer objects from the ID
bookings_controller.rb
def create
#booking = Booking.new(booking_params)
#booking.course = Course.find(#booking.course_id)
#booking.consumer = Consumer.find(#booking.consumer_id)
if #booking.save_with_payment
# Payment was successful, redirect to users account page to view it and past bookings
else
render :new
end
end
private
def booking_params
params.require(:booking).permit(:course_id, :consumer_id, :card_token, :visible, :created_at)
end
Is this best practice? I tried to name the form hidden fields as consumer and course, hoping that Rails would see that the value is an ID and automatically do a .find for me, but that doesn't appear to be the case. I'll be surprised if Rails can't take care of this automatically, I'm just not sure how to accomplish it.
It's simpler than you imagine and you're already most of the way there.
When you create a booking, you need only to set the course_id and consumer_id fields, so make sure you've got hidden fields set up in your form with these names and the right values:
<%= f.hidden_field :course_id, value: my_course_id %>
<%= f.hidden_field :consumer_id, value: my_consumer_id %>
Don't set course or consumer in your controller or in your form. That is, remove the following lines from your controller:
#booking.course = Course.find(#booking.course_id)
#booking.consumer = Consumer.find(#booking.consumer_id)
You already have course_id and consumer_id in your permit list, so when you post the form, the values for those parameters will be set on your new booking, which is all that you should care about.
When you attempt to access #booking.course, ActiveRecord will do a find for you based on the id set in course_id; this is handled by the belongs_to association that you've established in your model.

How to add new object of model in a foreign controller

I am rather new to Rails.
I am working on a profile-page for a User. The show view is divided into partials. The thing about my modelling structure is, that a User can have a Skill. But the User model itself does not have a Skill related column, so all Skill entries are saved in the Skill model.
So, my question is, how can I include a partial into the User show view, that contains a simple add Skill form (name needed only), which saves that Skill into its own table and adds it to the collection of the current User.skills?
in User#show create a #skill = Skill.new which you can use in your views.
then in you partial you can do
<%= form_for(#skill), ... %>
see more at http://guides.rubyonrails.org/form_helpers.html
I dont understand your problem. You could define a form in the partial and in the controller's action you would have to simply save the Skill and associate the user's id to it. I hope you know that the the Skill model must belong_to User and there should be a user_id column in the skills table to create the association. Even though I am writing a sample code for creating association below.
skill = Skill.new
user.skill << skill #Add association for user and skill
#Add skill related info from params into the skill model object
skill.save!
If you have the partial o the form at skill/_form.html.erb you can do:
<%= render "skills/form", skill: #skill %>
on the view, and you must add
#skill = Skill.new
on the show action of your Profile controller. To save that skill, you must do that on your update action on the Profile controller:
#skill = Skill.create(params[:skill])
#user.skills < #skill
#user.save

Attaching default params with a form

In order for a user to use my search form, they type a user's name, press submit, then rails brings up that user's homepage. I am still a beginner, and the "homepage" the user arrives is a response to the GET method pointed at the URL http://localhost:3000/center/show_user?utf8=%E2%9C%93&name=test&commit=Search when I type the user name "test" in the search box.
In the controller action center#show_user, I have set #user = User.find_by(name: params[:name])
and in the view, it displays well with <%= #user.name %>.
I would like to make a form on the center#show_user page for creating a new "item". An item is defined at belonging to the user in the scheme and a user is defined as owning many items (a user has an item_id column and an item has a user_id column).
When this form is submitted, how do I include with it the current user_id, inherited from the params in the url on the page hosting the form? For example, if I go to the page of the user named "sample_user", I want to be able to submit a "new item" form and have the user_id automatically included along with that form.
'Central' controller code for this page is
def show_user
#user = User.find_by(name: params[:name])
end
Just add
<%= hidden_field_tag "user_id", current_user.id %>
to form.
You should create the "item" through the relation with the user. That means:
Given this in your user model:
class User < ActiveRecord::Base
has_many :items
end
Do the following in the controller:
def create
#user = User.find_by(name: params[:name])
#user.items.create(params[:item])
# redirect_to or something different...
end
This will automatically build the relation for you (basically filling in the user_id field for you). The reason for doing it this way is that users can't mess with your form and fill in other user ids in the hidden field.

Trouble on saving an associated model and then retrieve some data from that

I am using Ruby on Rails 3 and I am trying to retrieve some data from a just saved child model (associated model) in order to store that data in the parent model.
More precisely (in steps) I would like to do:
Save the child model Account of the parent model User
Retrieve the just created Account ID value and save that value in the User model attribute named users_account_id.
... and more explicitly (in values) I would like to have the following scenario after saving the child model Account:
# Account values
Account.id = 222
Account.name = "Test_name"
...
Account.user_id = 111
# User values
User.id = 111
User.users_account_id = 222
I already implemented the first step, but how can I implement the second step?
In order to retrieve the Account ID, I tryed to use an association callback
class User < ActiveRecord::Base
has_one :account, :before_add => :callback_name
validates_associated :account
accepts_nested_attributes_for :account
def callback_name
self.users_account_id = Account.find_by_id(self.id).id
end
end
but I get this error:
Unknown key(s): before_add
This is way overkill. All you need to do is put the user_id in the form of the account that is getting created as a hidden field.
<% form_for(#account) do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<% end %>
Of course add your other fields that you want for account and you need a current_user object which you will need anyways with your current logic.
I'm going to side step your question a bit, and ask why you need IDs pointing in both directions? I assume you want your User to be related to an Account, and an Account to have one or more Users. The "Rails Way" to do this would be something like the following:
class User < ActiveRecord::Base
belongs_to :account
end
class Account < ActiveRecord::Base
has_many :users
end
In your database the users table will have account_id and the accounts table will not have a user_id of any kind.
This will still allow you to user the association in both directions:
some_user.account # Returns the correct account object
some_account.users # Returns all users for the account
I hope this helps somewhat!

nested form & habtm

I am trying to save to a join table in a habtm relationship, but I am having problems.
From my view, I pass in a group id with:
<%= link_to "Create New User", new_user_url(:group => 1) %>
# User model (user.rb)
class User < ActiveRecord::Base
has_and_belongs_to_many :user_groups
accepts_nested_attributes_for :user_groups
end
# UserGroups model (user_groups.rb)
class UserGroup < ActiveRecord::Base
has_and_belongs_to_many :users
end
# users_controller.rb
def new
#user = User.new(:user_group_ids => params[:group])
end
in the new user view, i have access to the User.user_groups object, however when i submit the form, not only does it not save into my join table (user_groups_users), but the object is no longer there. all the other objects & attributes of my User object are persistent except for the user group.
i just started learning rails, so maybe i am missing something conceptually here, but i have been really struggling with this.
Instead of using accepts_nested_attributes_for, have you considered just adding the user to the group in your controller? That way you don't need to pass user_group_id back and forth.
In users_controller.rb:
def create
#user = User.new params[:user]
#user.user_groups << UserGroup.find(group_id_you_wanted)
end
This way you'll also stop people from doctoring the form and adding themselves to whichever group they wanted.
What does your create method look like in users_controller.rb?
If you're using the fields_for construct in your view, for example:
<% user_form.fields_for :user_groups do |user_groups_form| %>
You should be able to just pass the params[:user] (or whatever it is) to User.new() and it will handle the nested attributes.
Expanding on #jimworm 's answer:
groups_hash = params[:user].delete(:groups_attributes)
group_ids = groups_hash.values.select{|h|h["_destroy"]=="false"}.collect{|h|h["group_id"]}
That way, you've yanked the hash out of the params hash and collected the ids only. Now you can save the user separately, like:
#user.update_attributes(params[:user])
and add/remove his group ids separately in one line:
# The next line will add or remove items associated with those IDs as needed
# (part of the habtm parcel)
#user.group_ids = group_ids

Resources