Ransackable Attributes for has many relationship columns - ruby-on-rails

I want to display has_many relationship columns in ransackable attributes list. So that I can display them in the dropdown.
I have a member model
class Member < ActiveRecord::Base
has_many :memberships
def self.ransackable_attributes(auth_object = nil)
if auth_object == 'admin'
super
else
super & ['first_name', 'last_name', 'license_number', 'memberships_membership_number_cont']
end
end
And membership model has some columns like membership_number which is unique and a string. Now in the dropdown of members listing page I want to provide membership_number, so that user can select membership_number from the dropdown and enter a value to search the respective member.
Any suggestions?
The dropdown I am taking about is:
PS: In the screenshot you may be looking for a dropdown for contains all/contain any ie options dropdown. I made is just one only contains any. Thats why its not visible.

You need to define the ransackable_attributes method in associated model for custom searchable attributes of that model. So your Membership model should be something like:
class Membership < ActiveRecord::Base
belongs_to :member
...
def self.ransackable_attributes(auth_object = nil)
['membership_number', ...]
end
end
And specify associations in ranssack form like:
<%= f.condition_fields do |c| %>
<%= c.attribute_fields do |a| %>
<%= a.attribute_select associations: [:memberships] %>
<% end %>
<% end %>

Related

Form to create relationship between objects

In my app I have User and Language models.
User can have multiple languages and a language can have multiple users.
class Language < ApplicationRecord
has_and_belongs_to_many :users
end
class User < ApplicationRecord
has_and_belongs_to_many :languages
end
I want to create a form that will allow user to add a new language to the profile.
Since both User and Language models already exist, I'm wondering how to create a form that will not create any new model, but just create a relation between existing models.
Both the has_many and HABTM macros create _ids and _ids= setters and getters that make it trivial to associate different records:
<%= form_with(model: #user) do |form| %>
<div class="field">
<%= form.label :language_ids, "Languages" %>
<%= form.collection_select(:language_ids, Language.all, :id, :name, multiple: true) %>
# or if you prefer checkboxes
<%= form.collection_checkboxes(:language_ids, Language.all, :id, :name) %>
</div>
# ...
<% end %>
The form collection helpers are smart enough to iterate accross the collection and will select/check depending on if an assocation already exists.
You whitelist an array parameter by passing a hash key to permit with an empty array as its value:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# ...
end
private
def user_params
params.require(:user)
.permit(
:foo, :bar, :baz,
langauge_ids: []
)
end
end
I would also seriously consider if you want to use has_and_belongs_to_many in the first place. Since there is no model you can't access any additional columns on the join table like for example how proficient a user is or if its their primary language. There is also no straight forward way to query the join table directly. has_many through: is a actually better solution in most cases.

Fetch id of selected option in dropdown in controller

I have a model named 'Assessment':
class Assessment < ApplicationRecord
has_many :assessment_students
has_many :students, through: :assessment_students
end
Join table is:
class AssessmentStudent < ApplicationRecord
belongs_to :student
belongs_to :assessment
end
There is another model:
class Classroom < ApplicationRecord
has_many :classroom_students
has_many :students, through: :classroom_students
has_many :assessments
end
In show,html.erb of classrooms, I have a dropdown which shows all assessments (generated from assessment table).
Code is:
<%= collection_select(:assessment :assessment_id, Assessment.all, :id, :assessment_name , :prompt => true) %>
Requirement of the project is: Based on the assessment chosen by the user in the show.html.erb page, we have to show all students details like name etc assigned to that particular assessment. I have stored this data in join table 'AssessmentStudent '. However, I am not sure how to pass id from the above collection_select to classroom controller. I have below code:
show.html.erb:
<%= collection_select(:assessment :assessment_id, Assessment.all, :id, :assessment_name , :prompt => true) %>
<div id="divResult">
<% #assessmentstudents1.each do |t| %>
<% t.assessment_students.each do |record| %>
<%= record.student_id %>
<% end %>
<% end %>
</div>
classroom controller:
def show
#assessmentstudents1 = Assessment.find(params[:assessment][:assessment_id]).preload(:assessment_students)
end
def classroom_params
params.require(:classroom).permit(:classroom_name, :classroom_year, :customer_id, :classroom_student, :student_ids => [])
params.require(:assessment).permit(:assessment_id)
end
I would first recommend to make a change to the controller structure of your application. Because the responsibility of the show action of your ClassroomsController should be to display the details of the classroom. When you want to show the details of an Assessment, that should be handled by an AssessmentsController.
First of all, I'm gonna assume that your Assessment model has a belongs_to :classroom association. Then I would suggest creating the following structure.
config/routes.rb
resources :classrooms
resources :assessments
app/views/classrooms/show.html.erb
<% #classroom.assessments.each do |assessment| %>
<%= link_to assessment_name, assessment_path(assessment) %>
<% end %>
app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def show
#assessment = Assessment.find(:id)
end
end
app/views/assessments/show.html.erb
<h1>Students for <%= #assessment.assessment_name %></h1>
<% #assessment.students.each do |student| %>
<p><%= student.student_name %></p>
<% end %>
<h2>In classroom:</h2>
<p><%= #assessment.classroom.classroom_name %></p>
So to explain what is happening here, we have configured the routes to allow the server to respond to the url /assessments/:id which will lead to the AssessmentsController#show action being called.
That action is being called when the user clicks on any of the links that we have setup in the classrooms/show template. Note that I used individual links for now instead of a select dropdown, because it is easier to setup and understand how it works. And like the previous answer suggested, using a select tag requires a little bit of JavaScript to get working.
And lastly, when the assessments/show template is being rendered, it will list out all of the students related to that particular assignment. And I also included which classroom it is assigned to (if my assumption of belongs_to was correct).
On a side note, if your question tag of ruby-on-rails-3 is correct, then the usage of params.permit and params.require is invalid, because that is something that was introduced in Rails 4 (unless I'm mistaken). And in any case, you only have to use permit when database updates take place, which means the create and update actions, not index, show, etc, because it is a way of restricting which changes are allowed.
Tag select does nothing by itself. So there are two possible options.
The first is wrapping select in form tag then submitting the form will lead to request. The second is writing a handler using JavaScript which will listen to select changes.

Random Generation of Items from Existing Model

I am fairly new to RoR and trying to get a basic app to work - I have a 'books' model and a 'genre' model. I wish to create a page that randomly generates books of different genre's for a user to select.
I have created a 'random_book' controller, but am unsure on how to proceed with the random selection and display.
Any help/pointers would be appreciated.
Edit:
Here's the work I've been doing in the random_book model:
" load 'user.rb'
class random_book < ActiveRecord::Base
belongs_to :book
belongs_to :genre
def get_random_book
find(:all).sample(5)
end
"
Thank you.
Based on discussion
4 models
Book
Genre
UserBook
User
They will look something like this:
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
class Genre < ActiveRecord::Base
has_many :books
def fetch_random_books(qty)
#you want to make sure you don't error out by requesting a sample of an empty list of books so check first. The qty argument lets you control the number of books for the search
unless self.books.empty?
self.books.limit(qty).sample
end
end
end
class Book < ActiveRecord::Base
belongs_to :genre
has_many :user_books
end
class UserBook < ActiveRecord::Base
belongs_to :book
end
I would most likely use a different route for the random book section, because it's not very url-friendly to say code-descriptive things like user_book.
There are 4 things to do
Create a new route to get a list of genre_ids that a user chooses.
Create an action that correlates to the route you created that renders a list of boos and adds those books to a users list
Create a form in a view (any view, like a sidebar in an existing books view, doesn't matter) this form will post the route and action you just made
Create a view to render the book list (the easy / DRY way is to add a few elements to the existing books index to let users know its a random generated list of books based on their genre pics)
Add the route
post "/random-books", to: "books#random_books", as: :random_books
Create the action in the books_controler
def random_books
if params[:genre_ids]
genres = Genre.where(id: params[:genre_ids])
#books = []
genres.each do |genre|
#books << genre.fetch_random_books(10)
end
else
#books = nil
end
respond_to do |format|
format.html { render action: :index }
end
end
Then create a form that makes a post request to the index action of the books_controller -- You can parse the form and update the UserBook model inside that action, and then display list of books all at the same time.
<%= form_tag(random_books_path, method: :post) do %>
<ul>
<% Genre.all.each do |genre| %>
<li>
<label class='genre-select'>
<%= check_box_tag 'genre_ids[]', genre.id -%>
<%= genre.name %>
</label>
</li>
<% end %>
</ul>
<%= submit_tag "Fetch Book List"%>
<% end %>
-- The last one I'm sure you can do, it returns a books object list so parse it however works best for you. In the controller action you can automatically add the ids for the books to the UserBook model by adding this inside the controller action:
#books.each{ |book| book.user_books.create(user: user)}

Setting An Attribute On a has_many_through Join Model

I have a has_many_through relationship between a Contributor and a Resource through a Contributorship. What makes it unusual is that when I set up the association I need to set a contribution_type for the :
model Contributor
has_many contributorships
has_many contributors, through: contributorships
end
model Resource
has_many contributorships
has_many resources, through: contributorships
end
model Contributorships
attr_accessible :contribution_type, :contributor, :archive_resource
belongs_to resources
belongs_to contributors
end
Setting up an association involves either:
c = Contributor.create!()
r = Resource.create!()
c.contributorships.create!(resource:r, contribution_type: :author)
Or (If I don't want to save upfront):
c = Contributor.new()
r = Resource.new()
cs = Contributorship.new(contributor:c, resource:r, contribution_type: :author)
c.save!
r.save!
cs.save!
If I didn't need to set the contribution_type atttribute on the Contributorship join Model I could do:
c.resources << r
So is there a more elgant way of doing this and setting the attribute at the same time?
You can use build to automatically set associations between instantiated objects.
contributor = Contributor.new
contributor.contributorships.build(:contribution_type => :author)
contributor.resources.build
contributor.save!
If the value of contributor_type is dependent on values from one of the other models I'd be inclined to just write a before_validation callback to set the correct corresponding value every time a Contributorships was created. i.e.
model Contributorships
attr_accessible :contribution_type, :contributor, :archive_resource
belongs_to resources
belongs_to contributors
before_validation :set_contributor_type
def set_contributor_type
if self.new_record?
# Whatever code you need to determine the type value
self.contributor_type = value
end
end
end
If instead this is a user defined value then you'll need to use the accepts_nested_attributes_forin which ever model defines your contributor_type and then in the form use fields_for to allow the user to set the value when creating the record. This is covered very comprehensively and in good detail in the following railscast: http://railscasts.com/episodes/197-nested-model-form-part-2?view=asciicast
I.e:
<%= form_for(contributor) do |f| %>
# Whatever fields you want to save for the contributor
<% contributorship = f.object.contributorships.build %>
<%= f.fields_for(:contributorships, contributorship) do |new_contributorship| %>
# Whatever fields you want to save for the contributorship
<% resource = new_contributorship.object.resources.build %>
<%= f.fields_for(:resources, resource) do |new_resource| %>
# Whatever fields you want to save for the resource
<% end %>
<% end %>
<% end %>

Ruby on Rails collection_select complexity

i have the following Problem, i Have the following in my customer bill view
<%= f.collection_select :product_id,Product.all,:id,:name %>
This is getting list of all the products from "Product" model and giving option to select from it. But i want to select the list of products from the "StoreOpeningStock" model.
I have these in my model
class Product< ActiveRecord::Base
has_many :store_opening_stocks
has_many :customer_bills
attr_accessible :name
end
class StoreOpeningStock < ActiveRecord::Base
attr_accessible :product_id
belongs_to :product
end
class CustomerBill < ActiveRecord::Base
attr_accessible :product_id
belongs_to :product
accepts_nested_attributes_for :store_opening_stock
end
Can anyone guide me how i can get product name and id from store_opening_stock??? Should i use Helpers??? or is there any other way?? Thanks in advance
I tried using helpers
def getting_prod_names
#sto = StoreOpeningStock.all
for x in #sto
[
['{x.product.title}', '{x.product_id}']
]
end
end
getting following output
<%= f.select :product_id, options_for_select(getting_prod_names) %>
ANy Help?? :)
When you create a form the data used to ccreate a collection_select isnt limited to the Class your going to create an object for. You could simply do the following:
<%= f.collection_select :product_id,StoreOpeningStock.all,:product_id ,:name %>
This should to it for you,...
add this to your StoreOpeningStock class:
def name
return self.product.name unless self.product.nil?
""
end
You need to clarify the relationship between your models...
But just to give you an idea. You can define the collection of products you want to display in your controller, inside the action related to the view (where you are displaying the collection).
Controller:
#products= #here you should call all products you want
Then, your collection of products can be displayed like:
<%= f.collection_select :product_id, #products,:id,:name %>
EDIT
You need to revise the relationship between your models. A product has many customer_bills, but are you sure that each customer_bill belongs to a single product?
I think you have a many-to-many relationship, as a customer_bill can also have many products.
If I understand it right, the solution is to create a ProductLine model between this many-to-many relationship.
Also, what is the difference between Product and StoreOpeningStock? What attributes have you included in the StoreOpeningStock?
If you have created this model only to show the availability of products, why don't you add an attribute in the Product model, for example a boolean column called availability.
So you want to find all products that have a StoreOpeningStock.
This is solely a model concern and have nothing to do with helpers.
class Product
# Find all products that have a StoreOpeningStock
def self.in_stock
find(StoreOpeningStock.product_ids)
end
end
class StoreOpeningStock
# Collect all product ids from stocks
def self.product_ids
uniq.pluck(:product_id)
end
end
Now you can use Product.in_stock instead of Product.all to have the only ones in stock.
I'd add a scope to your products model:
class Product< ActiveRecord::Base
has_many :store_opening_stocks
has_many :customer_bills
attr_accessible :name
scope :having_store_opening_stocks, :joins => : store_opening_stocks, :select => 'distinct product.*', :conditions => 'store_opening_stocks.product > 0'
end
Then you can use Product.all.having_store_opening_stocks to select only products with such stocks, for example:
<%= f.select :product_id, Product.having_store_opening_stocks.map { |product| [product.name, product.id] } %>

Resources