how to specify has_many through vs has_many - ruby-on-rails-4

So I'm trying to figure out how to specify between calling all instances of an object based on the has_many and has_many :through associations. I have users, groups and members. the member model is a pairing of groups and users. the groups also have a user_id in that model. so this will make more sense after you see the code:
user model
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :groups
has_many :groups, :through => :members
end
group model
class Group < ActiveRecord::Base
has_many :users, :through => :members
belongs_to :user
end
member model
class Member < ActiveRecord::Base
belongs_to :groups
belongs_to :users
end
Index View
<!--these are the groups I own (the group.user_id is my id) NEEDS EDITING-->
<% #groups.each do |group| %>
<%= link_to group.title, group_path(group) %>
<%= truncate group.desc %>
<% end %>
<!--I'm a member of these groups (connected through the member model) NEEDS EDITING'-->
<% current_user.groups.order(:created_at).each do |group| %>
<%= link_to group.title, group_path(group) %>
<%= truncate group.desc %>
<% end %>
I'm trying to figure out how to call the different types of associations users and groups in the Index View.
I want to get a list of all the groups that have me listed as admin (group.user_id) and then I want to get a separate list of all the groups that I'm a member of (through the member model).
I know I can query it with a find or where call. However, I was hoping there was a simple way like current_user.groups or something. anyway, thanks!

I would consider calling the 1:N relationship to groups another name. So you would end up with something like
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :owned_groups, class_name: 'Group'
has_many :groups, :through => :members
end
So the index view would look something like:
<% current_user.owned_groups.each do |group| %>
<%= link_to group.title, group_path(group) %>
<%= truncate group.desc %>
<% end %>
<% current_user.groups.order(:created_at).each do |group| %>
<%= link_to group.title, group_path(group) %>
<%= truncate group.desc %>
<% end %>

Related

Need Simple Has_Many :through controller code

I'm learning to use Rails has_many :through association. Most of what I'm reading provides only how to set up the models, but not how to set up the controller actions. My app is very basic for the purposes of learning this topic. I have a form that lists some vertical industries. When a "vertical" is created, there is an option to select from a list of apps that apply to that vertical (check boxes). When the vertical gets created, the associations between the vertical and the selected apps should be established.
I have 3 models:
class App < ActiveRecord::Base
has_many :solutions
has_many :verticals, through: :solutions
end
class Vertical < ActiveRecord::Base
has_many :solutions
has_many :apps, through: :solutions
end
class Solution < ActiveRecord::Base
belongs_to :app
belongs_to :vertical
end
Here is my form:
<%= simple_form_for(#vertical) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :name %>
<%= f.input :description %>
<%= f.association :apps, as: :check_boxes %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Here is my verticals_controller create action:
def create
#vertical = Vertical.new(vertical_params)
#solutions = #vertical.apps.build(params[:app])
<respond_to code omitted for brevity>
end
def vertical_params
params.require(:vertical).permit(:name, :description, apps_attributes: [ :name, :description, :developer, :mpp, :partner, :website, :app_id[] ])
end
I am able to create the associations from the rails console this way:
vertical = Vertical.first
app = App.first
vertical.apps << app
But I don't think this is the right way to do it in the controller, nor do I understand how to get the app params that were selected in the form. I'm looking for some basic, clean code examples to follow that adhere to Rails best practices. Also, if you can point me to any recent tutorials that address controller code would be great. Thx.
I was able to get the associations to be created by making the following changes:
In my Create action in my controller:
def create
#vertical = Vertical.new(vertical_params)
#solutions = #vertical.apps.build
<respond_to code omitted for brevity>
end
In my secure parameters I have the following:
def vertical_params
params.require(:vertical).permit(:name, :description, app_ids:[])
end
I am not certain this is done to Rails best practices or not.

How to raise error when validation on nested attributes fails

Here are 2 models: customer and address. A customer has_one address.
class Customer < ActiveRecord::Base
has_one :address
accepted_nested_attributes_for :address, :allow_destroy => true
end
class Address < ActiveRecord::Base
belongs_to :customer
validates :add_line, :presence => true
end
<% simple_form_for #customer do |f| %>
.....
<%=f.simple_fields_for :address do |builder| %>
<%=render ('address', f: builder) %>
<% end %>
<%end %>
address view
<%=f.input :add_line %>
address is nested attribute in customer. The problem we are having is that if address is modified wrongly (ex, a nil add_line) within customer view, there is no error (#customer.update_attributes in customer controller) popping up. Is there a way setting up the nested attributes in such a way nil add_line will fail the update?
Two things caught my eye with your original post:
One,remember that you need a belongs_to :customer in the Address model.
Two, you need to add a validation in the Customer model
class Customer < ActiveRecord::Base
has_one :address
accepted_nested_attributes_for :address, allow_destroy: true, reject_if: :address_invalid
private
def address_invalid(attributes)
# add custom validation code here ...
end
end

Rails: How to use find_or_create_by in my nested model

Models: I have a Company model that has_many Tasks. Each Task has_one Employee.
Task model:
class Task < ActiveRecord::Base
belongs_to :company
has_one :employee, dependent: :destroy
accepts_nested_attributes_for :employee
validates_associated :noteholder
end
Employee model:
class Employee < ActiveRecord::Base
belongs_to :task
validates :name, presence: true, uniqueness: { case_sensitive: false }
end
Form: I created a nested form for Task that accepts_nested_attributes_for the Employee model. The form has an Employee name field with an autocomplete function that loads all Company.employees.
_form.html.erb form:
<%= simple_form_for(#task) do |f| %>
<%= f.simple_fields_for :employee do |employee| %>
<%= employee.input :name, input_html: { data: { autocomplete_source: #employees.pluck(:name).to_json } } %>
<% end %>
<%= f.button :submit, 'Save' %>
<% end %>
This saves a new record for each employee, regardless of whether or not an employee with that name exists
Desired behaviour:
If the user fills in a new Employee the model should create a new record, whereas if it would provide an existing Employee name, it should not.
My attempt: I thought the find_or_create_by method would be useful here, but have had no success with implementing it.
Question: How do I correctly set up my model sothat the Employee model only saves a new employee if the employee name does not yet exist?

Rails4 - Association has_many through - Reader_id

I would like with Rails 4 to do a simple association beetween 3 models but i can't succeed to do that.
First model:Reader,Second model:Comments Third model : Books
The relation beetween the 3 models are:
class Book < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :book
belongs_to :reader
class Reader < ActiveRecord::Base
has_many :comments
has_many :books, through: :comments
I have no problem for the relation Book has_many :comments but i have some problem with the relation Reader has_many :comments.
Field from CommentsTable : username, message and reader_id (for the has_many association).
Field from ReaderTable : name , email.
I have a file _form.html.erb in view:
<%= form_for [#book, #comment] do |f| %>
<%= f.hidden_field :book_id %>
<%= f.text_field :reader_id, :placeholder => "reader id" %>
<br />
<%= f.text_area :message %>
<br />
<%= f.submit %>
<% end %>
And an other file _comment.html.erb:
<% if comment.reader_id != nil %>
<li><strong><%= link_to comment.reader.name, comment.reader %></strong></li>
<p><%= comment.message %></p>
<% end %>
Like you see i have a form with reader_id and message. When the user will enter this 2 informations, it will appears the reader.name (thanks to reader_id) and the comment.message.
But i don't want that the user enter the integer "reader_id" (it has no sense) but directly the string "reader.name".
How can i do that, knowing that after i will do a link_to on the reader.name to comment.reader ?
Please help me.
Thank you.
I'm guessing the reader is the user. If it is, then you'll probably want to have a whole authentication system (maybe think about Devise) and just assign the comment to the current user within the controller. You'll also want to assign the book in the controller.
EDIT:
Devise will make available a current_user method in your controller. So your create method in your comments controller should look like this:
def create
comment = current_user.comments.new(comment_params)
end
You don't need to assign the reader.name to comments. That's what the association is for. You can do comment.reader.name, or better yet, use delegate (look that up) and do comment.reader_name.

Devise - Nested Attributes - Customer --> Customer_addresses --> Address

I have been trying to create a relationship like the database listed below, using devise to create the Customer table. I have created migrations for the other tables using the ID ing though table-names and ID. My models look like the below. I know I have gone overboard with the has_many relationships, but I have been trying this all day.
Can anyone help or show the correct way to set up this so that I can create, edit and update addresses on the user. I have managed success with simple nested attributes say Customer/ Direct to address but when I place the table in the middle I just cant get the address attributes to show or update.
I guess there is a more complex edit, create , destroy method that needs to be implemented also.
I'm also getting lost with how to allow strong params on this type of nesting as most examples i can find only have it connected to tables that hold the user_id in them and not one that is connected through another table.
cheers in advance for the guidance.
DataModel image here
Tables
customers / Devise table defaults
customer_addresses
id
address_id
address_type_id
customer_id
addresses
id
address xzy Marua Road
other details / Front house
address_type
id
address_type / Home , Business etc
address_type_description / Where you live etc
class Customer < ActiveRecord::Base
has_many :customer_addresses
has_many :addresses
has_many :address_types
accepts_nested_attributes_for :customer_addresses
accepts_nested_attributes_for :address_types
accepts_nested_attributes_for :addresses
end
class UserAddress < ActiveRecord::Base
has_many :customer
has_many :address_types
has_many :addresses
end
class AddressType < ActiveRecord::Base
belongs_to :customer_address
end
class Address < ActiveRecord::Base
belongs_to :user_address
end
<div class="field">
<%= f.fields_for :user_addresses do |ff| %>
<div>
<%= ff.label :address_id %><br />
<%= ff.text_field :address_id %>
<%= ff.fields_for :address do |fff| %>
<%= fff.label :address %><br />
<%= fff.text_field :address %>
<% end %>
<% end %>
</div>
</div>
user controller params
def user_params
params.require(:user).permit(:id, :username, :first_name, :last_name, :email, :password, :password_confirmation,
user_address_attributes:[:user_is, :address_id, :address_type_id],
addresses_attributes:[:id, :address, :other_address_details ],
address_type_attributes:[ :id, :address_type])
end
The associations you defined should have been like the following
class Customer
has_many :customer_addresses
has_many :addresses, :through => :customer_addresses
has_many :address_types, :through => :customer_addresses
end
class Address
has_many :customer_addresses
has_many :customers, :through => :customer_addresses
has_many :address_types, :through => :customer_addresses
end
class AddressType
has_many :customer_addresses
has_many :customers, :through => :customer_addresses
has_many :addresses, :through => :customer_addresses
end
class CustomerAddress
belongs_to :customer
belongs_to :address
belongs_to :address_type
end
class Customer < ActiveRecord::Base
has_many :customer_addresses
accepts_nested_attributes_for :customer_addresses
end
class AddressType
has_many :customer_addresses
end
class CustomerAddress
belongs_to :address_type
belongs_to :customer
belongs_to :address
accepts_nested_attributes_for :address
after_initialize :add_address, unless: 'address.present?'
def add_address
self.build_address
end
end