How to show a view based on user attribute? - ruby-on-rails

In my Rails app, I have a store with products and users. Both of those have models and controllers.
What I want to achieve is to show on a view template a product to a current_user based on the attribute from a model that he has. For an example if a user has "Female" attribute from user model, and then to show some products related to this attribute. How can I achieve this?
These are my product views where all products are showed:
<% #products.each do |product| %>
<%= render "product_row", product: product, order_item: #order_item %>
<% end %>
_product_row.html.erb
<h4><%= product.name %></small></h4>
<div class="image">
<%= image_tag product.image.url(:original), class: "img-responsive" %></div>
<p>Some description.</p>
Add to Package

It sounds like you need to retrieve objects from your product class based on an attribute from the User class. Sounds like a basic service object or just a method on the user:
class User < ActiveRecord::Base
def products_for_gender
if gender == 'female'
Product.where("do some logic here based on female")
elsif gender == 'male'
Product.where("do some logic here based on male")
else
#do some other logic just in case it's nil
end
end
end
then in your controller you do this
#products = current_user.products_for_gender
in your view you then render a list with those products. This prevents you from putting logic in your view, which is rarely a good idea.
Also there's more abstraction possible, the if statement is not the prettiest, but this will cover your issue i believe. Eventually you could look into using service objects for example, https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services

How are you saving the users model. If you are using devise then you have current_user helper method available in your views and you can use that to get the curren_user.gender attribute and show the view based on this

Related

Display multiple model results in your views in rails app

I'm adding a new model to my equasion and I'm wondering if there is a way to associate two models into one model then display any/all results within a view. For example, here is what I've currently have;
#tweet_category.order("position").each do |tweet|
<%= tweet.title %>
end
just a short example... now what if I added facebook into this. I was first thinking of creating a model thats named stuff then associate it to tweet_category and facebook_category like so;
class Stuff < ActiveRecord::Base
attr_accessible :title
belongs_to :user
has_many :tweet_category
has_many :facebook_category
end
Now in my controller I'm guessing I would do the following;
class StuffController < ApplicationController
def index
#stuff_list = Stuff.find(:all)
end
end
and in my view I would just simply do the following from above view;
#stuff_list.order("position").each do |stuff|
<%= stuff.title %>
end
am I understanding the logic here??? would that work having two models / two tables db.. etc..
First of all, I don't understand why you would need that "stuff" model. It belongs to users and has_many tweet_category and facebook_category, and just does nothing but offering a "title", when your User model could do the job ( I mean, each user could have many tweets and fb category, instead of having one or several "stuff" which has/have many of them ).
Anyway, if you want to make links between your models and then display everything in a view, first in your User model you just have to do :
class User < ActiveRecord::Base
...
has_many :facebook_categories #( I don't know how rails would pluralize it, btw, I'm just making an assumption )
has_many :tweeter_categories
end
and
class Facebook_category
...
belongs_to :user
end
and do the same fot the tweeter category
Then in your controller :
def show_everything #Here it's a custom action, but you can call it wherever you want
#users = User.all
end
And finally in your view :
<% #users.each do |user| %>
<% user.facebook_categories.all.each do |fb_c| %>
<%= fb_c.title %>
<% end %>
<% user.tweeter_categories.all.each do |t_c| %>
<%= t_c.title %>
<% end %>
<% end %>
Maybe just try to grab a better name for your models, so the pluralization doesn't get messy ( and I saw that the ".all" method is deprecated, so maybe replace it with something
Hope it helps !
Edit :
Basically, when you're doing
#users = User.all
What rails' doing is putting every hash defining every "User" in an array. So, if you want to mix two tables' arrays inside a single array, you can do something like this :
#categories = [] << Facebook_category.all, Tweeter_category.all
You will then have an array ( #category ), filled with 2 arrays ( one ActiveRecord relation for Facebook_category and one for Tweeter_category ). Themselves filled with hashes of their model. So, what you need to do is :
#categories.flatten!
Here's the API for what flatten does ( basically removing all your nested arrays inside your first tarray )
Now, you got a single array of hashes, being the informations from both your model's instances. And, if these informations can be ordered, in your view, you just have to :
<% #categories.order("updated_at").each do |i| %>
<%= i.title %>
<% end %>

Rails 4 - Calling a value from one model into another

I'm creating a marketplace app where sellers can list items to sell. I am in the process of creating seller pages - a page with seller profile and their specific listings.
I've gotten as far as creating the page with seller listings but am having trouble pulling in the name and profile info which is in another model.
For the context here, I have 2 models - a listing model and a user model. The listing model has a user_id which joins with the user table. The user table has name, profile_image, profile_description.
My routes.tb:
get '/listings/s/:id' => 'listings#vendor', as: 'vendor'
My listings_controller.rb:
def vendor
#listings = Listing.where(user: User.find(params[:id]))
end
My view: Note that in the first line below I have ???. I want to pull in user.name in there, which is the sellers name. How do I pull that in? Once I know that, I can use the same process to pull in other fields from the user model.
<h4>Listings for ??? </h4>
<div class="center">
<div class="row">
<% #listings.each do |listing| %>
<div class="col-md-3 col-sm-3 col-xs-6">
<div class="thumbnail" >
<%= link_to image_tag(listing.image.url(:medium), class: "img-responsive"), listing, data: { no_turbolink: true } %>
</div>
<div class="caption">
<h3><%= link_to listing.name.downcase.titleize, listing, data: { no_turbolink: true } %></h3>
<p><%= number_to_currency(listing.price) %></p>
</div>
</div>
<% end %>
</div>
</div>
You can set another instance variable for the user. For example:
def vendor
#user = User.find(params[:id])
#listings = Listing.where(user: #user)
end
and then in the view:
<h4>Listings for <%= #user.name %> </h4>
Associations
You'll be best looking up about ActiveRecord Associations
ActiveRecord is an ORM (Object Relationship Mapper), which provides a level of abstraction for your application's object associations. The importance of this is that if you use it correctly, it will only make Rails run much faster, but also ensure your code is succinct:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :listings
end
#app/models/listing.rb
class Listing < ActiveRecord::Base
belongs_to :user
end
This means that if you call a #user object, you'll be able to call the associative data too, like so:
def vendor
#user = User.find params[:id]
#listings = #user.listings
end
The value of this is that the association is then take care of with ActiveRecord (not in your controller). If you therefore change your association (change models etc), you'll have a single place to make the required changes, rather than having to pick through all your controllers again
Objects
To give you a better understanding about this, you need to consider the role of objects in a Rails application.
Since Rails is built on top of Ruby (it's a gem), it's an object orientated framework. Object orientation means more than just being a buzzword - it's an entire practice of programming; putting the objects for your software at the center of the flow (as opposed to a logic-driven flow):
In reality, objects are just variables with a lot of JSON-notation data inside, including things like data-type etc. Rails populates objects in your models, allowing you to then use the objects in a variety of Rails helper methods such as form_for etc.
The reason this is important is because of how object orientation works. You can associate objects, manipulate them & destroy them. This means that anything you do in Rails should be based around objects, which is why the association I mentioned above is so important

Show link if session user matches certain criteria

I'm trying to make a specific link in my application only visible to users who have the attribute :department equal to "Sales".
In other words, I've got a model Users, in which a user has a password, username, and department. The session saves the :user_id once the user is logged in.
What I would like to do is when my view is rendered, depending on the :department of the logged in user, either display, or don't display a specific link.
Here's the code I've got in my view, but I'm struggling with how to take the session info and find the department of the user from it.
<% if Users.where(id: session[:user_id])[:department] == "Sales" %>
<%= link_to 'New Request', new_request_path %>
<% else nil %>
<% end %>
I know it's bad to do a query anywhere other than the controller or model, so if you have any advice on how to better structure this logic as well, I would appreciate it.
I think what you want is:
<% user = User.find_by_id(session[:user_id]) %>
<% if user.present? && user[:department] == "Sales" %>
<%= link_to 'New Request', new_request_path %>
<% end %>
Personally, I'd put this into a helper method to clean it up:
In app/helpers/users_helper.rb:
def user_in_sales?
user = User.find_by_id(session[:user_id])
user.present? && user[:department] == "Sales"
end
Then your view:
<% if user_in_sales? %>
<%= link_to 'New Request', new_request_path %>
<% end %>
Personally, I'd strongly look into using something like cancan to handle this situation. I think you may find that you could use cancan as an effective authorization tool other places in your app, especially if you're doing logic like this elsewhere.
First, you're using an object oriented language. It would help you to quit getting hung up on implementation details (e.g. department == "Sales") and instead consider the intent or meaning you're trying to express and code to meet that. For example:
if current_user.works_in?(:jewelry)
link_to 'Request Receipt', new_request_path
end
Your models should expose a public interface that allows other objects in your code (like your controller) to get the information they need (i.e. whether or not a user has an association with a department) without knowledge of or concern for the underlying data storage schema.
class User
def works_in?(department_name)
departments.pluck(:name).include?(department_name.to_s)
end
end

avoid issue in the mvc

I know that a view shouldn't have any knowledge, of the model or the controller but i am not to sure how to avoid it.
The issue is i am trying to fetch the information from the controller or the model before i need to manipulate it in my view.
Here the code
Model
USER LOCATION ARTICLE
id user_id title
first article_id ...
last
...
Relationship
user has many locations
article has many locations
location belongs to user
location belongs to article
controller
#userlocation = #user.locations
View
<% #userlocation.each do |event| %>
...
<% evTitle = Article.find_by_id(event.article_id) %>
<%= evTitle.title %>
<% end %>
Obviously its not the best not sure how to do the query in the controller, and evTitle doesnt report properly why?
In controller you can do like this:
#userlocations = #user.locations
#locations_articles = Article.find_all_by_id(#userlocations.map(&:article_id).uniq)
Then you can use #locations_articles in view. Avoid using db queries in view.
<% #locations_articles.each do |article| %>
<%= article.title %>
<% end %>
You can first watch the rails cast multiple form episode at here :-
http://railscasts.com/episodes/196-nested-model-form-part-1
and there is an attribute that first you should define in model to access the information at single view form.
def new
#survey = Survey.new
3.times do
question = #survey.questions.build
4.times { question.answers.build }
end
end

Complex form with Rails

I have a form where I'd like to create a parent record and a child record at the same time. For a simple example let's say its a Company with the first Employee.
in my controller I do something like:
def new
#company = Company.new
#company.employees.new
end
and in my view this:
<%= form_for(#company) do |form| %>
<div>
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<%= form.fields_for :employees do |employee_form| %>
<div>
<%= employee_form.label :name %>
<%= employee_form.text_field :name %>
</div>
<% end %>
<% end %>
and back in my controller again:
def create
#company = Company.new(params[:company])
#company.employees << Employee.new(params[:company][:employees_attributes]["0"])
# save stuff
end
Question 1:
I couldn't get the employee collection on the company to be populated with the single employee created in the form. When I looked at the params I found the [:employees_attributes]["0"] stuff.
What I have works, but is there a cleaner way to do this?
Question 2:
If the validation doesn't pass for the employee I get a generic "Employees is invalid" instead of the Name required validator message. I get I am calling save on the collection and rails is doing its best to bubble a validation error up, but is there a cleaner way to do this so I can get the errors specific to the employee?
In Short
How can I clean this up so the related models are created automatically from the params, and so that I get the validation messages for a single employee.
Thanks for looking.
1) fields_for arranges for the child objects attributes to be nested inside the parent objects attributes in the params hash that gets sent back to the controller action. To get Rails to automatically update the child objects tell the parent model to accept nested attributes using the accepts_nested_attributes_for declaration.
2) There is an errors object for every ActiveRecord object. Loop through the errors list and display the messages.
Best way to achieve this is to create a partial and a view helper method that will take render the errors for you. then replace the generated errors messages in the forms with a call to your render_error_messages method. You have all the code to do this already in the generated forms. You just need to refactor that code into a partial, create the helper - which should accept an array of model names as a parameter then do what you want with the info. Wither render a partial for each model or render a partial that will deal with child objects as well as the parent object. Totally your call.
3) Change your new action to build rather that create a new child object so instead of
def new
#company = Company.new
#company.employees.new
end
do this
def new
#company = Company.new
#company.employees.build
end
4) Watch those Railscasts to see how accepts_nested_attributes works
http://railscasts.com/episodes/196-nested-model-form-part-1
and
http://railscasts.com/episodes/197-nested-model-form-part-2
Update
So how does the above information leave you in relation to your questions.
1) What I have works, but is there a cleaner way to do this?
You've fixed the new action as per point 3 above right? Now your create action can look like this
def create
#company = Company.new(params[:company])
# save stuff
end
Which is much cleaner as it has reverted to the original generated create action.
You may not think that's much of an update and therefore not that much cleaner. Well in isolation you'd be right. But consider that you could add as many relationships as you like ad add as many fields_for declarations as you like nd you could turn the user -> employee relationship into a has_many (I know that you wouldn't). You could do all that and your create and update actions stay EXACTLY the same and that's why it's cleaner.
2) is there a cleaner way to do this so I can get the errors specific to the employee?
Given my response in point 2 above you know that there is an errors object on the employee object as well as on the user object right? You also know now that you can loop through that errors object to get the messages right?
So you could do this
<% if #user.employee.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.employee.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.employee.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
At the risk of repeating myself I'll just say that you should refactor your error messages view code into a partial that will take any object as a parameter then you can call it from any view thus enabling you to change the styling and the functionality for all your forms.
Hope that's clearer

Resources