Related
I have 2 models:
class Person
has_one :user
validates :document, uniqueness: true, on: :create
class User
belongs_to :person
accepts_nested_attributes_for :person
Then i have a form for users that information about the person:
<%= form_for #user do |f| %>
<%= f.fields_for :person do |person_fields| %>
<%= person_fields.label "Document" %>
<%= person_fields.text_field :document %>
<% end %>
<%= f.label "Username" %>
<%= f.text_field :username %>
<% end %>
When i go for /users/edit/1 for example, it loads the user its person attributes. If i change the username and save, it says that the document number is already at use, bypassing the person validation of uniqueness only on create.
Am i missing something here? Are validations like this not supposed to work on nested forms?
Nested attributes allow you to save attributes on associated records through the parent (has_one, has_many), not on the parent through the child (belongs_to).
Your model relationship is not correct; According to Rails api nested attributes are valid for the child through the parent, not the parent through the child as you have specified in your models below.
Your models
class Person
has_one :user
validates :document, uniqueness: true, on: :create
class User
belongs_to :person
accepts_nested_attributes_for :person (sets up nested attributes for parent)
You could set up nested attributes for the child via the parent, as such
Suggested change
class Person
has_one :user
validates :document, uniqueness: true, on: :create
accepts_nested_attributes_for :user
class User
belongs_to :person
Nesting on your form will work only if the nesting associations in the model are set up properly.
Sidebar: The beauty of :Inverse_of
This is a sidebar, but relevant in this context. Inverse_of gives you a reverse reference from the child to the parent. Rails spec for :inverse_of. You should try this out and see how it works for your app. It's a very good thing.
class Person
has_one :user, inverse_of: person
validates :document, uniqueness: true, on: :create
accepts_nested_attributes_for :user
class User
belongs_to :person, inverse_of: user
Debugging questions (added)
I added the on: :create validation to one of my models as a test. I would ask you do a similar test without the model nesting; I think it's informative to test the logic in the model for the on: create, which will validate the controller actions. And only then layer on the nesting attributes. What do you think?
Duplicate field on create => ROLLBACK
Started POST "/locations" for 127.0.0.1 at 2015-07-20 08:03:36 -0500
Processing by LocationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Fxxx", "location"=>{"name"=>"Foo", "food_trailer"=>"true", "street"=>"100 Main", "city"=>"Fredericksburg", "state"=>"Texas", "country"=>"US", "phone"=>"", "website"=>"", "short_desc"=>"A Texan, Ms. Diana has been cookin' up barbecue for 50 years and, spreadin' the “barbecue love” in Paris (France!) since 2010.", "known_for"=>"Tings", "meats_beef"=>"0", "meats_beef_ribs"=>"0", "meats_pork"=>"1", "meats_pork_ribs"=>"0", "meats_chicken"=>"0", "meats_turkey"=>"0", "meats_sausage"=>"0", "meats_venison"=>"0", "sauce"=>"0", "sides"=>"None"}, "commit"=>"Add Restaurant"}
[1m[36mUser Load (0.0ms)[0m [1mSELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1[0m [["id", 8]]
[1m[35m (0.0ms)[0m BEGIN
[1m[36mLocation Exists (46.9ms)[0m [1mSELECT 1 AS one FROM "locations" WHERE "locations"."short_desc" = 'A Texan, Ms. Diana has been cookin'' up barbecue for 50 years and, spreadin'' the “barbecue love” in Paris (France!) since 2010.' LIMIT 1[0m
[1m[35mLocation Exists (0.0ms)[0m SELECT 1 AS one FROM "locations" WHERE ("locations"."latitude" IS NULL AND "locations"."longitude" IS NULL) LIMIT 1
[1m[36m (46.9ms)[0m [1mROLLBACK[0m
Duplicate field on edit => COMMIT
Started PATCH "/locations/46" for 127.0.0.1 at 2015-07-20 08:04:30 -0500
Processing by LocationsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"xxx", "location"=>{"name"=>"Grillrestaurant Rusticana", "street"=>"Grillparzerstraße 5, 81675", "city"=>"München", "state"=>"Alabama", "country"=>"US", "phone"=>"", "website"=>"", "short_desc"=>"A Texan, Ms. Diana has been cookin' up barbecue for 50 years and, spreadin' the “barbecue love” in Paris (France!) since 2010.", "known_for"=>"", "meats_beef"=>"0", "meats_beef_ribs"=>"0", "meats_pork"=>"0", "meats_pork_ribs"=>"0", "meats_chicken"=>"0", "meats_turkey"=>"0", "meats_sausage"=>"0", "meats_venison"=>"0", "sauce"=>"0", "sides"=>""}, "commit"=>"Save changes to Restaurant", "id"=>"46"}
[1m[36mLocation Load (0.0ms)[0m [1mSELECT "locations".* FROM "locations" WHERE "locations"."id" = $1 LIMIT 1[0m [["id", 46]]
[1m[35m (0.0ms)[0m BEGIN
[1m[36mLocation Exists (0.0ms)[0m [1mSELECT 1 AS one FROM "locations" WHERE ("locations"."latitude" = 48.1334073 AND "locations"."id" != 46 AND "locations"."longitude" = 11.6100255) LIMIT 1[0m
[1m[35mSQL (15.6ms)[0m UPDATE "locations" SET "short_desc" = $1, "meats_beef" = $2, "meats_pork" = $3, "meats_beef_ribs" = $4, "meats_pork_ribs" = $5, "meats_chicken" = $6, "meats_turkey" = $7, "meats_sausage" = $8, "meats_venison" = $9, "sauce" = $10, "latitude" = $11, "updated_at" = $12 WHERE "locations"."id" = $13 [["short_desc", "A Texan, Ms. Diana has been cookin' up barbecue for 50 years and, spreadin' the “barbecue love” in Paris (France!) since 2010."], ["meats_beef", "f"], ["meats_pork", "f"], ["meats_beef_ribs", "f"], ["meats_pork_ribs", "f"], ["meats_chicken", "f"], ["meats_turkey", "f"], ["meats_sausage", "f"], ["meats_venison", "f"], ["sauce", "f"], ["latitude", 48.13340729999999], ["updated_at", "2015-07-20 13:04:30.437500"], ["id", 46]]
[1m[36m (46.9ms)[0m [1mCOMMIT[0m
I'm receiving the following error:
The AWS Access Key Id you provided does not exist in our records.
I'm not sure why I would be getting this error because I have the correct access key and secret key shown to me in my aws security credentials page (I have copied and pasted multiple times to refute the possibility of a typo). Why would it be telling me that my access key is not in their records if I am able to log in and visit my STILL empty buckets?
Here is my s3.yml:
GMAIL_USERNAME: <my email>
GMAIL_PASSWORD: <my password>
MONGOHQ_URL: <my mongoid url>
S3_BUCKET_NAME_DEVELOPMENT: <my development bucket>
S3_BUCKET_NAME_PRODUCTION: <my production bucket>
S3_BUCKET_NAME_TEST: <my test bucket>
AWS_ACCESS_KEY_ID: <my access key>
AWS_SECRET_ACCESS_KEY: <my secret key>
Here is my picture.rb:
class Picture
include Mongoid::Document
include Mongoid::Paperclip
embedded_in :datum, inverse_of: :pictures
has_mongoid_attached_file :attachment,
path: ':attachment/:id/:style.:extension',
storage: :s3,
url: ':s3_domain_url',
bucket: Proc.new{ |a| a.instance.s3_bucket },
s3_credentials: File.join(Rails.root, 'config', 's3.yml'),
styles: {
original: ['1920x1680', :jpg],
small: ['100x100', :jpg],
medium: ['250x250', :jpg],
large: ['500x500', :jpg]
},
convert_options: { all: '-background white -flatten +matte' }
def s3_bucket
if Rails.env.development?
ENV['S3_BUCKET_NAME_DEVELOPMENT']
else
ENV['S3_BUCKET_NAME_PRODUCTION']
end
end
end
Here is my datum.rb:
class Datum
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paperclip
field :title, type: String
field :user_id, type: Integer
validates :title, presence: true
validates :user_id, presence: true
embeds_many :todo_items
embeds_many :pictures, cascade_callbacks: true
accepts_nested_attributes_for :pictures
end
Here is my datum_params method:
# Never trust parameters from the scary internet, only allow the white list through.
def datum_params
params.require(:datum).permit(:title, :user_id, :identifying_image, pictures_attributes: [:picture])
end
Here is my picture_params method:
def picture_params
params.require(:picture).permit(:datum_id, :attachment)
end
Here is the form I use for the upload:
<%= bootstrap_form_for(picture, url: { action: :create }, html: { multipart: true }, remote: true ) do |f| %>
<%= f.file_field :picture, multiple: true, name: 'picture[picture]' %>
<%= f.submit 'Upload' %>
<% end %>
Originally, I was having a strong_parameters issue that rails4guides.com (the user with the answer below) was able to help me overcome. I knew I made progress when I stopped getting an unpermitted parameter error and started getting errors from AWS. Can anyone see any reason why my credentials are not working? Am I supposed to be seeing anything about my access key in the request parameters? Because I don't at this time.
Okay, I will explain further in a separate answer to make further elaboration more clear.
In a typical situation you would have something like this in your form :
<%= f.file_field :picture %>
Picture is the name of the file you want to upload. Now imagine this form will pass on it's results to the ImagesController. In order for the ImagesController to be able to process the picture it should be whitelisted through strong parameters, as in:
def image_params
params.require(:image).require(:picture)
end
An other case would be using nested attributes. If you want to set a picture for an article your form will look something like this:
<% f.fields_for :image do |image_form| %>
<%= image_form.file_field :picture %>
<% end %>
And your article_params will look like this:
def article_params
params.require(:article).permit(:title, :content, images_attributes: [:picture])
end
Small note: It should be images_attributes (both plural) and not image_attributes. The actual nested attributes on the other hand should be singular.
This is pretty much a basic outline of strong parameter usage, I hope it will help you. P.s. There might be some typos, since I'm typing this on my mobile phone.
In Rails 4, Why isn't an array of genre_id being saved to the database? I think it's because the ids are being read as a string, when it needs to be saved as an integer, but in the past I didn't have this problem.
I get a no implicit conversion of String into Integer error when trying to POST the following:
<%= form_for #project do |f| %>
<%= select_tag 'project[genres_projects_attributes][genre_id][]',
options_for_select(Genre.order(:id).collect{|g| [g.name, g.id]}),
{ include_hidden: true, multiple: true, class: 'form-control'} %>
<% end %>
In my app,
Project has_many :genres, through: :genres_projects
My parameters look like this:
{"utf8"=>"✓","authenticity_token"=>"[xxx]",
"project"=>{"name"=>"sdfds",
"genres_projects_attributes"=>{"genre_id"=>["2",
"3",
"4"]},
"commit"=>"Go!"}
My project_params:
def project_params
params.require(:project).permit(:id, genres_projects_attributes:
[:id, {genre_id: []}, :project_id])
end
Here's the line in my controller that's throwing the error:
def create
#project = Project.create(project_params)
end
Unfortunately didn't find an answer so did the following workaround:
#genre_ids = project_params[:genres_projects_attributes][:genre_id]
#genre_ids.each do |g|
GenresProject.create!(project_id: #project.id, genre_id: g)
end
UPDATE:
I found out that it was the first blank value that was causing the issue. As of Rails 4.x, you can also add include_hidden: false in your form.
UPDATE AGAIN:
if project_params[:genres_projects_attributes]
#project.genres.delete_all
project_params[:genres_projects_attributes][:genre_id].each do |g|
#project.genres << Genre.find(g)
end
#project.update!(project_params.except :genres_projects_attributes)
else
#project.update!(project_params)
I'm having trouble with embeds in a mongoid4-based rails 4 app. I've been looking for an answer everywhere for the past 2 days. So here is the code.
This is a church management app, with a Service model, embedding a team and a band. Each team/band has several roles such as "presidence", "communion" that refer to a user.
My models :
class Service
include Mongoid::Document
...
embeds_one :team, autobuild: true
embeds_one :band, autobuild: true
...
accepts_nested_attributes_for :team, :band
end
class Team
include Mongoid::Document
embedded_in :service
has_one :presidence, :class_name => 'User', autosave: true
has_one :message, :class_name => 'User', autosave: true
...
end
class Band
include Mongoid::Document
has_one :lead, :class_name => 'User', autosave: true
has_one :guitar, :class_name => 'User', autosave: true
...
embedded_in :service
end
class User
include Mongoid::Document
embeds_one :profile
belongs_to :team, :inverse_of => :presidence
belongs_to :team, :inverse_of => :message
belongs_to :band, :inverse_of => :lead
belongs_to :band, :inverse_of => :guitar
def fullname
"#{profile.firstname} #{profile.lastname}"
end
def self.find_by_talent(talent)
self.where("profile.talents.name" => talent)
end
end
The services controller :
# POST /services
# POST /services.json
def create
#service = Service.new(service_params)
respond_to do |format|
if #service.save
format.html { redirect_to #service, notice: 'Service was successfully created.' }
format.json { render action: 'show', status: :created, location: #service }
else
format.html { render action: 'new' }
format.json { render json: #service.errors, status: :unprocessable_entity }
end
end
end
...
def service_params
params.require(:service).permit(:date, :time, :place, :name, :theme, :team => [:id, :precedence, :message ], :band => [:id, :lead, :guitar ])
end
And the form in _form.html.erb :
<%= form_for(#service) do |f| %>
...
<%= f.fields_for #service.team do |tf| %>
<%= tf.collection_select :presidence, User.find_by_talent(:presidence), :_id, :fullname, {:include_blank => "select a person"} %>
<%= tf.collection_select :message, User.find_by_talent(:message), :id, :fullname, {:include_blank => "select a person"} %>
<% end %>
<%= f.fields_for #service.band do |bf| %>
<%= bf.collection_select :lead, User.find_by_talent(:lead), :id, :fullname, {:include_blank => "select a person"} %>
<%= bf.collection_select :guitar, User.find_by_talent(:guitar), :id, :fullname, {:include_blank => "select a person"} %>
<% end %>
...
<% end %>
When creating a service, everything seems to run fine, but this is what I get in the console :
2.0.0-p195 :001 > s = Service.last
=> #<Service _id: 52ea18834d61631e7e020000, date: "2014-02-02", time: "10:00", place: "Where it's at", name: "My great name", theme: "The service's theme">
2.0.0-p195 :002 > s.team
=> #<Team _id: 52ea18834d61631e7e030000, >
2.0.0-p195 :003 > s.team.presidence
=> nil
Why is s.team.presidence not created ? s.team looks weird, too, with an ending comma...
Here is the content of my rails log :
Started POST "/services" for 127.0.0.1 at 2014-01-30 10:16:51 +0100
Processing by ServicesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Ph6lbdHC2FbiANn/fGSzHWprenj3fWKXM40Hrsc5+AM=", "service"=>{"date"=>"2014-02-02", "name"=>"My great name", "theme"=>"The service's theme", "time"=>"10:00", "place"=>"Where it's at", "team"=>{"presidence"=>"52ea18324d61631e81010000", "message"=>"52ea18324d61631e81010000"}, "band"=>{"lead"=>"52ea18324d61631e81010000", "guitar"=>"52ea18324d61631e81010000"}}, "commit"=>"Create Service"}
MOPED: 127.0.0.1:27017 COMMAND database=admin command={:ismaster=>1} runtime: 0.6610ms
MOPED: 127.0.0.1:27017 UPDATE database=service_boot_camp_development collection=users selector={"band_id"=>BSON::ObjectId('52ea18834d61631e7e010000'), "_id"=>{"$nin"=>[]}} update={"$set"=>{"band_id"=>nil}} flags=[:multi]
COMMAND database=service_boot_camp_development command={:getlasterror=>1, :w=>1} runtime: 0.5800ms
MOPED: 127.0.0.1:27017 INSERT database=service_boot_camp_development collection=services documents=[{"_id"=>BSON::ObjectId('52ea18834d61631e7e020000'), "date"=>"2014-02-02", "time"=>"10:00", "place"=>"Where it's at", "name"=>"My great name", "theme"=>"The service's theme", "team"=>{"_id"=>BSON::ObjectId('52ea18834d61631e7e030000')}, "band"=>{"_id"=>BSON::ObjectId('52ea18834d61631e7e010000')}}] flags=[]
COMMAND database=service_boot_camp_development command={:getlasterror=>1, :w=>1} runtime: 2.7460ms
I guess I'm doing something wrong, but I have no clue if it is in the database model or in the form... or anything else...
You will not be able to do it this way. When you create an embedded document, its _id and all of its data are embedded directly within the parent document. This is in contrast to an association, where the document with the belongs_to gets a foreign key which points to its associated parent document. So here, your User documents each have a team_id and band_id, but when the database tries to get the documents, it can't find them, since you can't query directly for embedded documents; you need the parent document first. For more, see the Mongoid documentation.
Another potential issue is that you have multiple belongs_to definitions in the User models. This will also cause an issue, because for each one of those, Mongoid will attempt to create a team_id and band_id. You should name them separately and specify a class name; perhaps names like :presiding_team and :message_team, :lead_band and :guitar_band, etc. This answer should show you what that would look like.
I would recommend making the Team and Band separate referenced documents instead of embedded documents, since you won't be able to reference users effectively while they're embedded.
Hope this helps.
I'm trying to add a field to products on spree which is just a checkbox which is just ment to mark products if they are for sale or they're internal products.
I've added the migration and finally figured out how to add the checkbox on the form, but when I click Update I get Can't mass-assign protected attributes: for_sale
This is the migration
class AddProductForSaleField < ActiveRecord::Migration
def up
add_column :spree_products, :for_sale, :boolean
end
def down
remove_column :spree_products, :for_sale
end
end
Here's the field being added
Deface::Override.new(:virtual_path => "spree/admin/products/_form",
:name => "for_sale",
:insert_before => "code[erb-silent]:contains('track_inventory_levels')",
:partial => "spree/admin/products/for_sale")
And this is the partial
<%= f.field_container :for_sale do %>
<%= f.label :for_sale, t(:for_sale) %>
<%= f.check_box :for_sale, { :checked => true } %>
<% end %>
got it, was missing the model part
Spree::Product.class_eval do
attr_accessible :for_sale
end
Mass Assignment is the name Rails gives to the act of constructing your object with a parameters hash. It is "mass assignment" in that you are assigning multiple values to attributes via a single assignment operator.
The following snippets perform mass assignment of the name and topic attribute of the Post model:
Post.new(:name => "John", :topic => "Something")
Post.create(:name => "John", :topic => "Something")
Post.update_attributes(:name => "John", :topic => "Something")
In order for this to work, your model must allow mass assignments for each attribute in the hash you're passing in.
There are two situations in which this will fail:
You have an attr_accessible declaration which does not include :name
You have an attr_protected which does include :name
It recently became the default that attributes had to be manually white-listed via a attr_accessible in order for mass assignment to succeed. Prior to this, the default was for attributes to be assignable unless they were explicitly black-listed attr_protected or any other attribute was white-listed with attr_acessible.
If it's a permission issue then you can add :
Spree::Product.class_eval do
attr_accessible :variable_1, :variable_2 :as => [:default, :product]
end
Marking it as default for a specific model, will remove the mass-assignment warning message !