How to query a Ruby on Rails object's nested attribute? - sql

I have a User model and a Product model. The User has one product and the Product has one user. To create the User and Product models, I have a single form that creates both using nested attributes.
I am trying to create a search that can look for a User based on their name, email address or Product serial number. I have this working when looking up a User's name or email address, but I don't know how to go about looking up a User's Product by serial number in the same form.
So I'm trying to search User.last_name, User.email AND User.product.serial - cannot figure out how to go about this.
Here is my User model
class User < ActiveRecord::Base
has_many :products, dependent: :destroy
accepts_nested_attributes_for :products, :allow_destroy => true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(?:\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :last_name, presence: true
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }
def self.search(query)
where("last_name = ? OR email = ?", query, query)
end
end
And my Product model
class Product < ActiveRecord::Base
belongs_to :user
default_scope -> { order('created_at DESC') }
validates :serial, presence: true
end
My User view that contains the search field
<%= form_tag(users_path, method: "get", :id => "user_search_form") do %>
<%= text_field_tag :search, params[:search], placeholder: "Search users" %>
<%= submit_tag("Search", :name => nil ) %>
<% end %>
The relevant part of my Users controller
def index
if params[:search]
#users = User.search(params[:search]).order("created_at DESC").paginate(page: params[:page], :per_page => 30)
else
#users = User.paginate(page: params[:page], :per_page => 30)
#product = #users.product
end
end

You can do this:
def self.search(query)
joins(:products)
.where("last_name = :query OR email = :query OR products.serial = :query",
query: query)
end

Related

Limiting how often a user can post on a particular person's profile/wall in Rails

How can I limit a user to only to being able to post once or twice per day on a particular users's wall? I primarily want to do it in order to limit spam. My code for the wall, models, view, and controllers are below. I don't really know how to go about it as I'm new to rails but I know there is something time.now. I'm not exactly sure how to implement such a feature.
Class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#first_name = #user.first_name
#last_name = #user.last_name
#wallpost = WallPost.new(params[:wall_post])
#showwallposts = #user.received_wallposts
end
def create
#wallpost = WallPost.create(params[:wall_post])
end
models
class WallPost < ActiveRecord::Base
attr_accessible :content, :receiver_id, :sender_id
belongs_to :receiver, :class_name => "User", :foreign_key => "receiver_id"
belongs_to :sender, :class_name => "User", :foreign_key => "sender_id"
end
class User < ActiveRecord::Base
has_many :sent_wallposts, :class_name => 'WallPost', :foreign_key => 'sender_id'
has_many :received_wallposts, :class_name =>'WallPost', :foreign_key => 'receiver_id'
in the view
<%= form_for(#wallpost, :url => {:action => 'create'}) do |f| %>
<%= f.hidden_field :receiver_id, :value => #user.id %>
<%= f.hidden_field :sender_id, :value => current_user.id %>
<%= f.text_area :content, :class => 'inputbox' %>
<%= f.submit 'Post', class: 'right btn' %>
<% end %>
You could create a custom validator which assures maximum DAILY_LIMIT posts have been created on that person's wall that day by that user:
class SpamValidator < ActiveModel::Validator
DAILY_LIMIT = 2
def validate(record)
if similar_posts_today(record).count >= DAILY_LIMIT
record.errors[:spam_limit] << 'Too many posts today!'
end
end
def similar_posts_today(record)
WallPost.where(receiver: record.receiver, sender: record.sender)
.where("DATE(created_at) = DATE(:now)", now: Time.now)
end
end
Then add that validation to your WallPost model:
validates_with SpamValidator
Then it will fail with a validation error when trying to create a wall post beyond the limit set in the constant. You need to handle this case in the create action in your controller. A simple (but not optimal in terms of user experience) way of handling this is:
def create
#wallpost = WallPost.new(params[:wall_post])
flash[:error] = "You've reached the daily posting limit on that wall." unless #wallpost.save
redirect_to user_path(#wallpost.receiver)
end
With that, it'll try to save the new wall post, if it is unable to, it'll set flash[:error] to the error message above. You'd need to show this on your show.html.erb page with <%= flash[:error] if flash[:error] %>.

Multiple (n) identical nested forms generated square-times(n*n) when validation fails

User has two addresses shipping(:address_type=0) and billing(:address_type=1)
User form with 2 classic nested forms for each address type are generated square times every submit and failed validation.
Models:
class User < ActiveRecord::Base
has_many :addresses, :dependent => :destroy
accepts_nested_attributes_for :addresses
validates_associated :addresses
end
class Address < ActiveRecord::Base
belongs_to :user
validates :user, :address_type, :first_name, :last_name, :street
end
Controller
class UsersController < ApplicationController
public
def new
#user = User.new
#shipping_address = #user.addresses.build({:address_type => 0})
#billing_address = #user.addresses.build({:address_type => 1})
end
def create
#user = User.new(params[:user])
if #user.save
#fine
else
render => :new
end
end
Uncomplete Form
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for :addresses, #shipping_address do |f|
=f.hidden_field :address_type, :value => 0
=ff.fields_for :addresses, #billing_address do |f|
=f.hidden_field :address_type, :value => 1
=ff.submit
The form should look like this:
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for :addresses do |f|
Nothing else.
Addressess is already a collection, so you should have just one rendering of it.
Also that ":addresses, #shipping_address" makes it to render addresses AND shipping address, even if it's included in #user.addresses.
The addressess built in new action will show there because they are in the addresses collection.
EDIT:
If you need only these two addresses, you can sort it and pass it to fields_for directly:
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for ff.object.addresses.sort{|a,b| a.address_type <=> b.address_type } do |f|
That should do it.
Surprised? I guess not but I was. I found it am I correct? And its stupid and simple.
There is no #shipping_address nor #billing_address when validation fails and rendering the new action (the form) again. But #user has already 2 addresses builded and nested form behave correctly to render each twice for first time failed validation.
def create
#user = User.new(params[:user])
if #user.save
#fine
else
#user.addresses.clear
#user_address = #user.addresses.build({:address_type => 0})
#user_address.attributes = params[:user][:addresses_attributes]["0"]
#billing_address = #user.addresses.build({:address_type => 1})
#billing_address.attributes = params[:user][:addresses_attributes]["1"]
render => :new
end
end

Find and count all Profiles with object name of HABTM relationship

In my Rails 3 app I have two models, Profile and Item. Each has a HABTM relationship with the other. In my Profile model, I have a method wish_items that creates an Array of that same name (wish_items). If an item contains the category "wish", it is added to the wish_items Array for that profile.
For the purpose of this question, say I have an item named "phone" with category "wish". What I'd like to do is be able to find and count all Profiles that have "phone" in their wish_items Array so I can render that count in a view. My code is below.
My Profile.rb model:
class Profile < ActiveRecord::Base
has_and_belongs_to_many :items
def wish_items
wish_items = Array.new
items.each do |item|
if item.category == "wish"
wish_items << item
end
end
return wish_items
end
end
My Item.rb model:
class Item < ActiveRecord::Base
has_and_belongs_to_many :profiles
end
I have a join table items_profiles for this relationship. Here is that migration:
class CreateItemsProfiles < ActiveRecord::Migration
def self.up
create_table :items_profiles, :id =>false do |t|
t.references :item
t.references :profile
end
end
...
end
I saw this previous question and gave the answer a try but got an error NameError: uninitialized constant phone. Here is the code I tried from that:
Profile.all(:include => :items, :conditions => ["items.name = ?", phone])
Specifically I put that code in the following:
<%= pluralize(Profile.all(:include => :items, :conditions => ["items.name = ?", phone])).count, "person") %>
How can I do this?
The above was failing because I didn't have phone in quotations. Simple. The following worked:
Profile.all(:include => :items, :conditions => ["items.name = ?", "phone"])
And pluralizing:
<%= pluralize(Profile.all(:include => :items, :conditions => ["items.name = ?", "phone"]).count, "person") %>

how to insert same data into two tables using nested forms

I am using nested forms to insert data into two tables (user and address).I want to have my users email id in both the table, but the user should enter the email id once. Here is my current view
<%= form_for #user do |f| %>
<%= f.text_field(:name, :size => 20) %>
<%= f.text_field(:email, :size => 20) %>
<%= f.fields_for :address do |r| %>
<%= r.text_field(:street, :size => 20) %>
<%= r.text_field(:city, :size => 20) %>
<% end %>
<%= f.submit "Create" %>
<% end %>
In my nested section "address" i once again want the email field, but i don't want a repeated text field for email. How can i use the email section inside the nested form "address"? I have tried using hidden form fields, but it didn't worked. Can somebody help please.
EDIT
Controller(Only question related parts)
class UsersController < ApplicationController
def new
#domain = Domain.find(params[:id])
#user = #domain.users.build
#title = "Add User"
end
def create
#user = User.new(params[:user])
if #user.save
flash[:success] = "Welcome!"
redirect_to manageDomain2_path(:id => #user.domain_id)
else
#title = "Add User"
redirect_to addNewUser_path(:id => #user.domain_id)
end
end
I have tried with:
#user = User.new(params[:user][:address_attributes].merge!(:email => params[:user][:email]))
When using:
#user = User.new(params[:user][:address].merge!(:email => params[:user][:email]))
I am getting error "Undefined method merge".
User Model (Question related portion only)
class User < ActiveRecord::Base
attr_accessible: :email, :domain_id, :address_attributes
belongs_to :domain
has_one :address
accepts_nested_attributes_for :address
end
Address Model (Full)
class Address < ActiveRecord::Base
attr_accessible :user_id, :email, :street, :city
end
When i use
belongs_to :user
in address model i get syntax error. So i have tried without using it. the user_id, street, city is getting proper values but email email field is not getting any thing.
You can just add it to the params hash from your controller:
params[:user][:address].merge!(:email => params[:user][:email])

rails3 is not adding the 2nd id to my table

the problem I'm having is company_id is not been save to the details table
I know the company_id is there I check it using <%= debug(params[:id])%> on the form before adding all company details information but for some reason is saving everything else but the company_id
##user.rb
has_one :company
##company.rb
belongs_to :user
has_one :detail
##detail.rb
belongs_to :user
##details controller
def new
#detail = Detail.new
user_id = session[:user_id]
company_id = params[:id]
end
def create
#detail = Detail.new(params[:detail])
#detail.user_id = session[:user_id]
#detail.company_id = params[:id]
end
###settings.html.erb
### this is where i make sure that company_id gets pass with the url
link_to 'New Detail', {:controller => 'details', :action =>'new', :id => company.id }, :class => 'plus'
#####routes
resources :details
resources :companies
resources :users
resources :sessions
I know this may not look pretty or proper if you know a better way please let me know...thanks in advance.
I notice something immediately. You may need to fix your associations first. Assuming, a user has one company and a company has one detail.
##user.rb
has_one :company
##company.rb
belongs_to :user
has_one :detail
##detail.rb
belongs_to :user
Should be:
##user.rb
has_one :company
##company.rb
belongs_to :user
has_one :detail
##detail.rb
belongs_to :company
Although, depending on your domain requirements. I would normally have it as: User has_many Companies. And since details is 1:1 with company, I would include all the details inside company.
I didn't realize this until later but I need it pass values to from on the view to the from like so
<% #companies.each do |company| %>
<%= link_to 'New Detail', {:controller =>'details', :action => 'new', :company_id => company.id}, :class => 'plus' %>
<% end %>
and I need it to collect that value on the from...like so
<%= form_for(:detail, :url =>{:action => 'create', :company_id => #company.id}) do |f| %>
...(values)
<% end %>