First come caveats. I'm a total RoR n00b but i have experience with programming so i get the basic's. I've got an application i'm building which i need to build a complex search engine for. The basic layout is Guides >> Profiles >> Mentorings >> MentorAreas. Below is the code for each of the models and then the code i'm trying to build. My issue is i can't seem to figure out the proper object name to get the search engine to search mentor_areas.
System Setup:
rails -v :: Rails 3.1.1
ruby -v :: ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.7.0]
RanSack :: ransack (0.5.8) from git://github.com/ernie/ransack.git (at master)
Guide:
class Guide < User
end
User: (what's relevant)
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Virtual attribute for authenticating by either username or email
# This is in addition to a real persisted field like 'username'
attr_accessor :login
# Setup accessible (or protected) attributes for your model
attr_accessible :login, :username, :email, :password, :password_confirmation, :remember_me, :sex,
:location, :role, :first_name, :last_name, :home_town, :profile_attributes
has_one :profile, :dependent => :destroy
accepts_nested_attributes_for :profile, :allow_destroy => true
has_and_belongs_to_many :roles
end
Profile
class Profile < ActiveRecord::Base
belongs_to :user
has_many :mentor_areas, :through => :mentorings
has_many :mentorings
accepts_nested_attributes_for :mentor_areas,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }, :allow_destroy => true
validates_uniqueness_of :user_id
end
Mentoring
class Mentoring < ActiveRecord::Base
belongs_to :mentor_area
belongs_to :profile
validates_uniqueness_of :profile_id, :scope => :mentor_area_id
end
MentorArea
class MentorArea < ActiveRecord::Base
has_many :profiles, :through => :mentorings
has_many :mentorings
validates_uniqueness_of :mentor_area
end
In my Guides Controller i have:
#search_guides = Guide.joins(:roles).where("sex = :sex AND roles.name = :role",{:sex => current_user.sex, :role => 'guide'}).search(params[:search])
#guides_found = #search_guides.all
and in my view (index.html.erb) i have the following:
<%= form_for #search_guides do |f| %>
<%= f.label :username_cont %>
<%= f.text_field :username_cont %><br />
<%= f.label :guides_profiles_mentor_areas_mentor_area_cont %>
<%= f.text_field :guides_profiles_mentor_areas_mentor_area_cont %><br />
<%= f.submit %>
<% end %>
I can't seem to figure out what the correct name should be for the second field so that it will search against the mentor_areas that a person has associated with there profile.
thanks in advance!
Updated for RanSack Code
If I'm reading your code correctly, you want:
:profile_mentor_areas_mentor_area_cont
I think it's:
:profiles_mentorings_mentor_area_mentor_area_contains
Basically, it's you have to think about which tables you have to reach through one by one in sequence to get to your search field. I think in your case, your view for guides needs to go through : profiles -> mentoring -> mentor_area in order to search for the mentor area? Your mentor_area only have profiles :through the mentoring model. You can't go directly to the mentor_are without going through the mentoring model first.
Also,
<%= f.label :guides_profiles_mentor_areas_mentor_area_contains %>
looks really cumbersome in the view. You can just say this instead:
<%= f.label "Mentor Area" %>
Hope that works.
Related
I'm working on my ligthweight Padrino CMS which very much resembles the functionalities of Wordpress. When creating a new post I want to be able to assign them to many of the existing categories. Somehow I can not get my form work.
My models look like that:
Post model
class Post < ActiveRecord::Base
belongs_to :account
has_many :categorizations
has_many :categories, :through => :categorizations
accepts_nested_attributes_for :categories
end
Category model
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, :through => :categorizations
belongs_to :category
end
Categorization model
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
end
I have also created a migration for the joint table
class CreateCategorizations < ActiveRecord::Migration
def self.up
create_table :categorizations do |t|
t.integer :category_id
t.integer :post_id
t.timestamps
end
end
def self.down
drop_table :categorizations
end
end
And here is the related part of the form
<% fields_for :categories do |c| %>
<fieldset class='control-group <%= error ? 'has-error' : ''%>'>
<%= c.label 'Category title', :class => 'control-label' %>
<div class='controls'>
<%= c.select(:id, :collection => #categories, :fields => [:title, :id], :include_blank => true, :multiple => true, :class => 'form-control input-xlarge input-with-feedback') %>
<span class='help-inline'><%= error ? c.error_message_on(:id) : "Select a category if there is a parent category" %></span>
</div>
</fieldset>
<% end %>
I don't know what I'm missing but the association is not created. I do not mention categories in the controller during the creation but I do fill up the dropdown with the existing Categories. Somehow I would like to associate them to the new post.
I will greatly appreciate if someone can point me to the right direction with this. The error I get is this:
NoMethodError at /admin/posts/create
undefined method `each' for nil:NilClass
file: collection_association.rb location: replace line: 383
The forms POST data contains that:
POST
Variable authenticity_token
Value "c760c21a5d1f85bfc19e179b37d56f67"
category_active_record_relation {"id"=>["2", "3"]}
post
{"post_name"=>"Test post", "post_type"=>"blogpost", "post_title"=>"Postie", "slug"=>"This is a custom set slug", "post_date"=>"2015-06-30", "post_content"=>"Lorem ipsum dolor sit amet consequtiv", "post_excerpt"=>"Lorem ipsum", "post_status"=>"published", "comment_status"=>"closed", "comment_count"=>"0"}
save_and_continue "Save and continue"
I have managed to answer my own question, the solution was fairly easy, however maybe there is a nicer one, with more magic. Anyway using the CollectionProxy API documentation it became clear that I can assign these categories in the controller.
admin/controllers/posts.rb
Just include before the if #post.save
params[:category_active_record_relation]['id'].each do |category|
category = Category.find(category)
#post.categories << category
end
If I would create new categories than I could use the #post.categories.build(category) method.
Hope it will help others as well.
This could be the wrong way to go about this entirely, and I'm very open to alternatives.
I've got the following models, where Users can have many Positions:
class User < ApplicationRecord
has_many :user_positions
has_many :positions, through: :user_positions
accepts_nested_attributes_for :user_positions,
reject_if: :all_blank
end
class UserPosition < ApplicationRecord
belongs_to :user
belongs_to :position
end
class Position < ApplicationRecord
end
On my edit user form, I'd like to allow a User's current position to be updated. I do that in the following way:
<%= form_for #user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :user_positions, #user.user_positions.order(created_at: :desc).first do |ff| %>
<%= ff.hidden_field :user_id, value: #user.id %>
<%= ff.collection_select :position_id, Position.all, :id, :label %>
<% end %>
<%= f.submit "Update User" %>
<% end %>
The issue I'm running into is that a new instance of UserPosition is being created every time I submit the form, even if the Position that's selected hasn't changed. This leads to a bunch of duplicate entries in the join table, when I really only care about "promotions" or "demotions" when the value of position_id has changed.
I don't want to add a custom validator to disallow the creation, because I still want the form to be able to submit with an unchanged position. An example of this is when I only want to change the User's name.
Any advice on how to deal with this use case?
It turns out that you can actually use any method as a Symbol argument the to accepts_nested_attributes_for reject_if option.
I updated my User model as follows, and now the behavior is exactly what I want:
class User < ApplicationRecord
has_many :user_positions
has_many :positions, through: :user_positions
accepts_nested_attributes_for :user_positions,
reject_if: :same_as_previous_position
def same_as_previous_position(attributes)
if self.user_positions.empty?
return false
end
Position.find(attributes[:position_id]) == self.user_positions.order(created_at: :desc).first.position
end
end
I am building a Rails 4.2.4 app where I have Units and Medics. When I edit each unit I have two spots for the medics, incharge and attendant. I want some way to validate that both the incharge_id and attendant_id are not the same. That way I can't assign myself as both positions on the unit.
Here is what my model and form view looks like.
unit.rb
class Unit < ActiveRecord::Base
belongs_to :attendant, :foreign_key => :attendant_id, :class_name => 'Medic'
belongs_to :incharge, :foreign_key => :incharge_id, :class_name => 'Medic'
belongs_to :unit_status
end
medic.rb
class Medic < ActiveRecord::Base
has_many :units
end
units/_form.html.erb
<%= form_for(#unit) do |f| %>
<%= f.label 'Attendant'%>
<%= f.collection_select(:attendant_id, Medic.order('name ASC'), :id, :name, {}) %>
<%= f.label 'In Charge'%>
<%= f.collection_select(:incharge_id, Medic.order('name ASC'), :id, :name, {}) %>
<%= f.label 'Unit Status'%>
<%= f.collection_select(:unit_status_id, UnitStatus.order("status ASC"), :id, :status, {})%>
<%= f.submit "Update" %>
<% end %>
So in summary if I edit a unit and I accidentally assign the id of "1" to the unit, I want to error out and give some sort of message, "Cannot assign the same medic to both positions". Something like that.
The only thing I can think of, is to somehow filter the params in the controller saying if the params of attendant_id and incharge_id are are == then redirect to the edit_unit_path and display a flash message, "You cannot assign the same medic to both positions".
It seems like it would be better to do validations on the model side instead of stuffing logic in the controller, but I'm not sure how to simultaneous validate the two different columns for uniqueness.
I came up with this on the Unit model.
validate :attendant_and_incharge
def attendant_and_incharge
errors.add(:attendant_id, "can't be the same as the incharge") if attendant_id == incharge_id
end
This will not allow me to save the same id to the unit model for attendant_id and incharge_id. It fails silently and directs to the units_path. Just need to put some conditional in the controller to redirect to the edit path on failure. (ThumbsUp)
Here are the three files associated with the original problem, I think...please excuse my ignorance...the rails generator does a lot of work for you, so I don't understand all the connections yet.
class Post < ActiveRecord::Base
has_many :comments
belongs_to :user
default_scope { order('created_at DESC') }
end
class AddUserToPosts < ActiveRecord::Migration
def change
add_column :posts, :user_id, :integer, :name
add_index :posts, :user_id
end
end
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.text :body
t.text :name #added this
t.timestamps
end
end
end
where the error is occuring:
<%= link_to post.title, post %>
</h4>
<small>
>> submitted
<%= time_ago_in_words(post.created_at) %> ago by
<%= post.user.name %> <br/>
<!-- <%= post.comments.count %> Comments -->
</small>
Ruby is basically telling you that on line 11 (<%= post.user.name %>) the user association of your post object is nil (i.e. a non-existent value, which in Ruby is a singleton instance of class NilClass).
Thus you're trying to call the #namemethod on nil, that obviously has no such method, hence the NoMethodError.
Just add a check to make sure that user exists before printing its name <%= post.user.name unless post.user.nil? %> or make sure that there is always a user associated with your posts (e.g. through a validation), whatever fits best in your scenario.
Currently, I am able to create multiple Students through my Adult Form by using accepts_nested_attributes_for :student.
But how can I Edit an existing Adult through a Student Form? (So the Opposite, except Edit)
Currently I am able to create more Parents through my Student form, but thats not what I want.
MODELS
class Adult < ActiveRecord::Base
has_many :students
accepts_nested_attributes_for :student
end
class Student < ActiveRecord::Base
belongs_to :adult
accepts_nested_attributes_for :adult
end
CONTROLLER
class StudentsController < ApplicationController
def update
#adult = Adult.find(params[:adult_id])
#student = Student.find(params[:id])
if #student.update_attributes(student_params)
redirect_to path
end
end
private
def student_params
params.require(:student).permit(:adult_id, :school_id, :username, :password,
:firstName, :middleName, :lastName, :alias,
adult_attributes: [:id, :name])
end
end
VIEWS
###HOW CAN I UPDATE THE EXISTING PARENT AS OPPOSE TO CREATING ONE
<%= simple_form_for #student.build_adult do |f| %>
<h4>Update Parent Information</h4>
<%= f.label :firstName, 'First Name' %>
<%= f.text_field :firstName, placeholder: "Parent's First Name", :class => "form-control"%>
<%= f.submit "Save & Continue", class: "btn btn-primary"%>
<% end %>
ROUTES
resources :adult do
resources :student
end
Firstly,it is has_many :students,so it should be accepts_nested_attributes_for :students not accepts_nested_attributes_for :student.
Secondly,if i understood your question correctly,you are trying to update the existing parent(adult) record but you are ending up creating a new one.Is that the problem? If so,you need to permit the :id of the student for which the parent(adult) record need to be updated.
Change your student_params method to like this
def student_params
params.require(:student).permit(:id,:adult_id, :school_id, :username, :password,
:firstName, :middleName, :lastName,:alias,adult_attributes: [:id, :name])
end
I'm not sure about this solution, but it may work...
Try using #parent.update_attributes instead of creating a separate parent form, send the parent params with the student params. You may need to define a parent_params() function to permit them.
** I believe the permit params will filter out the non-permitted ones, so you won't need to specify only the student or parents fields for their respect update_attributes.