Rails 4 not passing permitted parameters - ruby-on-rails-4

Apologies for posting what seems to be an oft-asked question but I have toiled for two days without getting closer.
I am running Rails 4.0.1 (with Devise, CanCan, among others) and have user and role models with a HABTM users_roles table (as created by the since-removed rolify).
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource, :polymorphic => true
attr_reader :user_tokens
def user_tokens=(ids)
self.user_ids = ids.split(",")
end
end
My submitted form data is:
"role"=>{"name"=>"disabled", "resource_id"=>"", "resource_type"=>"Phone", "user_tokens"=>["", "3"]}
(Not sure where the empty string comes from; it's not in the option list, but hey, welcome to Rails).
My problem is with getting the user_tokens data passed into the appropriate variable in the controller.
There are lots of postings suggesting the correct format (e.g., how to permit an array with strong parameters)
If I specify the permitted parameters thus:
params.require(:role).permit(:name, :resource_id, :resource_type, :user_tokens => [] )
I get a '500 Internal server error' with no other details.
If I specify the permit as
params.require(:role).permit(:name, :resource_id, :resource_type, user_tokens: [] )
I don't get any errors (No unpermitted parameters in the dev log) but the user_tokens array is not passed through in the #role.
If I run this scenario through a console I get:
params = ActionController::Parameters.new(:role => {:resource_id => "", :resource_type => "Phone", :user_tokens => ["", "3"]})
params.require(:role).permit(:name, :resource_id, :resource_type, user_tokens: [] )
=> {"resource_id"=>"", "resource_type"=>"Phone", "user_tokens"=>["", "3"]}
params.require(:role).permit(:user_tokens).permitted?
Unpermitted parameters: resource_id, resource_type, user_tokens
=> true
I'm stuck as to where I should try looking next.

Even though I was sure I had already tried it, changing the model to be
def user_tokens=(ids)
self.user_ids = ids
end
fixed it. I've always struggled to understand this bit of notation except when I've spent 4 days trying why my many:many models don't work.
I guess it'd be easier if I was doing this full time rather than just for particular sysadmin projects.

Related

Rails 4 polymorphic has_many ignores table_name

Short version: I'm building a new Rails 4 application that uses (read-only) some tables from a database used by a legacy Rails 2 application, which is still in use. The old application models/tables were very confusingly named, however (especially in the context of the new application), so I want to use different names for the models/tables using self.table_name. This all works perfectly until I tried to add in a polymorphic relationship. Rails ignores my defined table_name and does a query on the type using the new model name, which of course is different so it doesn't work. Is there any way to change this?
Long version: There are three models in this equation, and here they are:
class Exporter < MysqlBase
has_many :lic_exporter_addresses, :as => :place
self.table_name = 'excons'
self.primary_key = 'id'
end
class LicBusiness < MysqlBase
has_one :physical_address, -> { where(category: 'Physical') }, :class_name => 'LicExporterAddress', :as => :place
has_one :mailing_address, -> { where(category: 'Mailing') }, :class_name => 'LicExporterAddress', :as => :place
has_many :lic_exporter_addresses, :as => :place
self.table_name = 'businesses'
self.primary_key = 'id'
end
class LicExporterAddress < MysqlBase
belongs_to :place, polymorphic: true
self.table_name = 'addresses'
self.primary_key = 'id'
end
We have a ton of different kinds of businesses, so the Business model is the most problematic. I really don't want to have that in the new app because it would be very confusing as to what a "business" actually is. With the current code if I go into the rails console and try to get lic_exporter_addresses for a LicBusiness or Exporter, it does:
SELECT `addresses`.* FROM `addresses` WHERE `addresses`.`place_id` = '00044c693f6848f9b0978f873cf9999a' AND `addresses`.`place_type` = 'LicBusiness'
when what I need is place_type = 'Business'.
Is there any way to tell Rails what place_type to look for? I did see this question and the second answer looked promising, except that I'm already sort of doing that with Physical and Mailing addresses so I can't figure out how that'd work with both options at the same time... Thanks for any info or ideas.
In Rails 4.2, it looks like the exact string used for the query is defined as owner.class.base_class.name, where owner is the model declaring the association. So I don't think it's directly supported. But there are a few ways I can think of to hack around this. I think the most promising might be, in LicBusiness:
has_many :lic_exporter_addresses, ->{where place_type: "Business"}, foreign_key: "place_id"
That is, don't define the association as polymorphic, but define the type scope yourself. This will NOT correctly define place_type in the lic_exporter_addresses table if you ever use lic_exporter_address.place = some_lic_business_instance. However you said this table was read-only, so this may in fact not be an issue for you. If it is, there may be ways to override the behavior to get what you need.
Two other ideas both make me very nervous and I think they are probably quite dangerous for unintended side-effects. They are to override LicBusiness.base_class (this might actually be ok if you do not now and never will have STI set up on LicBusiness, but I'm still nervous), or to override LicBusiness.name (I'm pretty sure this would have unintended side-effects).

Rolify and Rails 4 role management

I am trying to implement a role based access system in my rails 4 app, and I want the end user (super_admin) to have the ability to edit role assignments via the UI.
I have achieved some success but can't help feeling that there has to be a better way (Since I'm new to rails). Here is my code:
users_roles_controller.rb
# GET /user_roles/new/:id
def new
#roles = Role.all
end
# POST /user_roles/new/:id
def create
populated = params[:roles][:name].reject(&:empty?)
populated.each do |key|
#user.add_role Role.find(key).name
end
redirect_to users_path
end
And in my Form (HAML and simple_form):
= simple_form_for :roles, :url => create_user_roles_path(#user.id), :method => "post" do |f|
= f.input :name, :collection => #roles, as: :check_boxes
= f.button :submit
I'm struggling with the following:
How do I validate form entries, since there is no model?
Should I be using strong parameters and if so how do I implement on a form without a model
How do I create something similar, but with Current roles already checked? (True role management)
UPDATE
I have looked at using the reform Gem as suggested by the comments. This seems like a great solution. I am however having issues with the implementation on this case.
Let me map it out:
I have 3 tables in the database:
users
users_roles (Mapping Table with 2 Attributes : user_id & role_id {Join Table -> HABTM})
roles
I want to construct a form with all the values in the Roles model as checkboxes.The checkboxes should dictate what values are fed into the users_roles table (With relation to a specific user). What I want reform to do is validate the input of this form. This form will always display all of the values in Roles, but some/all of the boxes might be unchecked.
I have created a form folder in my app and started with the following code:
class UserRoleForm < Reform::Form
property :user__id, on: :user
property :role_id, on: :role
validates :user__id, presence: true
validates :role__id, presence: true
end
Am I going in the right direction?
Thanks for the help.
You need two things to build your form: a user's roles and the possible roles.
If I recall correctly, rolify gives your model associations ad should just let you do something like some_user.roles to return all the roles applied to some_user.
To get possible roles, try Role.all.
To combine both, try
user_roles = some_user.roles.pluck(:name) # suppose this returns ["admin"]
Role.pluck(:name).map { |r| [r, user_roles.include?(r)] }
You now have an array like this that you can use to build your form checkboxes with an each loop.
[["customer", false], ["admin", true], ["editor", false]]
Define your reform object's sync or save method to handle what to do with the submitted input, however you are handling it. You can (SHOULD) make a custom validation to verify if the submitted roles are valid roles.

Rails 4: strong_params,nested_attributes_for and belongs_to association trouble

I really can't get my head around Rails 4 strong parameters, belongs_to association and form with fields_for.
Imagine I have model for quoting some price:
class Quote < ActiveRecord::Base
belongs_to :fee
accepts_nested_attributes_for :fee
Now, I have seeded some fees into the db, and have put some radiobuttons on my form_for #quote using fields_for. The values of the radiobuttons are simply ids of the records.
Here is the troubling part, the controller:
def create
#quote = Quote.new(quote_params)
...
end
def quote_params
params.require(:quote).permit(:amount_from, fee_attributes: [:id])
end
From my understanding, automagically Rails should fetch fee record with some id, but there is some mystic error instead.
params hash is: "quote"=>{"amount_from"=>"1200", "fee_attributes"=>{"id"=>"1"}}
Log tail:
Completed 404 Not Found in 264ms
ActiveRecord::RecordNotFound (Couldn't find Fee with ID=1 for Quote with ID=)
app/controllers/quotes_controller.rb:14:in `create'
I really don't understand what is going on here, have read Rails association guide, googled for hour for all info, but to no avail.
What I want to achieve here is to understand the correct "Rails way" to fetch some associations for new Quote object using some params I've put in the form.
Guess I got nested_attributes_for wrong, somehow thought it would call Fee.find automagically.
I've opted for ditching fields_for helpers from the form and rendering fields manually like
radio_button_tag 'fee[id]', fee.id
Then in controller I have 2 params methods now:
def quote_params
params.require(:quote).permit(:amount_from)
end
def fee_params
params.require(:fee).permit(:id)
end
And my action looks like
def create
#quote = Quote.new(quote_params)
#quote.fee = Fee.find(fee_params[:id])
...
Any additions on best practices when one has to handle lots of different objects with not so straight init logic are welcome.

Globalize: How to show in the view the current locale information

I am currently having a bit of a problem with Globalize gem.
I explain the current situation:
I have a Model called Question. After creating it, without any data stored, I added the following lines to the model:
class Question < ActiveRecord::Base
translates :wording, :answer1, :answer2, :answer3, :answer4
end
Then, I created a migration to create the translations table
class CreateTranslationsTable < ActiveRecord::Migration
def up
Question.create_translation_table! :wording => :string, :answer1 => :string, :answer2 => :string, :answer3 => :string, :answer4 => :string
def end
def down
Question.drop_translation_table!
def end
My default locale is :en. After that I added some data.
If I go execute rails c and put the command Question.first.wording everything works fine.
Although when I execute in 'rails c' I18n.locale = :es and then I do Question.first.wording still displays the english text I put at the beginning.
I tried one thing which it seemed to help me is that I dropped all the translated columns (like specified in Globalize documentation after you migrated data. In my case I didn't have any data to migrate at the beginning though). After that I made a rollback (which got back the columns I deleted form the Question model), then executing Question.first.wording with I18n.locale = :es got it working. Which means that Question.first.wording returns nil.
After that, I implemented the 'Locale from Url Params' as specified in the Ruby on Rails guide
Which means the first URL param si the ':locale' param.
Now the current problem: The view still displays the information in English when it should display it in Spanish, since the URL I entered was http://localhost.com/es/questions/.
How can I make it to display in the view the Spanish information?
My mistake. I interpreted from the documentation that the the chunck of code (in application_controller.rb) that works for setting the url:
def default_url_options(options={})
{ locale: params[:locale] }
end
would actually set the 'I18n.locale' variable. What I did is the next to get around this (in application_controller.rb):
before_action :change_to_current_locale
def change_to_current_locale
I18n.locale = params[:locale]
end
That made it work.

Rails 4, sqlserver-adapter: using alias_attribute to rename legacy attributes

Dealing with a legacy system that has non-Rails conventional naming. Note that all tables and attributes are UPPERCASE, which in the sqlserver-adapter means that ID is NOT the same as id.
I had thought that alias_attribute :new, :OLD allowed you to specify a name that you could use in ActiveRecord/ActiveRelation queries. From what I'm seeing below (tested in rails console), that is not the case.
The end goal is making the legacy system "act" in a Rails-conventional methodology by making each model have an ID attribute, etc...
Model definition:
# app/models/organization.rb
class Organization < ActiveRecord::Base
self.table_name = "ORGANIZATION"
self.primary_key = "ORGANIZATION_ID"
alias_attribute :id, :ORGANIZATION_ID
end
Does not work:
Organization.select(:id) => invalid column name 'id'
Organization.select(:ID) => invalid column name 'ID'
Organization.select("ID") => invalid column name 'ID'
Does work:
Organization.select(:organization_id) => <finds record>
Organization.select(:ORGANIZATION_ID) => <finds record>
Organization.select("organization_id") => <finds record>
Organization.select("ORGANIZATION_ID") => <finds record>
I just used alias_attribute the other day. Honestly, I kept poking at it until I got mine to work.
I was able to achieve the functionality that you are looking for by reversing the params passed to alias_attribute.
Example:
alias_attribute :active?, :active
alias_attribute :alert, :message
alias_attribute :delete_at, :expire_at
Where the first param is what the column name is, and the second is what you are aliasing.
I understand that seems backwards from documentation, however this is how I got this to work.
I solved this situation by configuring the DB adapter (MS SQL Server) to treat the schema as all lowercase. Thus, even though the actual DB table names can be "USERS", "PEOPLE", etc..., I can reference them as "users", "people" in my application.
See: https://github.com/rails-sqlserver/activerecord-sqlserver-adapter#force-schema-to-lowercase
Here's the code I added to resolve this:
# config/initializers/sql_server_adapter.rb
ActiveRecord::ConnectionAdapters::SQLServerAdapter.lowercase_schema_reflection = true