Issue: Specific record from associated model won't pass through on create, but others from the same associated model will....
I have the following in my create for UserProducts:
def create
#user_product = UserProduct.new(user_product_params)
#product = Product.find_by(params[:product_id])
#user = current_user
#user_product.user_id = #user.id
#user_product.country = #product.country
#user_product.production_price = #product.price
...
private
...
def user_product_params
params.require(:user_product).permit(:product_id, :color, :size, :production_price, {product_ids: []})
end
Models:
Product:
has_and_belongs_to_many :user_products
User Products:
has_and_belongs_to_many :products
The issue I don't get is that #user_product.country = #product.country passes through to my models db perfectly fine. But #user_product.production_price = #product.price won't. Is there a possible interference?
Product model has a t.string "price", and UserProduct has the t.string "production_price"
I attempted putting .to_s at the end of price, didn't work. Tried putting .to_i at the end of price, didn't work either.
If I do user_product.production_price = #product.price + 5 it will go through as 5. So, it's essentially saying production_price is nil when it isn't?
My biggest mind boggle is it passes country through but not price.
In the CMD. I see everything passing through, including :country without production_price. No errors, or unpermitted params, etc.
Related
I'm a beginner to coding. Apologies if this is some simple answer, but I've been looking for a couple hours and no luck.
Current problem: May 2018 exists with 1 run.
On my /months index page, there is a link to the May 2018 page, where I could create my future runs this month.
However, if I create a 2nd run, when I navigate back to my /months index page, TWO links to May 2018 show up (not one like I expect).
In the db there is only one object May 2018, and it owns both runs. (It then becomes 3, 4, 5, 6, etc. links when I create more runs...)
Quick summary: This is a running-log app. A month has_many runs.
When I create a run, it's attached to a month.
runs_controller.rb
def create
#run = #month.runs.build(run_params)
#run[:user_id] = current_user.id
#run[:pace_per_mile] = #run.format_pace_per_mile
if #run.save
redirect_to month_path(#month)
else
#month = Month.find(params[:month_id])
#runs = #month.runs
render 'months/show'
end
end
Here is my /month index.html.erb code where the error is happening:
<strong><h2>Your Previous Runs</h2></strong>
<% #months.each do |month| %>
<%= link_to(month) do %>
<h3><%= month.name %> <%= month.year %></h3>
<% end %>
<% end %>
Here is my months#index so you can see the scope.
def index
#months = current_user.months
#month = Month.new
end
I can provide more code if I'm not including something!
#xploshioOn, #fool-dev, and #moveson, Thanks for your responses.
I'm including the month and user models, as well as the code where a month gets created...
month.rb
class Month < ApplicationRecord
has_many :runs
has_many :users, through: :runs
validates :name, :year, presence: true
def month_mileage
self.runs.collect {|run| run.distance}.sum
end
end
user.rb
class User < ApplicationRecord
has_secure_password
validates :email, presence: true
validates :email, uniqueness: true
validates :password, presence: true
validates :password, confirmation: true
has_many :runs
has_many :months, through: :runs
end
I'm currently creating months from the months_controller. I'm starting to get the feeling this is where my error lies?
def create
#month = Month.new(month_params)
if #month.save
redirect_to month_url(#month)
else
#months = current_user.months
render :index
end
end
Thanks again for any advice!
It may be confusing to have the relationship with a User having many months through runs. Consider whether that relationship is necessary at all.
If you want to keep your current has many through relationship between User and Month, in your MonthsController#index action, you can do this:
def index
#months = current_user.months.uniq
#month = Month.new
end
If you want to do away with that relationship, in your MonthsController#index action, I would do this:
def index
#months = current_user.runs.map(&:month).uniq
#month = Month.new
end
Accessing months through current_user.runs is more explicit and may be easier to follow. Calling .uniq on the result will eliminate duplicates.
Keep in mind that both of the above options will result in your getting back an Array rather than an ActiveRecord object. To avoid this problem, you could run your query directly on the Month model:
def index
#months = Month.joins(runs: :user).where(users: {id: current_user}).distinct
#month = Month.new
end
This will return an ActiveRecord object allowing you to further refine your query.
You are loading an association and it takes the month for every item that is in the relationship. So just add .uniq to your query.
#months = current_user.months.uniq
Could not find nothing close to what I'm trying to do. I want to store an object into a user's column. That column is in the form of an array:
#postgres
def change
add_column :users, :interest, :string, array: true, default: '{}'
end
I have another model called FooBar setup for other use. Each user has unique information inside as I've added a user_id key.
Im trying to make more sense:
def interest
#user = User.find(current_user.id ) # I need the logged in user's id
#support = Support.find(params[:id]) # I need the post's id they are on
u = FooBar.new
u.user_id = #user
u.support_id = #support
u.save # This saves a new Foo object..this is what I want
#user.interest.push(FooBar.find(#user)) # This just stores the object name itself ;)
end
So when I call u1 = FooBar.find(1) I get value return in hash. I want when I say u1.interest I get the same. The reason is, I need to target those keys on the user ie: u1.interest[0].support_id
Is this possible? I've looked over my basic ruby docs and nothing works. Oh..if I passed FooBar.find(#user).inspect I get the hash but not the way I want it.
Im trying to do something similar to stripe. Look at their data key. That's a hash.
Edit for Rich' answer:
I have, literally, a model called UserInterestSent model and table:
class UserInterestSent < ActiveRecord::Base
belongs_to :user
belongs_to :support # you can call this post
end
class CreateUserInterestSents < ActiveRecord::Migration
def change
create_table :user_interest_sents do |t|
t.integer :user_id # user's unique id to associate with post (support)
t.integer :interest_sent, :default => 0 # this will manually set to 1
t.integer :support_id, :default => 0 # id of the post they're on
t.timestamps # I need the time it was sent/requested for each user
end
end
end
I call interest interest_already_sent:
supports_controller.rb:
def interest_already_sent
support = Support.find(params[:id])
u = UserInterestSent.new(
{
'interest_sent' => 1, # they can only send one per support (post)
'user_id' => current_user.id, # here I add the current user
'support_id' => support.id, # and the post id they're on
})
current_user.interest << u # somewhere this inserts twice with different timestamps
end
And the interest not interests, column:
class AddInterestToUsers < ActiveRecord::Migration
def change
add_column :users, :interest, :text
end
end
HStore
I remembered there's a PGSQL datatype called hStore:
This module implements the hstore data type for storing sets of
key/value pairs within a single PostgreSQL value. This can be useful
in various scenarios, such as rows with many attributes that are
rarely examined, or semi-structured data. Keys and values are simply
text strings.
Heroku supports it and I've seen it used on another live application I was observing.
It won't store your object in the same way as Stripe's data attribute (for that, you'll just need to use text and save the object itself), but you can store a series of key:value pairs (JSON).
I've never used it before, but I'd imagine you can send a JSON object to the column, and it will allow you to to use the attributes you need. There's a good tutorial here, and Rails documentation here:
# app/models/profile.rb
class Profile < ActiveRecord::Base
end
Profile.create(settings: { "color" => "blue", "resolution" => "800x600" })
profile = Profile.first
profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
--
This means you should be able to just pass JSON objects to your hstore column:
#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def update
#profile = current_user.profile
#profile.update profile_params
end
private
def profile_params
params.require(:profile).permit(:x, :y, :z) #-> z = {"color": "blue", "weight": "heavy"}
end
end
As per your comments, it seems to me that you're trying to store "interest" in a User from another model.
My first interpretation was that you wanted to store a hash of information in your #user.interests column. Maybe you'd have {name: "interest", type: "sport"} or something.
From your comments, it seems like you're wanting to store associated objects/data in this column. If this is the case, the way you're doing it should be to use an ActiveRecord association.
If you don't know what this is, it's essentially a way to connect two or more models together through foreign keys in your DB. The way you set it up will determine what you can store & how...
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :interests,
class_name: "Support",
join_table: :users_supports,
foreign_key: :user_id,
association_foreign_key: :support_id
end
#app/models/support.rb
class Support < ActiveRecord::Base
has_and_belongs_to_many :users,
class_name: "Support",
join_table: :users_supports,
foreign_key: :support_id,
association_foreign_key: :user_id
end
#join table = users_supports (user_id, support_id)
by using this, you can populate the .interests or .users methods respectively:
#config/routes.rb
resources :supports do
post :interest #-> url.com/supports/:support_id/interest
end
#app/controllers/supports_controller.rb
class SupportsController < ApplicationController
def interest
#support = Support.find params[:support_id] # I need the post's id they are on
current_user.interests << #support
end
end
This will allow you to call #user.interests and bring back a collection of Support objects.
Okay, look.
What I suggested was an alternative to using interest column.
You seem to want to store a series of hashes for an associated model. This is exactly what many-to-many relationships are for.
The reason your data is being populated twice is because you're invoking it twice (u= is creating a record directly on the join model, and then you're inserting more data with <<).
I must add that in both instances, the correct behaviour is occurring; the join model is being populated, allowing you to call the associated objects.
What you're going for is something like this:
def interest_already_sent
support = Support.find params[:id]
current_user.interests << support
end
When using the method I recommended, get rid of the interest column.
You can call .interests through your join table.
When using the code above, it's telling Rails to insert the support object (IE support_id into the current_user (IE user_id) interests association (populated with the UserInterestSelf table).
This will basically then add a new record to this table with the user_id of current_user and the support_id of support.
EDIT
To store Hash into column, I suggest you to use "text" instead
def change
add_column :users, :interest, :text
end
and then set "serialize" to attribute
class User < ActiveRecord::Base
serialize :interest
end
once it's done, you can save hash object properly
def interest
#user = User.find(current_user.id ) # I need the logged in user's id
#support = Support.find(params[:id]) # I need the post's id they are on
u = FooBar.new
u.user_id = #user
u.support_id = #support
u.save # This saves a new Foo object..this is what I want
#user.interest = u.attributes # store hash
#user.save
end
To convert AR object to hash use object.attributes.
To store a custom hash in a model field you can use serialize or ActiveRecord::Store
You can also use to_json method as object.to_json
User.find(current_user.id ).to_json # gives a json string
I have a couple models shown below and I'm using the search class method in Thing to filter records
class Category << ActiveRecord::Base
has_many :thing
end
class Thing << ActiveRecord::Base
belongs_to :category
:scope approved -> { where("approved = true") }
def self.search(query)
search_condition = "%" + query + "%"
approved.where('name LIKE ?', search_condition)
end
end
It works fine in my Things controller. The index route looks like so:
def index
if params[:search].present?
#things = Thing.search(params[:seach])
else
#thing = Thing.all
end
end
On the categories show route I display the Things for this category. I also have the search form to search within the category.
def show
#category = Categories.find(params[:id])
if params[:search].present?
#category.things = #category.things.search()
end
end
So the problem is that the category_id attribute of all the filtered things are getting set to nil when I use the search class method in the categories#show route. Why does it save it to database? I thought I would have to call #category.save or update_attribute for that. I'm still new to rails so I'm sure its something easy I'm overlooking or misread.
My current solution is to move the if statement to the view. But now I'm trying to add pages with kaminiri to it and its getting uglier.
<% if params[:search].present? %>
<% #category.things.search(params[:search]) do |thing| %>
... Show the filtered things!
<% end %>
<% else %>
<% #category.things do |thing| %>
... Show all the things!
<% end %>
<% end %>
The other solution I thought of was using an #things = #categories.things.search(params[:search]) but that means I'm duplicated things passed to the view.
Take a look at Rails guide. A has_many association creates a number of methods on the model to which collection=(objects) also belongs. According to the guide:
The collection= method makes the collection contain only the supplied
objects, by adding and deleting as appropriate.
In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id]).
Like Yan said, "In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id])". Validations will solve this problem.
Records are being saved as nil because you have no validations on your model. Read about active record validations.
Here's the example they provide. You want to validate presence as well because records are being created without values.
class Person < ActiveRecord::Base
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
I know this question has been asked a zillion times, but I still feel I'm missing something obvious. Given a model Address with a field city which we want to be initialized with a default value, say Awesome City.
What I've tried so far:
1) Default value in the view:
# #file: /app/views/addresses/_form.html.erb
<dt><%= f.label :city %></dt>
<dd><%= f.text_field :city, :value => 'Awesome city' %></dd>
This works but doesn't feel right. Also the user cannot change it, the value will still be Awesome city.
2) Overwriting the city method:
# #file: app/models/address.rb
def city
self[:city] ||= 'Awesome city'
end
The field is rendered empty, so for some reason it doesn't work. If you try Address.new.city you get the default value though.
3) Assign the default value in the controller:
# address doesn't have a controller, we use nested forms
# belongs_to :parent
# #file: /app/controllers/parents_controller.rb
def new
#parent = Parent.new
#parent.build_address
#parent.address.city = 'Awesome city'
end
This actually works but I don't think it should be in the controller, rather in the model.
4) Adding an after_initialize call (yes, it works, you don't have to overwrite the callback):
# #file: app/models/address.rb
after_initialize :set_default_city
def set_default_city
self.city = 'Awesome city'
end
This works as well, but this will be called every time an object is instantiated as well, which we don't want.
5) JavaScript, this seems like an extreme measure, it should be easier than this.
Bottom line: what's the best way to add a default value? Everytime I have to give a field a default value it seems I'm trying to hard for such a simple thing. Are there other options which I'm missing?
Edit:: I'm using Rails 3.1.4 with Ruby 1.8.7/1.9.2
You can define it in your migration file:
class CreateAddress < ActiveRecord::Migration
def change
create_table :addresses do |t|
...
t.string :city, default: "Awesome City"
...
t.timestamps
end
end
end
Other option is to define it in the new action, in your controller, which is more flexible... So, when you create a new address it will be started with the default value....
def new
#address = Address.new
#address.city ||= "Awesome City"
...
end
EDIT - possible solution to define default value in the model:
before_save :set_default_city
def set_default_city
self.city ||= 'Awesome city'
end
My ruby is a little rusty but something like
Parent < ActiveRecord::Base
def self.custom_parent(options)
custom_parent = self.new
if options.empty?
custom_parent.update_attributes(default_options)
else
//Iterate over all options and assign them to custom_parent
end
return custom_parent
end
end
you have to create the "default_options" hash
I don't think this will run out of the box but I think you got my thought
In my Rails app I have Users. Users are asked for their home city and district / neighborhood.
class User < ActiveRecord::Base
belongs_to :city
belongs_to :district
end
class City < ActiveRecord::Base
has_many :users
has_many :districts
end
class District < ActiveRecord::Base
has_many :users
belongs_to :city
end
In forms I build the associations using a virtual attribute on the User model that accepts a string (more info below in case it's relevant).
In the console this all works great, but in the UI it's not working. The problem seems to be, I can get city_name through a form no problem, but when I try to assign city and district in the same form it always fails. In other words, mass assignment doesn't work.
#user.update_attributes(params[:user])
Instead the only thing I have been able to figure out is to manually set each key from a form submission, like:
#user.name = params[:user][:name] if params[:user][:name]
#user.city_name = params[:user][:city_name] if params[:user][:city_name]
#user.district_name = params[:user][:district_name] if params[:user][:district_name]
This approach works, but it's a pain, kind of brittle, and feels all wrong because it starts gunking the controller up with a lot of logic.
My question is:
Is there a way to create or update attributes in a specific order, ideally in the model so that the controller doesn't have to worry about all this?
Am I doing this wrong? If so, what approach would be better.
Extra Info
Here's how I build the associations using virtual attributes on the user model, in case that's relevant to any potential answerers:
I want users to be able to select a city by just typing in a name, like "Chicago, IL". This works fine, using a virtual attribute on the user model like so:
def city_name
city.try :full_name
end
def city_name=(string)
self.city = City.find_or_create_by_location_string( string )
end
It only makes sense for a user to find or create a district from the city they've chosen. This works slightly differently:
def district_name
district.try :name
end
def district_name=(string)
if self.city.nil?
raise "Cannot assign a district without first assigning a city."
else
self.district = self.city.districts.find_or_create_by_name( string )
end
end
In the model layer these things work fine, as long as both a city_name and district_name are set the district association works as expected.
I think you could do a few things to clean this up. First, you can use delegates to clean up the code. e.g.
delegate :name, :to => :city, :prefix => true, :allow_nil => true
that way you can do something like
user = User.create
user.city_name # nil
city = City.create(:name => 'chicago')
user.city = city
user.save
user.city_name # chicago
and it will just work.
Next, I would say take the name-to-id logic out of your model. You can do it either in the form (e.g. an ajax search puts the district id/city id into a hidden field), or in the controller. Then just assign the city/district as normal.