rails-settings-cached – bug with hash field? - ruby-on-rails

I have some global settings I want to be able to change through the user interface. The gem ‘rails-settings-cached’ seems to be the tool of choice for this.
I run into a problem with it though when working with a field of type hash.
With the example given in the installation readme (relevant code below for reference), when I first view the settings page in the browser, the default hash displays ok in the form (in YAML format). After form submission, the hash doesn’t get saved to the database and on page re-render the form displays --- !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}.
On investigation, I found the offending line in the source code YAML.safe_load(value).to_h which fails with error ‘
Tried to load unspecified class: Symbol (Psych::DisallowedClass)
’
If I change this line to:
YAML.safe_load(value, permitted_classes: [Symbol, ActiveSupport::HashWithIndifferentAccess]).to_h
It works as expected. I’m on Rails 6.1.6 and Ruby 3.0.1
My questions:
Am I missing something?
How do I apply this workaround in production? One option I think would be to install the gem directly into my project file rather than adding to the gemfile (bundle install --path vendor/bundle) and make the edit directly from there (and then push to production).
How do I give feedback to the gem author?
Thanks
Daniel
app/models/setting.rb
class Setting < RailsSettings::Base
field :hash_test, type: :hash, default: {
logging: true,
email: "foo#bar.com"
}
end
config/routes.rb
resource :settings
settings_controller.rb
class SettingsController < ApplicationController
def create
setting_params.keys.each do |key|
Setting.send("#{key}=", setting_params[key].strip)
end
redirect_to admin_settings_path, notice: "Setting was successfully updated."
end
private
def setting_params
params.require(:setting).permit :notification_options)
end
end
app/views/settings/show.html.erb
<%= form_for(Setting.new, url: admin_settings_path) do |f| %>
<div class="form-group">
<label class="control-label">Notification options</label>
<%= f.text_area :notification_options, value: YAML.dump(Setting.notification_options), class: "form-control"%>
</div>
<div>
<%= f.submit 'Update Settings' %>
</div>
<% end %>

Related

Saving custom attribute to Order model in Spree eCommerce

I have added a custom field in my spree_orders table (let's call it custom_attribute).
I have added Spree::PermittedAttributes.checkout_attributes << [:custom_attribute] to my spree.rb initializer.
In my checkout process I have a custom form with the following code (html formatting has been removed):
<%= form_for #order do |alt_form| %>
<%= alt_form.label :custom_attribute, "Custom Attribute" %><span class="required">*</span><br />
<%= alt_form.text_field :custom_attribute, :class => 'form-control required', maxlength: 11 %>
<% end %>
This form successfully submits the field in the post request (full dump below) to http://localhost:3000/checkout/update/address as order[custom_attribute] xyz, however, the information is not saved to the model.
_method=patch
_method=patch
authenticity_token=Y+ATRotWKfI57f+b0/YGwIw9Bg6mADHBDmeEOHYzLPnB6Vbydya4ITDTopcX65EG+TiL7bwyJKQPpBU9bQTaUg==
authenticity_token=Y+ATRotWKfI57f+b0/YGwIw9Bg6mADHBDmeEOHYzLPnB6Vbydya4ITDTopcX65EG+TiL7bwyJKQPpBU9bQTaUg==
commit=Save and Continue
order[bill_address_attributes][address1]=123 Test
order[bill_address_attributes][address2]=
order[bill_address_attributes][city]=Test
order[bill_address_attributes][country_id]=232
order[bill_address_attributes][firstname]=Test
order[bill_address_attributes][id]=3
order[bill_address_attributes][lastname]=Test
order[bill_address_attributes][phone]=555555555
order[bill_address_attributes][state_id]=3535
order[bill_address_attributes][zipcode]=30024
order[email]=spree#example.com
order[custom_attribute]=2414
order[state_lock_version]=32
utf8=✓
utf8=✓
I've inserted #order.inspect on the following (payment) page to can see at that point that #order.custom_attribute is still nil.
Does anyone have any idea about what I need to do in order to get the custom_attribute value sent in the post request saved to the model with the other attributes sent?
-------------------edit-------------------
Default spree permitted attributes are defined here https://github.com/spree/spree/blob/3-0-stable/core/lib/spree/core/controller_helpers/strong_parameters.rb and are added on by the strong_paramaters helper here (don't have the rep to post a third link):
module Spree
module Core
module ControllerHelpers
module StrongParameters
def permitted_attributes
Spree::PermittedAttributes
end
delegate *Spree::PermittedAttributes::ATTRIBUTES,
to: :permitted_attributes,
prefix: :permitted
def permitted_payment_attributes
permitted_attributes.payment_attributes + [
source_attributes: permitted_source_attributes
]
end
def permitted_checkout_attributes
permitted_attributes.checkout_attributes + [
bill_address_attributes: permitted_address_attributes,
ship_address_attributes: permitted_address_attributes,
payments_attributes: permitted_payment_attributes,
shipments_attributes: permitted_shipment_attributes
]
end
def permitted_order_attributes
permitted_checkout_attributes + [
line_items_attributes: permitted_line_item_attributes
]
end
def permitted_product_attributes
permitted_attributes.product_attributes + [
product_properties_attributes: permitted_product_properties_attributes
]
end
end
end
end
end
which can be at found spree/core/lib/spree/core/controller_helpers/strong_parameters.rb in the spree github repo.
-------------------final edit-------------------
If anyone finds this in the future and is trying to troubleshoot a similar issue, my code above is actually correct; I had (stupidly) placed it in an if Rails.env.production? block.
I will give you an example, maybe you can translate it into your code.
OPTIONAL
Imagine that I have a custom action, called "custom" on my users controller, defined this way in my routes:
resources :users do
collection do
get 'custom'
post 'custom'
end
end
This way I can call it by using custom_users_path.
Next, I want a form that submits to that function, to do that you need to specify an additional parameter in your form_for called :url, in this example I call it using custom_users_path, once I submit the form, It will run my custom action.
form_for would look like this:
<%= form_for :user, :url => custom_users_path do |f| %>
<%= f.text_field :random %>
<%= f.submit "Submit" %>
<% end %>
Then, I want to be able to access some :random parameter in my users controller. Let's suppose that I have a text_field which I want store the value on my :random parameter (see above). First, you need to permit that parameter to be accessible in your controller, in this example, in users controller. This way:
params.require(:user).permit(YOUR PARAMETER HERE, {:random => []})
So, every time I submit the form, I can access the :submit parameter value, by doing this params["controller-name"]["parameter-name"], translated into this example, would look like:
params["user"]["random"]
You can then convert it into string using to_s if you want.
Output (Supposing that I wrote "444" on my text_field):
444
I hope this helps you.

Audio files always null using Taglib-ruby in rails

I'm trying to build an app in Rails which will take audio file uploads and read metadata off them to populate a database. I'm using the Taglib-ruby gem to handle various file types. The uploads seem to be working on their own, but Taglib considers any file given to it as null.
Here's my controller:
class UploadsController < ApplicationController
require 'taglib'
def new
end
def create
file = params[:upload]
TagLib::FileRef.open(file) do |fileref|
unless fileref.null?
tag = fileref.tag
# properties = fileref.audio_properties
#song = Song.new(title: tag.title, artist: tag.artist, album: tag.album,
year: tag.year, track: tag.track, genre: tag.genre)
if #song.save
redirect_to songs_path
else
render 'new'
end
else
raise "file was null"
end
end
end
end
and my view for form submission:
<h1> Upload </h1>
<%= form_tag(url: { action: :create }, html: { multipart: true }) do %>
<%= label_tag :upload, "Scan your song:" %>
<%= file_field_tag :upload, multiple: true %>
<br />
<%= submit_tag "Submit" %>
<% end %>
Taglib itself seems to be working - adding "require 'taglib'" removed the error I had been getting in regards to that, and a mock-up I made of this outside of rails worked fine (so the files I'm using are also not the problem). Every time I run this, the control flow hits my raise command, and no record is saved. It's clear that fileref.null? is returning true, which suggests to me that there's something wrong with the upload process... but I'm not sure what.
Ideally, I'd like to use the multiple uploads option and run this process on each file sequentially, but I can't even get a single upload to register as anything but null.

Why am I getting 'nil' is not an ActiveModel-compatible object. It must implement :to_partial_path error?

Why am I getting the following error?
nil is not an ActiveModel-compatible object. It must implement :to_partial_path.
I think the error may relate to the tutorial I'm following is using Rails 3.2 while I'm using Rails 4.
Here is the model code:
class DashboardsController < ApplicationController
def show
#text_shout = TextShout.new
#photo_shout = PhotoShout.new
#shouts = current_user.shouts
end
end
class PhotoShoutsController < ApplicationController
def create
content = build_content
shout = current_user.shouts.build(content: content)
if shout.save
redirect_to dashboard_path
else
flash.alert = "Could not shout."
redirect_to dashboard_path
end
end
private
def build_content
PhotoShout.new(photo_shout_parameters)
end
def photo_shout_parameters
params.require(:photo_shout).permit(:image)
end
end
Here is the view code with the error occurring on the _shout.html partial
# app/view/dashboards/show.html.erb
<%= form_for #text_shout do |form| %>
<%= form.text_field :body, placeholder: 'Shout content here' %>
<%= form.submit 'Shout' %>
<% end %>
<%= form_for #photo_shout do |form| %>
<%= form.file_field :image %>
<%= form.submit 'Shout' %>
<% end %>
<%= render #shouts %>
# app/view/shouts/_shout.html.erb
<%= div_for shout do %>
<%= link_to shout.user.username, shout.user %>
shouted
+---------------------------------+
<%= render shout.content %> <--| ERROR "nil' is not an Active " |
| "Model-compatible object" |
+---------------------------------+
<%= link_to time_ago_in_words(shout.created_at), shout %>
<% end %>
# app/views/photo_shouts/_photo_shout.html.erb
<%= image_tag photo_shout.image.url(:shout) %>
Including ActiveModel into plain Ruby objects
The Thoughtbot Intermediate tutorial in Rails 4 has few complications, but, one problem comes in adding ActiveModel functionality into a plain Ruby object. I see this on the Week 3 Video around the half hour mark while the tutor (AKA Mr Halogenandtoast) is extracting the timeline object.
Instead of: extend ActiveModel::Naming you want to include ActiveModel::Model - Rails 4 change to make it easier to work with plain objects.
class Timeline
include ActiveModel::Model
...
...
For Thoughtbot learn subscribers there's a link to the discussion. Strong parameters were the other problem for this otherwise excellent tutorial.
The problem you are having is because you have existing records in your database that don't have content associated to them. This happens because you went from a non-polymorphic setup to a polymorphic setup. What you need to do is look for shouts that are missing content_type and content_id and remove them from the database. Once those are removed, it could be useful to add
validates_associated :content
to your Shout model to ensure data in the future doesn't end up "corrupting" your database.
#shouts = current_user.shouts on this line your #shouts is setting as nil
check for current_user.shouts, it must be returning as nil
Edit:
instead try this
<%= render #shouts.content %>
I found this error in my development log, which was the issue all along. I was super confused for a little while.
[paperclip] An error was received while processing <Paperclip::Errors::CommandNotFoundError: Could not run the `identify` command. Please install ImageMagick.>
Looks like the fix is just to run brew update (optional) and brew install imagemagick, for anyone else looking for the fix to the thoughtbot tutorial.
Installing imagemagick will solve the issue.
Check requirements section in Readme
https://github.com/thoughtbot/paperclip#requirements
https://github.com/thoughtbot/paperclip#image-processor
brew install imagemagick did the trick.
I was having a similar error, even after cleaning out the database of any old records. The PhotoShout was being saved into the database with content_id: nil, which was clearly the source of the problem.
I cleaned out the database once more, ran brew install imagemagick and the photos began uploading successfully.

Rails - Add a method to a textfield

I'm trying to get the checkSwear method to run on each textfield before it's submitted..
I have basically this: (stripped down)
<%= form_for(#profile) do |f| %>
<div class="field">
<%= f.label 'I love to ' %>
<%= f.text_field :loveTo %>
</div>
<div class="field">
<%= f.label 'I hate to ' %>
<%= f.text_field :hateTo %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In my controller I have:
def checkSwear
antiSwear.checkSwear(What goes here?)
end
In routes:
match '/check' => 'profiles#checkSwear'
Any help much appreciated!
(checkSwear is a separate gem; i.e. a separate problem! The what does here means what kind of variable is received from the form, to be put through the checkswear gem)
UPDATE:
Sorry for the camelcasing, I'm a Java developer studying Rails etc., old habits die hard. This is for a project. I'm supposed to be writing a small gem to do some ruby logic and apply it to something. The contents of the gem are:
module antiSwear
#swearwords = ["f**k", "f***ing", "shit", "shitting", "lecturer"]
#replacements = ["fornicate", "copulating", "poop", "pooping", "Jonathan"]
def self.checkText(text)
#swearwords.each do |swearword|
if text.include?(swearword)
index = #swearwords.index(swearword)
replacement = #replacements[index]
text.gsub(swearword, replacement)
end
end
return text
end
end
:/
This should really be done in model validations.
class Profile < ActiveRecord::Base
validate :deny_swearing
private
def deny_swearing
if AntiSwear.check_swear(love_to) || AntiSwear.check_swear(hate_to)
errors.add_to_base('Swearing is not allowed.')
end
end
end
That said, if you insist on this being in controller, you can check params[:profile][:love_to] and params[:profile][:hate_to] to see what's been submitted.
P.S. In this example I used proper ruby naming conventions, since we don't use "camelCasing".
Are you doing this as part of validation? You can do it one of a few ways. You can run the check before save, via a custom validation method or override the setter directly. I show you the custom validation approach here:
class Profile < ActiveRecord::Base
validate :clean_loveTo
protected
def clean_loveTo
errors.add(:loveTo, "can't contain swears") if antiSwear.checkSwear(loveTo)
end
end
I'm assuming checkSwear returns a boolean here.
I'd use an intersection on arrays, one of which is the source text split into words, then gsub the replacements in. You have to be sure to have a 1:1 relationship between the words and their replacements, in which case I'd suggest using a hash for your dictionary (coincidentally what hashes are sometimes called in other languages).
module antiSwear
# var names changed for formatting
#swears = ["f**k", "f***ing", "shit", "shitting", "lecturer"]
#cleans = ["fornicate", "copulating", "poop", "pooping", "Jonathan"]
def self.checkText(text)
# array intersection. "which elements do they have in common?"
bad = #swears & text.split # text.split = Array
# replace swear[n] with clean[n]
bad.each { |badword| text.gsub(/#{badword}/,#cleans[#swears.index(badword)] }
end
end
You might need to futz with text.split arguments if the replacement gets hung up on \n & \r stuff.

ActionController::InvalidAuthenticityToken in SessionsController#create error

I get this error in Rails 2.3.9 but not in 2.3.8. I didn't changed any code. Did I missed anything?
ActionController::InvalidAuthenticityToken in SessionsController#create ActionController::InvalidAuthenticityToken
Thanks :)
Here are the added details.
Request
Parameters:
{"commit"=>"Login",
"authenticity_token"=>"A9A4+sCsA/81FFoXJEUNziQYhgQ38pceGN2i7MUQbQY=",
"password"=>"r3dp0rt"}
Here's the code in the application controller
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery :secret => "r3dp0rtP#$$", :digest => "MD5" # See ActionController::RequestForgeryProtection for details
Here's the code from my session create controller
def create
session[:password] = params[:password]
flash[:notice] = "Sucessfully logged in"
redirect_to "/login"
end
and lastly here's the code from my simple login view
<div id="placeholder">
<% form_tag :action => "create" do %>
<p>
<%= label_tag "This will enable administrative features for the site." %><br>
<%= password_field_tag "password" %>
</p>
<br>
<p>
<%= submit_tag "Login" %>
</p>
<% end %>
</div>
There's a bug in the 2.3.9. It prevents to set the session ID when using an activerecord or memcache session store. See this rails ticket. You can fix it by using the Mislav's patch at http://gist.github.com/570149. You'll have to create and paste the code in config/initializers/sessions_patch.rb. Or you can run the following command in your project root path:
wget http://gist.github.com/570149.txt -O config/initializers/sessions_patch.rb
Finally don't forget to restart your server (and a maybe issue a rake db:sessions:clear).
I don't have enough points to leave as a comment to the accepted answer so I will add this as an answer. The patch does work but just be careful to name it sessions_patch.rb so it will be ordered alphabetically AFTER session_store.rb. As I found out the hard way (by mistakenly naming the patch session_patch.rb, the order of the initializers matters and the patch won't work if it is loaded before your key and secret are set in session_store.rb. Hopefully this saves someone some time.
Have you tried clearing the browsing data of your browser? Most likely it's still sending the old AuthenticityToken.

Resources